Skip to content

Commit

Permalink
feat: add sidebar, add small animations, some bug fixing and improvem…
Browse files Browse the repository at this point in the history
…ents
  • Loading branch information
JanStevens committed Apr 27, 2024
1 parent 2aef13d commit 27e0de3
Show file tree
Hide file tree
Showing 16 changed files with 385 additions and 122 deletions.
8 changes: 8 additions & 0 deletions panda.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ export default defineConfig({
// Useful for theme customization
theme: {
extend: {
keyframes: {
show: {
'100%': { opacity: 1 },
},
enemyCardPlayed: {
'100%': { opacity: 0.5, filter: 'grayscale(1)' },
},
},
tokens: {
fonts: {
philosopher: { value: 'var(--font-philosopher), sans-serif' },
Expand Down
43 changes: 43 additions & 0 deletions src/components/@common/Drawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use client';

import { Dialog as ArkDrawer } from '@ark-ui/react/dialog';
import { ark } from '@ark-ui/react/factory';
import { styled } from '@style/jsx';
import { drawer } from '@style/recipes';
import { createStyleContext } from 'lib/create-style-context';
import type { ComponentProps } from 'react';

const { withProvider, withContext } = createStyleContext(drawer);

export const Root = withProvider(ArkDrawer.Root);
export const Backdrop = withContext(styled(ArkDrawer.Backdrop), 'backdrop');
export const Body = withContext(styled(ark.div), 'body');
export const CloseTrigger = withContext(
styled(ArkDrawer.CloseTrigger),
'closeTrigger',
);
export const Content = withContext(styled(ArkDrawer.Content), 'content');
export const Description = withContext(
styled(ArkDrawer.Description),
'description',
);
export const Footer = withContext(styled(ark.div), 'footer');
export const Header = withContext(styled(ark.div), 'header');
export const Positioner = withContext(
styled(ArkDrawer.Positioner),
'positioner',
);
export const Title = withContext(styled(ArkDrawer.Title), 'title');
export const Trigger = withContext(styled(ArkDrawer.Trigger), 'trigger');

export type RootProps = ComponentProps<typeof Root>;
export type BackdropProps = ComponentProps<typeof Backdrop>;
export type BodyProps = ComponentProps<typeof Body>;
export type CloseTriggerProps = ComponentProps<typeof CloseTrigger>;
export type ContentProps = ComponentProps<typeof Content>;
export type DescriptionProps = ComponentProps<typeof Description>;
export type FooterProps = ComponentProps<typeof Footer>;
export type HeaderProps = ComponentProps<typeof Header>;
export type PositionerProps = ComponentProps<typeof Positioner>;
export type TitleProps = ComponentProps<typeof Title>;
export type TriggerProps = ComponentProps<typeof Trigger>;
1 change: 1 addition & 0 deletions src/components/@common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './Badge';
export * from './Button';
export * as Card from './Card';
export * as Dialog from './Dialog';
export * as Drawer from './Drawer';
export * from './Heading';
export * from './IconButton';
export * from './Input';
Expand Down
1 change: 1 addition & 0 deletions src/components/@scenario/CardThumbnail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const CardThumbnail = ({ name, image }: Props) => {
justifyContent="center"
alignItems="center"
height="75px"
width="auto"
aspectRatio="128/147"
position="relative"
cursor="pointer"
Expand Down
21 changes: 18 additions & 3 deletions src/components/@scenario/EnemyCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import { Box, Divider } from '@style/jsx';
import { Icon } from 'icons';
import { useShallow } from 'zustand/react/shallow';

import { useInitiative } from 'hooks/useInitiative';
import { useStore } from 'services/stores';
import type { BossDeck, MonsterDeck } from 'types/deck.types';

Expand All @@ -18,7 +20,10 @@ interface Props {
}

const EnemyCard = ({ deck }: Props) => {
const activeCard = useStore((state) => state.activeCards[deck.name]);
const [activeCard] = useStore(
useShallow((state) => [state.activeCards[deck.name]]),
);
const { isActiveTurn, hasPlayed } = useInitiative();
const { closeEnemy, selectCard, clearCard } = useStore(
(state) => state.actions,
);
Expand All @@ -28,8 +33,18 @@ const EnemyCard = ({ deck }: Props) => {

const handleClearCard = () => clearCard(deck.name);

const hasEnemyPlayed = hasPlayed(deck.name);
const isEnemyActive = isActiveTurn(deck.name);

return (
<Card.Root>
<Card.Root
animation={
hasEnemyPlayed ? 'enemyCardPlayed 300ms ease-out forwards' : ''
}
outlineColor="zinc.700"
outlineStyle="solid"
outlineWidth={isEnemyActive ? '2px' : '0'}
>
<Card.Header flexDir="row" gap="4" pt="3" px="3" pb="0">
<CardThumbnail name={deck.name} image={deck.image} />
{deck.isBoss ? (
Expand All @@ -52,7 +67,7 @@ const EnemyCard = ({ deck }: Props) => {
</Card.Header>
<Divider my="4" />
<Card.Body px="3" pb="3">
{activeCard ? (
{activeCard && !hasEnemyPlayed ? (
deck.isBoss ? (
<BossAbilityCard
card={activeCard}
Expand Down
71 changes: 0 additions & 71 deletions src/components/@scenario/InitiativeList.tsx

This file was deleted.

102 changes: 102 additions & 0 deletions src/components/@scenario/InitiativeList/InitiativeList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
'use client';

import { Box, Stack } from '@style/jsx';
import { Icon } from 'icons';
import { ArrowLeftFromLineIcon } from 'lucide-react';
import { useLayoutEffect, useState } from 'react';

import { useInitiative } from 'hooks/useInitiative';
import { useStore } from 'services/stores';
import { CharacterNames } from 'types/character.types';
import { EnemyNames } from 'types/enemies.types';

import { Drawer, IconButton } from 'components/@common';

import Item from './Item';
import Widget from './Widget';

const InitiativeList = () => {
const [drawerOpen, setDrawerOpen] = useState(false);
const { initiatives, activeTurn, hasPlayed, roundEnded } = useInitiative();
const { toggleInitiativePlayed } = useStore((state) => state.actions);

const handleToggleInitiativePlayed = (name: CharacterNames | EnemyNames) => {
if (name === activeTurn || hasPlayed(name)) {
toggleInitiativePlayed(name);
}
};

useLayoutEffect(() => {
if (roundEnded) setDrawerOpen(false);
}, [roundEnded]);

return (
<Box
width={75}
borderLeft="1px"
borderColor="border.subtle"
borderStyle="solid"
my="8"
px="4"
alignItems="center"
flexDirection="column"
justifyContent="space-between"
display={{ smDown: 'none', base: 'flex' }}
>
<Stack gap={6} flexDirection="column" alignItems="center" mb={6}>
{initiatives.map((initiative) => (
<Widget
key={initiative.name}
activeTurn={initiative.name === activeTurn}
initiative={initiative}
onClick={handleToggleInitiativePlayed}
/>
))}
</Stack>

{!!initiatives.length && (
<Drawer.Root
open={drawerOpen}
onOpenChange={(e) => setDrawerOpen(e.open)}
>
<Drawer.Trigger asChild>
<ArrowLeftFromLineIcon size={24} />
</Drawer.Trigger>
<Drawer.Backdrop />
<Drawer.Positioner>
<Drawer.Content gridTemplateRows="auto 1fr 0">
<Drawer.Header
flexDirection="row"
alignItems="center"
display="flex"
justifyContent="space-between"
>
<Drawer.Title fontSize="2xl" fontWeight="normal">
Initiative overview
</Drawer.Title>
<Drawer.CloseTrigger asChild>
<IconButton variant="ghost">
<Icon name="close" />
</IconButton>
</Drawer.CloseTrigger>
</Drawer.Header>
<Drawer.Body>
<Stack gap={4} flexDirection="column">
{initiatives.map((initiative) => (
<Item
key={initiative.name}
initiative={initiative}
onClick={handleToggleInitiativePlayed}
/>
))}
</Stack>
</Drawer.Body>
</Drawer.Content>
</Drawer.Positioner>
</Drawer.Root>
)}
</Box>
);
};

export default InitiativeList;
47 changes: 47 additions & 0 deletions src/components/@scenario/InitiativeList/Item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Box } from '@style/jsx';
import { CHARACTERS } from 'data/characters';

import { CharacterNames } from 'types/character.types';
import { EnemyNames } from 'types/enemies.types';
import { Initiative } from 'types/initiative.types';

import { Text } from 'components/@common';

import Thumbnail from './Thumbnail';
import { isCharacterName } from './utils';

interface Props {
initiative: Initiative;
onClick: (name: CharacterNames | EnemyNames) => void;
}

const Item = ({ initiative, onClick }: Props) => {
return (
<Box
key={initiative.name}
cursor="pointer"
onClick={() => onClick(initiative.name)}
display="flex"
alignItems="center"
justifyContent="space-between"
gap={4}
>
<Box display="flex" alignItems="center" gap={4}>
<Thumbnail initiative={initiative} size={45} />
<Text
fontSize="2xl"
color={initiative.played ? 'sand.5' : 'fg.default'}
>
{isCharacterName(initiative.name)
? CHARACTERS[initiative.name].spoilerName
: initiative.name}
</Text>
</Box>
<Text fontSize="xl" color={initiative.played ? 'sand.5' : 'sand.10'}>
{initiative.initiative}
</Text>
</Box>
);
};

export default Item;
Loading

0 comments on commit 27e0de3

Please sign in to comment.