From 5b76fe6496012c9645493e03dd3ac502dc67296e Mon Sep 17 00:00:00 2001 From: Anish George Date: Wed, 17 Jun 2026 10:19:53 +1000 Subject: [PATCH 01/29] chore: update formatting --- apps/ageorgedev-e2e/playwright.config.ts | 4 +- apps/ageorgedev-e2e/tests/smoke.spec.ts | 24 ++--- apps/ageorgedev/postcss.config.js | 2 +- .../src/components/GlobalProviders.tsx | 4 +- .../HomeAboveFold/HomeAboveFold.spec.tsx | 28 ++--- .../HomeAboveFold/HomeAboveFold.tsx | 12 +-- .../src/components/Resume/Skill.spec.tsx | 38 +++---- .../src/components/Resume/Skill.tsx | 42 ++++---- .../src/components/Resume/SocialLink.spec.tsx | 53 ++++----- .../src/components/Resume/SocialLink.tsx | 32 +++--- apps/ageorgedev/src/mdx-components.tsx | 6 +- apps/ageorgedev/src/router.tsx | 10 +- apps/ageorgedev/src/routes/__root.tsx | 16 +-- .../src/routes/_noLayout/resume.tsx | 36 +++---- apps/ageorgedev/src/routes/_public.tsx | 18 ++-- apps/ageorgedev/src/routes/_public/index.tsx | 8 +- .../src/routes/_public/talks.index.tsx | 10 +- .../src/routes/_public/talks.tailwind.tsx | 10 +- apps/ageorgedev/vite.config.ts | 16 +-- apps/ageorgedev/vitest.config.ts | 6 +- apps/design-docs/.storybook/main.ts | 24 ++--- apps/design-docs/.storybook/preview.tsx | 12 +-- apps/design-docs/postcss.config.js | 2 +- apps/design-docs/stories/colors.stories.tsx | 42 ++++---- .../stories/elevations.stories.tsx | 16 +-- apps/design-docs/stories/spacing.stories.tsx | 16 +-- apps/design-docs/vite.config.ts | 8 +- apps/dnd-cli/src/App.tsx | 71 ++++++------ apps/dnd-cli/src/index.tsx | 34 +++--- apps/dnd-cli/src/lib/defaults.ts | 4 +- apps/dnd-cli/src/lib/generateFile.ts | 28 ++--- apps/dnd-cli/src/steps/AbilitiesStep.tsx | 46 ++++---- apps/dnd-cli/src/steps/CombatStep.tsx | 96 ++++++++--------- apps/dnd-cli/src/steps/FilenameStep.tsx | 34 +++--- apps/dnd-cli/src/steps/IdentityStep.tsx | 80 +++++++------- apps/dnd-cli/src/steps/ProficienciesStep.tsx | 72 ++++++------- apps/dnd-cli/src/types.ts | 38 +++---- .../src/components/GlobalProviders.tsx | 4 +- apps/game-tools/src/router.tsx | 10 +- apps/game-tools/src/routes/__root.tsx | 16 +-- apps/game-tools/src/routes/_public.tsx | 8 +- .../routes/_public/dnd/characters/example.tsx | 8 +- .../routes/_public/dnd/characters/index.tsx | 6 +- apps/game-tools/src/routes/_public/index.tsx | 6 +- apps/game-tools/vite.config.ts | 12 +-- apps/game-tools/vitest.config.ts | 6 +- biome.json | 8 +- .../src/TwLogo/TwLogo.spec.tsx | 34 +++--- .../brand-components/src/TwLogo/TwLogo.tsx | 4 +- .../brand-components/src/TwLogo/styles.ts | 4 +- packages/brand-components/src/index.ts | 2 +- packages/brand-components/vitest.config.ts | 6 +- .../src/CodeBlock/CodeBlock.spec.tsx | 62 +++++------ .../src/CodeBlock/CodeBlock.stories.tsx | 32 +++--- .../design-system/src/CodeBlock/CodeBlock.tsx | 36 +++---- .../src/callouts/CalloutBadge.spec.tsx | 70 ++++++------ .../src/callouts/CalloutBadge.stories.tsx | 30 +++--- .../src/callouts/CalloutBadge.tsx | 24 ++--- .../design-system/src/cards/Card.spec.tsx | 40 +++---- packages/design-system/src/cards/Card.tsx | 4 +- .../design-system/src/cards/TiltCard.spec.tsx | 102 +++++++++--------- .../src/cards/TiltCard.stories.tsx | 28 ++--- packages/design-system/src/cards/TiltCard.tsx | 30 +++--- .../design-system/src/logo/NameLogo.spec.tsx | 52 ++++----- .../src/logo/NameLogo.stories.tsx | 20 ++-- packages/design-system/src/logo/NameLogo.tsx | 14 +-- .../src/logo/ShortNameLogo.spec.tsx | 36 +++---- .../design-system/src/logo/ShortNameLogo.tsx | 8 +- packages/design-system/src/logo/styles.ts | 14 +-- .../src/theming/ThemeContext.tsx | 16 +-- .../src/theming/ThemeProvider.tsx | 77 ++++++------- .../src/theming/ThemeSwitcher.spec.tsx | 46 ++++---- .../src/theming/ThemeSwitcher.tsx | 18 ++-- .../src/theming/__mocks__/ThemeProvider.tsx | 24 ++--- packages/design-system/src/theming/models.ts | 2 +- .../src/theming/theme-init-script.ts | 2 +- .../src/theming/theme-state.spec.ts | 44 ++++---- .../design-system/src/theming/theme-state.ts | 30 +++--- .../src/typography/Text.spec.tsx | 102 +++++++++--------- .../design-system/src/typography/Text.tsx | 47 ++++---- .../src/typography/core-type-classes.ts | 22 ++-- .../typography/typography-components.spec.tsx | 26 ++--- .../src/typography/typography-components.tsx | 48 ++++----- .../src/typography/typography.stories.tsx | 22 ++-- .../design-system/src/ui/button.stories.tsx | 26 ++--- packages/design-system/src/ui/button.tsx | 18 ++-- packages/design-system/vitest.config.ts | 6 +- .../src/characters/example-wizard.data.ts | 8 +- .../src/characters/example.tsx | 6 +- .../src/components/AbilityBox/AbilityBox.tsx | 56 +++++----- .../src/components/AttackList/AttackList.tsx | 70 ++++++------ .../BackstoryBlock/BackstoryBlock.tsx | 12 +-- .../src/components/CharacterSheet.tsx | 28 ++--- .../ClassFeatures/ClassFeatures.tsx | 10 +- .../src/components/CombatRow/CombatRow.tsx | 18 ++-- .../EquipmentBlock/EquipmentBlock.tsx | 12 +-- .../EquipmentTraining/EquipmentTraining.tsx | 22 ++-- .../src/components/Feats/Feats.tsx | 12 +-- .../HeroicInspiration/HeroicInspiration.tsx | 8 +- .../ProficiencyBlock/ProficiencyBlock.tsx | 12 +-- .../components/SheetHeader/SheetHeader.tsx | 44 ++++---- .../SpeciesTraits/SpeciesTraits.tsx | 12 +-- .../SpellcastingBlock/SpellLevelSection.tsx | 12 +-- .../components/SpellcastingBlock/SpellRow.tsx | 34 +++--- .../SpellcastingBlock/SpellcastingBlock.tsx | 34 +++--- .../SpellcastingBlock/SpellcastingHeader.tsx | 20 ++-- .../src/components/SpellcastingBlock/index.ts | 2 +- .../StandardCharacterSheet.tsx | 38 +++---- .../src/components/layout/BigNumber.tsx | 4 +- .../src/components/layout/Page.tsx | 6 +- .../src/components/layout/Panel.tsx | 24 ++--- .../src/components/layout/PanelTitle.tsx | 4 +- .../src/components/layout/SubPanel.tsx | 8 +- .../src/components/layout/checkables.tsx | 18 ++-- .../src/components/layout/dividers.tsx | 6 +- packages/dnd-character-sheet/src/index.ts | 4 +- .../src/lib/calculate-derived-stats.ts | 50 ++++----- .../src/lib/models/abilities.ts | 8 +- .../src/lib/models/attacks.ts | 46 ++++---- .../src/lib/models/character.ts | 100 ++++++++--------- .../src/lib/models/derived-stats.ts | 20 ++-- .../src/lib/models/feature.ts | 6 +- .../src/lib/models/index.ts | 8 +- .../src/lib/models/skills.ts | 8 +- .../src/lib/models/spellcasting.ts | 44 ++++---- packages/foundation-styles/postcss.config.js | 2 +- .../src/components/deck-styles.ts | 20 ++-- .../src/components/slide-components.tsx | 34 +++--- .../components/slide-layout-builders.spec.tsx | 48 ++++----- .../src/components/slide-layout-builders.tsx | 14 +-- .../src/components/slide-types.tsx | 28 ++--- packages/reveal-framework/src/globals.ts | 4 +- .../src/hooks/useRevealFramework.spec.ts | 48 ++++----- .../src/hooks/useRevealFramework.ts | 24 ++--- packages/reveal-framework/src/index.ts | 12 +-- packages/reveal-framework/vitest.config.ts | 6 +- .../src/diagrams/D01TailwindFilesize.tsx | 4 +- .../src/diagrams/D02CssPipeline.tsx | 4 +- packages/talk-tailwind/src/index.tsx | 36 +++---- .../talk-tailwind/src/slides/s01-intro.tsx | 8 +- .../src/slides/s02-fundamentals.tsx | 26 ++--- .../src/slides/s03-anti-patterns.tsx | 22 ++-- .../src/slides/s04-lean-config.tsx | 20 ++-- .../src/slides/s04b-cognitive-load.tsx | 17 +-- .../src/slides/s05-class-mgt.tsx | 24 ++--- .../talk-tailwind/src/slides/s06-plugins.tsx | 26 ++--- .../src/slides/s07-override-theming.tsx | 24 ++--- .../src/slides/s08-dark-mode.tsx | 35 +++--- .../src/slides/s09-css-in-js.tsx | 12 +-- .../talk-tailwind/src/slides/s10-outro.tsx | 6 +- .../testing-config/react-jsdom-test-setup.ts | 10 +- packages/toolbelt/src/cn.spec.ts | 38 +++---- packages/toolbelt/src/cn.ts | 6 +- packages/toolbelt/src/ramda-additions.spec.ts | 16 +-- packages/toolbelt/src/ramda-additions.ts | 8 +- packages/toolbelt/vitest.config.ts | 4 +- packages/ts-config/css-modules.d.ts | 12 +-- packages/ts-config/react-globals.d.ts | 10 +- syncpackrc.cjs | 4 +- 159 files changed, 1891 insertions(+), 1865 deletions(-) diff --git a/apps/ageorgedev-e2e/playwright.config.ts b/apps/ageorgedev-e2e/playwright.config.ts index c21707e6..21150f9e 100644 --- a/apps/ageorgedev-e2e/playwright.config.ts +++ b/apps/ageorgedev-e2e/playwright.config.ts @@ -1,4 +1,4 @@ -import { defineConfig, devices } from '@playwright/test' +import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './tests', @@ -11,4 +11,4 @@ export default defineConfig({ use: { ...devices['Desktop Chrome'] }, }, ], -}) +}); diff --git a/apps/ageorgedev-e2e/tests/smoke.spec.ts b/apps/ageorgedev-e2e/tests/smoke.spec.ts index 45ef1c2f..d786006a 100644 --- a/apps/ageorgedev-e2e/tests/smoke.spec.ts +++ b/apps/ageorgedev-e2e/tests/smoke.spec.ts @@ -1,20 +1,20 @@ -import { expect, test } from '@playwright/test' +import { expect, test } from '@playwright/test'; test('home page renders', async ({ page }) => { - await page.goto('/') - await expect(page.getByRole('heading', { name: 'Anish' })).toBeVisible() - await expect(page.getByRole('heading', { name: 'George' })).toBeVisible() + await page.goto('/'); + await expect(page.getByRole('heading', { name: 'Anish' })).toBeVisible(); + await expect(page.getByRole('heading', { name: 'George' })).toBeVisible(); await expect( page.getByRole('heading', { name: 'Web Developer' }) - ).toBeVisible() -}) + ).toBeVisible(); +}); test('resume page renders', async ({ page }) => { - await page.goto('/resume') - await expect(page.locator('body')).not.toBeEmpty() -}) + await page.goto('/resume'); + await expect(page.locator('body')).not.toBeEmpty(); +}); test('talks page renders', async ({ page }) => { - await page.goto('/talks') - await expect(page.locator('body')).not.toBeEmpty() -}) + await page.goto('/talks'); + await expect(page.locator('body')).not.toBeEmpty(); +}); diff --git a/apps/ageorgedev/postcss.config.js b/apps/ageorgedev/postcss.config.js index 60bf9096..8984cd25 100644 --- a/apps/ageorgedev/postcss.config.js +++ b/apps/ageorgedev/postcss.config.js @@ -8,4 +8,4 @@ export default { 'postcss-nesting': {}, '@tailwindcss/postcss': {}, }, -} +}; diff --git a/apps/ageorgedev/src/components/GlobalProviders.tsx b/apps/ageorgedev/src/components/GlobalProviders.tsx index f2af2fb8..9f7816de 100644 --- a/apps/ageorgedev/src/components/GlobalProviders.tsx +++ b/apps/ageorgedev/src/components/GlobalProviders.tsx @@ -1,5 +1,5 @@ -import { ThemeProvider } from '@ageorgedev/design-system/theming/ThemeProvider' +import { ThemeProvider } from '@ageorgedev/design-system/theming/ThemeProvider'; export function GlobalProviders({ children }: React.PropsWithChildren) { - return {children} + return {children}; } diff --git a/apps/ageorgedev/src/components/HomeAboveFold/HomeAboveFold.spec.tsx b/apps/ageorgedev/src/components/HomeAboveFold/HomeAboveFold.spec.tsx index 64f526e6..b2e5edd8 100644 --- a/apps/ageorgedev/src/components/HomeAboveFold/HomeAboveFold.spec.tsx +++ b/apps/ageorgedev/src/components/HomeAboveFold/HomeAboveFold.spec.tsx @@ -1,25 +1,25 @@ -import { render, screen } from '@testing-library/react' -import { describe, expect, it } from 'vitest' -import { HomeAboveFold } from './HomeAboveFold' +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { HomeAboveFold } from './HomeAboveFold'; describe('HomeAboveFold', () => { it('renders a heading with the title', () => { - render() + render(); expect( screen.getByRole('heading', { name: /web developer/i }) - ).toBeInTheDocument() - }) + ).toBeInTheDocument(); + }); it('renders the subtitle text', () => { - render() + render(); expect( screen.getByText(/architecting web experiences since 2016/i) - ).toBeInTheDocument() - }) + ).toBeInTheDocument(); + }); it('renders social links to GitHub and LinkedIn', () => { - render() - expect(screen.getByRole('link', { name: /github/i })).toBeInTheDocument() - expect(screen.getByRole('link', { name: /linkedin/i })).toBeInTheDocument() - }) -}) + render(); + expect(screen.getByRole('link', { name: /github/i })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: /linkedin/i })).toBeInTheDocument(); + }); +}); diff --git a/apps/ageorgedev/src/components/HomeAboveFold/HomeAboveFold.tsx b/apps/ageorgedev/src/components/HomeAboveFold/HomeAboveFold.tsx index d5422362..9aae3d17 100644 --- a/apps/ageorgedev/src/components/HomeAboveFold/HomeAboveFold.tsx +++ b/apps/ageorgedev/src/components/HomeAboveFold/HomeAboveFold.tsx @@ -1,11 +1,11 @@ -import { NameLogo } from '@ageorgedev/design-system/logo/NameLogo' +import { NameLogo } from '@ageorgedev/design-system/logo/NameLogo'; import { Heading3, Heading4, -} from '@ageorgedev/design-system/typography/typography-components' -import { cn } from '@ageorgedev/toolbelt/cn' -import { GithubLogoIcon, LinkedinLogoIcon } from '@phosphor-icons/react/ssr' -import styles from './HomeAboveFold.module.css' +} from '@ageorgedev/design-system/typography/typography-components'; +import { cn } from '@ageorgedev/toolbelt/cn'; +import { GithubLogoIcon, LinkedinLogoIcon } from '@phosphor-icons/react/ssr'; +import styles from './HomeAboveFold.module.css'; export function HomeAboveFold() { return ( @@ -62,5 +62,5 @@ export function HomeAboveFold() { - ) + ); } diff --git a/apps/ageorgedev/src/components/Resume/Skill.spec.tsx b/apps/ageorgedev/src/components/Resume/Skill.spec.tsx index 920b0f43..e84a4c2f 100644 --- a/apps/ageorgedev/src/components/Resume/Skill.spec.tsx +++ b/apps/ageorgedev/src/components/Resume/Skill.spec.tsx @@ -1,28 +1,28 @@ -import { render, screen } from '@testing-library/react' -import { describe, expect, it } from 'vitest' -import { Skill } from './Skill' +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { Skill } from './Skill'; describe('Skill', () => { it('renders the skill name', () => { - render() - expect(screen.getByText('TypeScript')).toBeInTheDocument() - }) + render(); + expect(screen.getByText('TypeScript')).toBeInTheDocument(); + }); it('renders for each skill type without throwing', () => { - const { rerender } = render() - expect(screen.getByText('React')).toBeInTheDocument() + const { rerender } = render(); + expect(screen.getByText('React')).toBeInTheDocument(); - rerender() - expect(screen.getByText('Performance')).toBeInTheDocument() + rerender(); + expect(screen.getByText('Performance')).toBeInTheDocument(); - rerender() - expect(screen.getByText('Mentoring')).toBeInTheDocument() - }) + rerender(); + expect(screen.getByText('Mentoring')).toBeInTheDocument(); + }); it('renders for all valid levels without throwing', () => { - const { rerender } = render() - rerender() - rerender() - expect(screen.getByText('skill')).toBeInTheDocument() - }) -}) + const { rerender } = render(); + rerender(); + rerender(); + expect(screen.getByText('skill')).toBeInTheDocument(); + }); +}); diff --git a/apps/ageorgedev/src/components/Resume/Skill.tsx b/apps/ageorgedev/src/components/Resume/Skill.tsx index 4bda756e..03061105 100644 --- a/apps/ageorgedev/src/components/Resume/Skill.tsx +++ b/apps/ageorgedev/src/components/Resume/Skill.tsx @@ -1,18 +1,18 @@ -import { cn } from '@ageorgedev/toolbelt/cn' -import { map } from 'ramda' -import styles from './Skill.module.css' +import { cn } from '@ageorgedev/toolbelt/cn'; +import { map } from 'ramda'; +import styles from './Skill.module.css'; -type SkillType = 'tool' | 'technique' | 'human' -type SkillLevel = 3 | 4 | 5 +type SkillType = 'tool' | 'technique' | 'human'; +type SkillLevel = 3 | 4 | 5; type SkillProps = { - name: string - type: SkillType - level: SkillLevel -} + name: string; + type: SkillType; + level: SkillLevel; +}; export function Skill(props: SkillProps) { - const styleClasses = levelClasses[props.type][props.level] + const styleClasses = levelClasses[props.type][props.level]; return (
{props.name}
- ) + ); } -type SkillClassMap = Record +type SkillClassMap = Record; const primaryAccentColorClasses: SkillClassMap = { 3: 'bg-primary-surface text-primary-onsurface', 4: 'bg-primary-surface-2 text-primary-onsurface-2', 5: `${styles.highSkill} bg-primary-surface-2 text-primary-onsurface-2`, // TODO -} +}; const secondaryAccentColorClasses: SkillClassMap = { 3: 'bg-secondary-surface text-secondary-onsurface', 4: 'bg-secondary-surface-2 text-secondary-onsurface-2', 5: `${styles.highSkill} bg-secondary-surface-2 text-secondary-onsurface-2`, -} +}; const levelClassesForHuman: SkillClassMap = { 3: 'bg-primary-surface text-primary-onsurface', 4: 'bg-primary-surface-2 text-primary-onsurface-2', 5: `${styles.highSkill} bg-primary-surface-2 text-primary-onsurface-2`, // TODO -} +}; const levelClasses: Record = { tool: primaryAccentColorClasses, technique: secondaryAccentColorClasses, human: levelClassesForHuman, -} +}; const skillMapper = (type: SkillType) => @@ -58,7 +58,7 @@ const skillMapper = name, level, type, - }) + }); export const toolSkills: SkillProps[] = map(skillMapper('tool'), [ ['React', 5], @@ -73,7 +73,7 @@ export const toolSkills: SkillProps[] = map(skillMapper('tool'), [ ['AgGrid', 3], ['Node', 4], ['Ramda', 4], -]) +]); export const techSkills: SkillProps[] = map(skillMapper('technique'), [ ['Performance', 4], @@ -89,7 +89,7 @@ export const techSkills: SkillProps[] = map(skillMapper('technique'), [ ['Functional programming', 5], ['Accessibility', 4], ['CSS animations', 5], -]) +]); export const humanSkills: SkillProps[] = map(skillMapper('human'), [ ['Mentoring', 4], @@ -100,10 +100,10 @@ export const humanSkills: SkillProps[] = map(skillMapper('human'), [ ['Leadership', 3], ['Stakeholder management', 5], ['Training', 5], -]) +]); export const AllSkills: SkillProps[] = [ ...toolSkills, ...techSkills, ...humanSkills, -] +]; diff --git a/apps/ageorgedev/src/components/Resume/SocialLink.spec.tsx b/apps/ageorgedev/src/components/Resume/SocialLink.spec.tsx index a41341b8..d98f0c61 100644 --- a/apps/ageorgedev/src/components/Resume/SocialLink.spec.tsx +++ b/apps/ageorgedev/src/components/Resume/SocialLink.spec.tsx @@ -1,47 +1,50 @@ -import { render, screen } from '@testing-library/react' -import { describe, expect, it } from 'vitest' -import { SocialLink } from './SocialLink' +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { SocialLink } from './SocialLink'; describe('SocialLink', () => { it('renders an anchor element', () => { - const { container } = render() - expect(container.querySelector('a')).toBeInTheDocument() - }) + const { container } = render(); + expect(container.querySelector('a')).toBeInTheDocument(); + }); it('has target="_blank"', () => { - const { container } = render() - expect(container.querySelector('a')).toHaveAttribute('target', '_blank') - }) + const { container } = render(); + expect(container.querySelector('a')).toHaveAttribute('target', '_blank'); + }); it('has a rel containing noreferrer', () => { - const { container } = render() - const rel = container.querySelector('a')?.getAttribute('rel') ?? '' - expect(rel).toContain('noreferrer') - }) + const { container } = render(); + const rel = container.querySelector('a')?.getAttribute('rel') ?? ''; + expect(rel).toContain('noreferrer'); + }); it('links to the correct URL for each type', () => { const cases: Array<[Parameters[0]['type'], string]> = [ ['github', 'https://github.com/bassoGeorge'], ['linkedin', 'https://linkedin.com/in/anishbassogeorge'], ['web', 'https://ageorge.dev'], - ] + ]; for (const [type, expectedHref] of cases) { - const { container, unmount } = render() - expect(container.querySelector('a')).toHaveAttribute('href', expectedHref) - unmount() + const { container, unmount } = render(); + expect(container.querySelector('a')).toHaveAttribute( + 'href', + expectedHref + ); + unmount(); } - }) + }); it('shows display text when full=true', () => { - render() - expect(screen.getByText('anishgeorgehb@gmail.com')).toBeInTheDocument() - }) + render(); + expect(screen.getByText('anishgeorgehb@gmail.com')).toBeInTheDocument(); + }); it('hides display text when full is omitted', () => { - render() + render(); expect( screen.queryByText('anishgeorgehb@gmail.com') - ).not.toBeInTheDocument() - }) -}) + ).not.toBeInTheDocument(); + }); +}); diff --git a/apps/ageorgedev/src/components/Resume/SocialLink.tsx b/apps/ageorgedev/src/components/Resume/SocialLink.tsx index 645e7e45..22081bd6 100644 --- a/apps/ageorgedev/src/components/Resume/SocialLink.tsx +++ b/apps/ageorgedev/src/components/Resume/SocialLink.tsx @@ -1,5 +1,5 @@ -import { BodySm } from '@ageorgedev/design-system/typography/typography-components' -import type { Icon } from '@phosphor-icons/react' +import { BodySm } from '@ageorgedev/design-system/typography/typography-components'; +import type { Icon } from '@phosphor-icons/react'; import { EnvelopeIcon, GithubLogoIcon, @@ -7,7 +7,7 @@ import { LinkedinLogoIcon, MapPinIcon, PhoneIcon, -} from '@phosphor-icons/react/ssr' +} from '@phosphor-icons/react/ssr'; type SocialLinkType = | 'github' @@ -15,17 +15,17 @@ type SocialLinkType = | 'email' | 'web' | 'phone' - | 'location' + | 'location'; type SocialLinkProps = { - type: SocialLinkType - full?: boolean -} + type: SocialLinkType; + full?: boolean; +}; export function SocialLink({ type, full }: SocialLinkProps) { - const link = LINKS[type] - const displayLink = DISPLAY_LINKS[type] ?? link - const Icon = ICONS[type] + const link = LINKS[type]; + const displayLink = DISPLAY_LINKS[type] ?? link; + const Icon = ICONS[type]; return ( {full && {displayLink}} - ) + ); } -const email = 'anishgeorgehb@gmail.com' -const phone = '+91-999999' +const email = 'anishgeorgehb@gmail.com'; +const phone = '+91-999999'; const LINKS: Record = { github: 'https://github.com/bassoGeorge', @@ -49,13 +49,13 @@ const LINKS: Record = { web: 'https://ageorge.dev', phone: `tel:${phone}`, location: 'https://goo.gl/maps/Q7mUm5VQ5ZuLGV4v7', -} +}; const DISPLAY_LINKS: Partial> = { email, phone, location: 'Bengaluru, India', -} +}; const ICONS: Record = { github: GithubLogoIcon, @@ -64,4 +64,4 @@ const ICONS: Record = { web: GlobeIcon, phone: PhoneIcon, location: MapPinIcon, -} +}; diff --git a/apps/ageorgedev/src/mdx-components.tsx b/apps/ageorgedev/src/mdx-components.tsx index 486273a7..da5eb21b 100644 --- a/apps/ageorgedev/src/mdx-components.tsx +++ b/apps/ageorgedev/src/mdx-components.tsx @@ -3,8 +3,8 @@ import { Heading2, Heading3, Heading4, -} from '@ageorgedev/design-system/typography/typography-components' -import type { MDXComponents } from 'mdx/types' +} from '@ageorgedev/design-system/typography/typography-components'; +import type { MDXComponents } from 'mdx/types'; export function useMDXComponents(components: MDXComponents): MDXComponents { return { @@ -13,5 +13,5 @@ export function useMDXComponents(components: MDXComponents): MDXComponents { h2: Heading2, h3: Heading3, h4: Heading4, - } + }; } diff --git a/apps/ageorgedev/src/router.tsx b/apps/ageorgedev/src/router.tsx index 60f36e5a..c8b5b987 100644 --- a/apps/ageorgedev/src/router.tsx +++ b/apps/ageorgedev/src/router.tsx @@ -1,5 +1,5 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' -import { routeTree } from './routeTree.gen' +import { createRouter as createTanStackRouter } from '@tanstack/react-router'; +import { routeTree } from './routeTree.gen'; // import { getContext } from './integrations/tanstack-query/root-provider'; @@ -10,12 +10,12 @@ export function getRouter() { scrollRestoration: true, defaultPreload: 'intent', defaultPreloadStaleTime: 0, - }) - return router + }); + return router; } declare module '@tanstack/react-router' { interface Register { - router: ReturnType + router: ReturnType; } } diff --git a/apps/ageorgedev/src/routes/__root.tsx b/apps/ageorgedev/src/routes/__root.tsx index a4065322..7abe5d5f 100644 --- a/apps/ageorgedev/src/routes/__root.tsx +++ b/apps/ageorgedev/src/routes/__root.tsx @@ -1,18 +1,18 @@ -import { THEME_INIT_SCRIPT } from '@ageorgedev/design-system/theming/theme-init-script' +import { THEME_INIT_SCRIPT } from '@ageorgedev/design-system/theming/theme-init-script'; import { createRootRouteWithContext, HeadContent, Scripts, -} from '@tanstack/react-router' -import { GlobalProviders } from '../components/GlobalProviders' -import appCss from '../styles.css?url' +} from '@tanstack/react-router'; +import { GlobalProviders } from '../components/GlobalProviders'; +import appCss from '../styles.css?url'; // import type { QueryClient } from '@tanstack/react-query'; type MyRouterContext = { // queryClient?: QueryClient; - temp?: string -} + temp?: string; +}; export const Route = createRootRouteWithContext()({ head: () => ({ @@ -36,7 +36,7 @@ export const Route = createRootRouteWithContext()({ ], }), shellComponent: RootDocument, -}) +}); function RootDocument({ children }: { children: React.ReactNode }) { return ( @@ -67,5 +67,5 @@ function RootDocument({ children }: { children: React.ReactNode }) { - ) + ); } diff --git a/apps/ageorgedev/src/routes/_noLayout/resume.tsx b/apps/ageorgedev/src/routes/_noLayout/resume.tsx index 2fdb504a..f56cb7d4 100644 --- a/apps/ageorgedev/src/routes/_noLayout/resume.tsx +++ b/apps/ageorgedev/src/routes/_noLayout/resume.tsx @@ -1,4 +1,4 @@ -import { NameLogo } from '@ageorgedev/design-system/logo/NameLogo' +import { NameLogo } from '@ageorgedev/design-system/logo/NameLogo'; import { Body, BodyLg, @@ -8,27 +8,27 @@ import { Heading4, Heading5, Heading6, -} from '@ageorgedev/design-system/typography/typography-components' -import { cn } from '@ageorgedev/toolbelt/cn' -import { ArrowSquareOutIcon } from '@phosphor-icons/react/ssr' -import { createFileRoute } from '@tanstack/react-router' -import { Fragment } from 'react' +} from '@ageorgedev/design-system/typography/typography-components'; +import { cn } from '@ageorgedev/toolbelt/cn'; +import { ArrowSquareOutIcon } from '@phosphor-icons/react/ssr'; +import { createFileRoute } from '@tanstack/react-router'; +import { Fragment } from 'react'; import { humanSkills, Skill, techSkills, toolSkills, -} from '../../components/Resume/Skill' -import { SocialLink } from '../../components/Resume/SocialLink' -import styles from './resume.module.css' -import './resume.global.css' +} from '../../components/Resume/Skill'; +import { SocialLink } from '../../components/Resume/SocialLink'; +import styles from './resume.module.css'; +import './resume.global.css'; export const Route = createFileRoute('/_noLayout/resume')({ component: RouteComponent, -}) +}); function RouteComponent() { - const socialLinksBlockClasses = 'flex flex-col gap-2' + const socialLinksBlockClasses = 'flex flex-col gap-2'; return (
@@ -281,13 +281,13 @@ function RouteComponent() {
- ) + ); } const education: Array<{ - period: string - institute: string - programme: string + period: string; + institute: string; + programme: string; }> = [ { period: '2012-2015', @@ -304,8 +304,8 @@ const education: Array<{ programme: 'Higher Secondary', institute: 'Fr. Agnel School | New Delhi', }, -] +]; function Imp({ children }: React.PropsWithChildren) { - return {children} + return {children}; } diff --git a/apps/ageorgedev/src/routes/_public.tsx b/apps/ageorgedev/src/routes/_public.tsx index 8710d29f..96365238 100644 --- a/apps/ageorgedev/src/routes/_public.tsx +++ b/apps/ageorgedev/src/routes/_public.tsx @@ -1,13 +1,13 @@ -import { TiltCard } from '@ageorgedev/design-system/cards/TiltCard' -import { ShortNameLogo } from '@ageorgedev/design-system/logo/ShortNameLogo' -import { ThemeSwitcher } from '@ageorgedev/design-system/theming/ThemeSwitcher' -import { InterfaceXl } from '@ageorgedev/design-system/typography/typography-components' -import { cn } from '@ageorgedev/toolbelt/cn' -import { createFileRoute, Link, Outlet } from '@tanstack/react-router' +import { TiltCard } from '@ageorgedev/design-system/cards/TiltCard'; +import { ShortNameLogo } from '@ageorgedev/design-system/logo/ShortNameLogo'; +import { ThemeSwitcher } from '@ageorgedev/design-system/theming/ThemeSwitcher'; +import { InterfaceXl } from '@ageorgedev/design-system/typography/typography-components'; +import { cn } from '@ageorgedev/toolbelt/cn'; +import { createFileRoute, Link, Outlet } from '@tanstack/react-router'; export const Route = createFileRoute('/_public')({ component: RouteComponent, -}) +}); const links = [ { @@ -15,7 +15,7 @@ const links = [ to: '/talks', label: 'Talks', }, -] +]; function RouteComponent() { return ( @@ -55,5 +55,5 @@ function RouteComponent() { - ) + ); } diff --git a/apps/ageorgedev/src/routes/_public/index.tsx b/apps/ageorgedev/src/routes/_public/index.tsx index 7a90a033..121687e3 100644 --- a/apps/ageorgedev/src/routes/_public/index.tsx +++ b/apps/ageorgedev/src/routes/_public/index.tsx @@ -1,10 +1,10 @@ -import { createFileRoute } from '@tanstack/react-router' -import { HomeAboveFold } from '../../components/HomeAboveFold/HomeAboveFold' +import { createFileRoute } from '@tanstack/react-router'; +import { HomeAboveFold } from '../../components/HomeAboveFold/HomeAboveFold'; export const Route = createFileRoute('/_public/')({ component: HomePage, -}) +}); function HomePage() { - return + return ; } diff --git a/apps/ageorgedev/src/routes/_public/talks.index.tsx b/apps/ageorgedev/src/routes/_public/talks.index.tsx index 0edc911e..5d60ff3c 100644 --- a/apps/ageorgedev/src/routes/_public/talks.index.tsx +++ b/apps/ageorgedev/src/routes/_public/talks.index.tsx @@ -1,15 +1,15 @@ -import { TiltCard } from '@ageorgedev/design-system/cards/TiltCard' +import { TiltCard } from '@ageorgedev/design-system/cards/TiltCard'; import { Body, BodyXl, Heading1, Heading2, -} from '@ageorgedev/design-system/typography/typography-components' -import { createFileRoute, Link } from '@tanstack/react-router' +} from '@ageorgedev/design-system/typography/typography-components'; +import { createFileRoute, Link } from '@tanstack/react-router'; export const Route = createFileRoute('/_public/talks/')({ component: RouteComponent, -}) +}); function RouteComponent() { return ( @@ -35,5 +35,5 @@ function RouteComponent() { - ) + ); } diff --git a/apps/ageorgedev/src/routes/_public/talks.tailwind.tsx b/apps/ageorgedev/src/routes/_public/talks.tailwind.tsx index 82e5de1b..9e7138a8 100644 --- a/apps/ageorgedev/src/routes/_public/talks.tailwind.tsx +++ b/apps/ageorgedev/src/routes/_public/talks.tailwind.tsx @@ -1,11 +1,11 @@ -import { TalkTailwind } from '@ageorgedev/talk-tailwind' -import { createFileRoute } from '@tanstack/react-router' -import '@ageorgedev/reveal-framework/globals' +import { TalkTailwind } from '@ageorgedev/talk-tailwind'; +import { createFileRoute } from '@tanstack/react-router'; +import '@ageorgedev/reveal-framework/globals'; export const Route = createFileRoute('/_public/talks/tailwind')({ component: RouteComponent, -}) +}); function RouteComponent() { - return + return ; } diff --git a/apps/ageorgedev/vite.config.ts b/apps/ageorgedev/vite.config.ts index 181585af..541b373a 100644 --- a/apps/ageorgedev/vite.config.ts +++ b/apps/ageorgedev/vite.config.ts @@ -1,9 +1,9 @@ -import process from 'node:process' -import tailwindcss from '@tailwindcss/vite' -import { tanstackStart } from '@tanstack/react-start/plugin/vite' -import react from '@vitejs/plugin-react' -import { defineConfig, searchForWorkspaceRoot } from 'vite' -import dts from 'vite-plugin-dts' +import process from 'node:process'; +import tailwindcss from '@tailwindcss/vite'; +import { tanstackStart } from '@tanstack/react-start/plugin/vite'; +import react from '@vitejs/plugin-react'; +import { defineConfig, searchForWorkspaceRoot } from 'vite'; +import dts from 'vite-plugin-dts'; const config = defineConfig({ cacheDir: '../../node_modules/.vite/ageorgedev', @@ -44,6 +44,6 @@ const config = defineConfig({ allow: [searchForWorkspaceRoot(process.cwd())], }, }, -}) +}); -export default config +export default config; diff --git a/apps/ageorgedev/vitest.config.ts b/apps/ageorgedev/vitest.config.ts index 9b6bd972..c641e500 100644 --- a/apps/ageorgedev/vitest.config.ts +++ b/apps/ageorgedev/vitest.config.ts @@ -1,5 +1,5 @@ -import react from '@vitejs/plugin-react' -import { defineConfig } from 'vitest/config' +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vitest/config'; export default defineConfig({ plugins: [react()], @@ -8,4 +8,4 @@ export default defineConfig({ setupFiles: ['@ageorgedev/testing-config/react-jsdom-test-setup.ts'], include: ['src/**/*.spec.{ts,tsx}'], }, -}) +}); diff --git a/apps/design-docs/.storybook/main.ts b/apps/design-docs/.storybook/main.ts index 0cde29c7..d3eafff8 100644 --- a/apps/design-docs/.storybook/main.ts +++ b/apps/design-docs/.storybook/main.ts @@ -1,17 +1,17 @@ // This file has been automatically migrated to valid ESM format by Storybook. -import { createRequire } from 'node:module' -import { dirname, join } from 'node:path' -import { fileURLToPath } from 'node:url' -import type { StorybookConfig } from '@storybook/react-vite' +import { createRequire } from 'node:module'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import type { StorybookConfig } from '@storybook/react-vite'; // import { mergeConfig } from 'vite' -const fileGlob = 'src/**/*.stories.@(js|jsx|ts|tsx|mdx)' -const projects = ['foundation-styles', 'design-system'] +const fileGlob = 'src/**/*.stories.@(js|jsx|ts|tsx|mdx)'; +const projects = ['foundation-styles', 'design-system']; -const __filename = fileURLToPath(import.meta.url) -const __dirname = dirname(__filename) -const require = createRequire(import.meta.url) +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const require = createRequire(import.meta.url); const config: StorybookConfig = { stories: [ @@ -36,14 +36,14 @@ const config: StorybookConfig = { // } // }) // }, -} +}; -export default config +export default config; // To customize your Vite configuration you can use the viteFinal field. // Check https://storybook.js.org/docs/react/builders/vite#configuration // and https://nx.dev/packages/storybook/documents/custom-builder-configs function getAbsolutePath(value: string): string { - return dirname(require.resolve(join(value, 'package.json'))) + return dirname(require.resolve(join(value, 'package.json'))); } diff --git a/apps/design-docs/.storybook/preview.tsx b/apps/design-docs/.storybook/preview.tsx index 432a6809..7e0edf84 100644 --- a/apps/design-docs/.storybook/preview.tsx +++ b/apps/design-docs/.storybook/preview.tsx @@ -1,7 +1,7 @@ -import './storybook.css' -import { ThemeProvider } from '@ageorgedev/design-system/theming/ThemeProvider' -import { withThemeByClassName } from '@storybook/addon-themes' -import type { Preview } from '@storybook/react-vite' +import './storybook.css'; +import { ThemeProvider } from '@ageorgedev/design-system/theming/ThemeProvider'; +import { withThemeByClassName } from '@storybook/addon-themes'; +import type { Preview } from '@storybook/react-vite'; const preview: Preview = { decorators: [ @@ -25,6 +25,6 @@ const preview: Preview = { }, }, }, -} +}; -export default preview +export default preview; diff --git a/apps/design-docs/postcss.config.js b/apps/design-docs/postcss.config.js index 60bf9096..8984cd25 100644 --- a/apps/design-docs/postcss.config.js +++ b/apps/design-docs/postcss.config.js @@ -8,4 +8,4 @@ export default { 'postcss-nesting': {}, '@tailwindcss/postcss': {}, }, -} +}; diff --git a/apps/design-docs/stories/colors.stories.tsx b/apps/design-docs/stories/colors.stories.tsx index a41d6e69..f91ffc5d 100644 --- a/apps/design-docs/stories/colors.stories.tsx +++ b/apps/design-docs/stories/colors.stories.tsx @@ -3,16 +3,16 @@ import { BodyXl, BodyXs, InterfaceMd, -} from '@ageorgedev/design-system/typography/typography-components' -import { cn } from '@ageorgedev/toolbelt/cn' -import type { StoryObj } from '@storybook/react-vite' -import { createContext, type HTMLAttributes, useContext } from 'react' +} from '@ageorgedev/design-system/typography/typography-components'; +import { cn } from '@ageorgedev/toolbelt/cn'; +import type { StoryObj } from '@storybook/react-vite'; +import { createContext, type HTMLAttributes, useContext } from 'react'; export default { title: 'Foundation/Colors', -} +}; -type Story = StoryObj +type Story = StoryObj; export const LightTheme: Story = { globals: { theme: 'light' }, @@ -35,7 +35,7 @@ export const LightTheme: Story = { ), -} +}; export const DarkTheme: Story = { globals: { theme: 'dark' }, @@ -58,35 +58,35 @@ export const DarkTheme: Story = { ), -} +}; function Layout({ children }: React.PropsWithChildren) { return (
{children}
- ) + ); } function Section({ className, ...attributes }: HTMLAttributes) { return (
- ) + ); } function SwatchSection({ className, ...props }: HTMLAttributes) { - return
+ return
; } function TextRow({ className, ...attributes }: Omit, 'children'>) { - const a11yFailures = useContext(A11yFailureContext) - const label = className?.replace('text-', '') ?? '' + const a11yFailures = useContext(A11yFailureContext); + const label = className?.replace('text-', '') ?? ''; return (
@@ -96,13 +96,13 @@ function TextRow({ {className}
- ) + ); } function Swatch({ className }: HTMLAttributes) { - const classes = className?.split(' ') - const bgClass = classes?.find((c) => c.startsWith('bg-')) - const fgClass = classes?.find((c) => c.startsWith('text-')) + const classes = className?.split(' '); + const bgClass = classes?.find((c) => c.startsWith('bg-')); + const fgClass = classes?.find((c) => c.startsWith('text-')); return (
) { > {bgClass}
{fgClass}
- ) + ); } function PageSample({ @@ -125,7 +125,7 @@ function PageSample({ {children}
- ) + ); } function ColorSamples({ a11yFailures = [] }: { a11yFailures?: string[] }) { @@ -206,7 +206,7 @@ function ColorSamples({ a11yFailures = [] }: { a11yFailures?: string[] }) {
- ) + ); } -const A11yFailureContext = createContext([]) +const A11yFailureContext = createContext([]); diff --git a/apps/design-docs/stories/elevations.stories.tsx b/apps/design-docs/stories/elevations.stories.tsx index 619f0a90..aba2be02 100644 --- a/apps/design-docs/stories/elevations.stories.tsx +++ b/apps/design-docs/stories/elevations.stories.tsx @@ -1,13 +1,13 @@ -import { BodyMd } from '@ageorgedev/design-system/typography/typography-components' -import { cn } from '@ageorgedev/toolbelt/cn' -import type { StoryObj } from '@storybook/react-vite' -import type { HTMLAttributes } from 'react' +import { BodyMd } from '@ageorgedev/design-system/typography/typography-components'; +import { cn } from '@ageorgedev/toolbelt/cn'; +import type { StoryObj } from '@storybook/react-vite'; +import type { HTMLAttributes } from 'react'; export default { title: 'Foundation/Elevations', -} +}; -type Story = StoryObj +type Story = StoryObj; export const Elevations: Story = { render: () => ( @@ -25,7 +25,7 @@ export const Elevations: Story = {
), -} +}; function Box({ className, ...otherProps }: HTMLAttributes) { return ( @@ -45,5 +45,5 @@ function Box({ className, ...otherProps }: HTMLAttributes) { ))} - ) + ); } diff --git a/apps/design-docs/stories/spacing.stories.tsx b/apps/design-docs/stories/spacing.stories.tsx index 7b406db8..f01bb7df 100644 --- a/apps/design-docs/stories/spacing.stories.tsx +++ b/apps/design-docs/stories/spacing.stories.tsx @@ -1,12 +1,12 @@ -import { Interface } from '@ageorgedev/design-system/typography/typography-components' -import { cn } from '@ageorgedev/toolbelt/cn' -import type { StoryObj } from '@storybook/react-vite' +import { Interface } from '@ageorgedev/design-system/typography/typography-components'; +import { cn } from '@ageorgedev/toolbelt/cn'; +import type { StoryObj } from '@storybook/react-vite'; export default { title: 'Foundation/Spacing', -} +}; -type Story = StoryObj +type Story = StoryObj; const SPACING_VALUES: Array<[string, string, string]> = [ ['w-0', '0', '0'], @@ -31,7 +31,7 @@ const SPACING_VALUES: Array<[string, string, string]> = [ ['w-thin-line', 'thin-line', '1px'], ['w-medium-line', 'medium-line', '2px'], ['w-thick-line', 'thick-line', '4px'], -] +]; function SpaceBar({ className, name }: { className: string; name: string }) { return ( @@ -39,7 +39,7 @@ function SpaceBar({ className, name }: { className: string; name: string }) { {name}
- ) + ); } export const SpacingScale: Story = { @@ -57,4 +57,4 @@ export const SpacingScale: Story = { ))} ), -} +}; diff --git a/apps/design-docs/vite.config.ts b/apps/design-docs/vite.config.ts index 262ad46b..bcb63a61 100644 --- a/apps/design-docs/vite.config.ts +++ b/apps/design-docs/vite.config.ts @@ -1,7 +1,7 @@ -import tailwindcss from '@tailwindcss/vite' -import react from '@vitejs/plugin-react' -import { defineConfig } from 'vite' +import tailwindcss from '@tailwindcss/vite'; +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; export default defineConfig({ plugins: [tailwindcss(), react()], -}) +}); diff --git a/apps/dnd-cli/src/App.tsx b/apps/dnd-cli/src/App.tsx index d768a045..3ea8b389 100644 --- a/apps/dnd-cli/src/App.tsx +++ b/apps/dnd-cli/src/App.tsx @@ -1,58 +1,63 @@ /** biome-ignore-all lint/style/noNonNullAssertion: not important at the moment */ -import { Box, Text, useApp } from 'ink' -import { useState } from 'react' -import { AbilitiesStep } from './steps/AbilitiesStep.js' -import type { CombatValues } from './steps/CombatStep.js' -import { CombatStep } from './steps/CombatStep.js' -import { FilenameStep } from './steps/FilenameStep.js' -import type { IdentityValues } from './steps/IdentityStep.js' -import { IdentityStep } from './steps/IdentityStep.js' -import type { ProficiencyValues } from './steps/ProficienciesStep.js' -import { ProficienciesStep } from './steps/ProficienciesStep.js' -import type { AbilityValues, WizardState } from './types.js' +import { Box, Text, useApp } from 'ink'; +import { useState } from 'react'; +import { AbilitiesStep } from './steps/AbilitiesStep.js'; +import type { CombatValues } from './steps/CombatStep.js'; +import { CombatStep } from './steps/CombatStep.js'; +import { FilenameStep } from './steps/FilenameStep.js'; +import type { IdentityValues } from './steps/IdentityStep.js'; +import { IdentityStep } from './steps/IdentityStep.js'; +import type { ProficiencyValues } from './steps/ProficienciesStep.js'; +import { ProficienciesStep } from './steps/ProficienciesStep.js'; +import type { AbilityValues, WizardState } from './types.js'; -type StepId = 'identity' | 'abilities' | 'combat' | 'proficiencies' | 'filename' +type StepId = + | 'identity' + | 'abilities' + | 'combat' + | 'proficiencies' + | 'filename'; function toKebabCase(name: string): string { return name .trim() .toLowerCase() .replace(/[^a-z0-9]+/g, '-') - .replace(/^-|-$/g, '') + .replace(/^-|-$/g, ''); } export function App({ onComplete, }: { - onComplete: (state: WizardState) => void + onComplete: (state: WizardState) => void; }) { - const { exit } = useApp() - const [step, setStep] = useState('identity') - const [identity, setIdentity] = useState(null) - const [abilities, setAbilities] = useState(null) - const [combat, setCombat] = useState(null) + const { exit } = useApp(); + const [step, setStep] = useState('identity'); + const [identity, setIdentity] = useState(null); + const [abilities, setAbilities] = useState(null); + const [combat, setCombat] = useState(null); const [proficiencies, setProficiencies] = useState( null - ) + ); function handleIdentity(values: IdentityValues) { - setIdentity(values) - setStep('abilities') + setIdentity(values); + setStep('abilities'); } function handleAbilities(values: AbilityValues) { - setAbilities(values) - setStep('combat') + setAbilities(values); + setStep('combat'); } function handleCombat(values: CombatValues) { - setCombat(values) - setStep('proficiencies') + setCombat(values); + setStep('proficiencies'); } function handleProficiencies(values: ProficiencyValues) { - setProficiencies(values) - setStep('filename') + setProficiencies(values); + setStep('filename'); } function handleFilename({ filename }: { filename: string }) { @@ -72,14 +77,14 @@ export function App({ savingThrowProficiencies: proficiencies!.savingThrowProficiencies, skillProficiencies: proficiencies!.skillProficiencies, filename, - } - onComplete(state) - exit() + }; + onComplete(state); + exit(); } const defaultFilename = identity ? `${toKebabCase(identity.name)}.data.ts` - : 'character.data.ts' + : 'character.data.ts'; return ( @@ -102,5 +107,5 @@ export function App({ /> )} - ) + ); } diff --git a/apps/dnd-cli/src/index.tsx b/apps/dnd-cli/src/index.tsx index d0cc63b6..c01673f9 100644 --- a/apps/dnd-cli/src/index.tsx +++ b/apps/dnd-cli/src/index.tsx @@ -1,34 +1,34 @@ -import { mkdirSync, writeFileSync } from 'node:fs' -import { dirname, join } from 'node:path' -import { fileURLToPath } from 'node:url' -import { render } from 'ink' -import { App } from './App.js' -import { generateFile } from './lib/generateFile.js' -import type { WizardState } from './types.js' +import { mkdirSync, writeFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { render } from 'ink'; +import { App } from './App.js'; +import { generateFile } from './lib/generateFile.js'; +import type { WizardState } from './types.js'; -const __dirname = dirname(fileURLToPath(import.meta.url)) +const __dirname = dirname(fileURLToPath(import.meta.url)); const CHARACTERS_DIR = join( __dirname, '../../../packages/dnd-character-sheet/src/characters' -) +); -const result: { state: WizardState | null } = { state: null } +const result: { state: WizardState | null } = { state: null }; const { waitUntilExit } = render( { - result.state = state + result.state = state; }} /> -) +); -await waitUntilExit() +await waitUntilExit(); if (result.state) { - mkdirSync(CHARACTERS_DIR, { recursive: true }) - const outputPath = join(CHARACTERS_DIR, result.state.filename) - writeFileSync(outputPath, generateFile(result.state)) + mkdirSync(CHARACTERS_DIR, { recursive: true }); + const outputPath = join(CHARACTERS_DIR, result.state.filename); + writeFileSync(outputPath, generateFile(result.state)); console.log( `\n✓ Written: packages/dnd-character-sheet/src/characters/${result.state.filename}` - ) + ); } diff --git a/apps/dnd-cli/src/lib/defaults.ts b/apps/dnd-cli/src/lib/defaults.ts index b9d8ac1b..a8454242 100644 --- a/apps/dnd-cli/src/lib/defaults.ts +++ b/apps/dnd-cli/src/lib/defaults.ts @@ -1,4 +1,4 @@ -import type { HitDiceType } from '../types.js' +import type { HitDiceType } from '../types.js'; export const DEFAULTS = { level: 1, @@ -7,4 +7,4 @@ export const DEFAULTS = { armorClass: 10, speed: 30, hitDiceType: 'd10' as HitDiceType, -} as const +} as const; diff --git a/apps/dnd-cli/src/lib/generateFile.ts b/apps/dnd-cli/src/lib/generateFile.ts index 4cd174c8..a59b35cc 100644 --- a/apps/dnd-cli/src/lib/generateFile.ts +++ b/apps/dnd-cli/src/lib/generateFile.ts @@ -1,5 +1,5 @@ -import { Ability } from '@ageorgedev/dnd-character-sheet/models' -import type { WizardState } from '../types.js' +import { Ability } from '@ageorgedev/dnd-character-sheet/models'; +import type { WizardState } from '../types.js'; function toExportName(name: string): string { return name @@ -7,28 +7,28 @@ function toExportName(name: string): string { .split(/\s+/) .filter(Boolean) .map((word, i) => { - const clean = word.replace(/[^a-zA-Z0-9]/g, '') - if (!clean) return '' - if (i === 0) return clean.charAt(0).toLowerCase() + clean.slice(1) - return clean.charAt(0).toUpperCase() + clean.slice(1).toLowerCase() + const clean = word.replace(/[^a-zA-Z0-9]/g, ''); + if (!clean) return ''; + if (i === 0) return clean.charAt(0).toLowerCase() + clean.slice(1); + return clean.charAt(0).toUpperCase() + clean.slice(1).toLowerCase(); }) - .join('') + .join(''); } function formatSkillArray(items: string[]): string { - if (items.length === 0) return '[]' - return `[${items.map((s) => `Skill.${s}`).join(', ')}]` + if (items.length === 0) return '[]'; + return `[${items.map((s) => `Skill.${s}`).join(', ')}]`; } function formatAbilityArray(items: string[]): string { - if (items.length === 0) return '[]' - return `[${items.map((s) => `"${s}"`).join(', ')}]` + if (items.length === 0) return '[]'; + return `[${items.map((s) => `"${s}"`).join(', ')}]`; } export function generateFile(state: WizardState): string { - const exportName = toExportName(state.name) || 'character' + const exportName = toExportName(state.name) || 'character'; const subclassLine = state.subclass ? `\n subclass: "${state.subclass}",` - : '' + : ''; return `import type { Character } from '../lib/models/character'; import type { Skill } from '../lib/models/skills'; @@ -79,5 +79,5 @@ export const ${exportName}: Character = { currency: { cp: 0, sp: 0, ep: 0, gp: 0, pp: 0 }, features: [], }; -` +`; } diff --git a/apps/dnd-cli/src/steps/AbilitiesStep.tsx b/apps/dnd-cli/src/steps/AbilitiesStep.tsx index b8529ae3..4856f960 100644 --- a/apps/dnd-cli/src/steps/AbilitiesStep.tsx +++ b/apps/dnd-cli/src/steps/AbilitiesStep.tsx @@ -1,8 +1,8 @@ -import { Ability } from '@ageorgedev/dnd-character-sheet/models' -import { Box, Text, useInput } from 'ink' -import { useState } from 'react' -import { DEFAULTS } from '../lib/defaults.js' -import type { AbilityValues } from '../types.js' +import { Ability } from '@ageorgedev/dnd-character-sheet/models'; +import { Box, Text, useInput } from 'ink'; +import { useState } from 'react'; +import { DEFAULTS } from '../lib/defaults.js'; +import type { AbilityValues } from '../types.js'; const FIELDS: Array<{ key: Ability; label: string }> = [ { key: Ability.Strength, label: 'Strength (STR)' }, @@ -11,24 +11,24 @@ const FIELDS: Array<{ key: Ability; label: string }> = [ { key: Ability.Intelligence, label: 'Intelligence (INT)' }, { key: Ability.Wisdom, label: 'Wisdom (WIS)' }, { key: Ability.Charisma, label: 'Charisma (CHA)' }, -] +]; -const LABEL_WIDTH = 22 +const LABEL_WIDTH = 22; export function AbilitiesStep({ onComplete, }: { - onComplete: (values: AbilityValues) => void + onComplete: (values: AbilityValues) => void; }) { - const [fieldIndex, setFieldIndex] = useState(0) - const [collected, setCollected] = useState>({}) - const [buffer, setBuffer] = useState(String(DEFAULTS.abilityScore)) + const [fieldIndex, setFieldIndex] = useState(0); + const [collected, setCollected] = useState>({}); + const [buffer, setBuffer] = useState(String(DEFAULTS.abilityScore)); - const current = FIELDS[fieldIndex] + const current = FIELDS[fieldIndex]; useInput((input, key) => { if (key.return) { - const raw = buffer.trim() + const raw = buffer.trim(); const value = Math.max( 1, Math.min( @@ -36,22 +36,22 @@ export function AbilitiesStep({ parseInt(raw || String(DEFAULTS.abilityScore), 10) || DEFAULTS.abilityScore ) - ) - const next = { ...collected, [current.key]: value } + ); + const next = { ...collected, [current.key]: value }; if (fieldIndex < FIELDS.length - 1) { - setCollected(next) - setBuffer(String(DEFAULTS.abilityScore)) - setFieldIndex((i) => i + 1) + setCollected(next); + setBuffer(String(DEFAULTS.abilityScore)); + setFieldIndex((i) => i + 1); } else { - onComplete(next as AbilityValues) + onComplete(next as AbilityValues); } } else if (key.backspace || key.delete) { - setBuffer((b) => b.slice(0, -1)) + setBuffer((b) => b.slice(0, -1)); } else if (input && !key.ctrl && !key.meta && /[0-9]/.test(input)) { - setBuffer((b) => (b.length < 2 ? b + input : b)) + setBuffer((b) => (b.length < 2 ? b + input : b)); } - }) + }); return ( @@ -78,5 +78,5 @@ export function AbilitiesStep({ Enter to confirm (default: {DEFAULTS.abilityScore}, range 1–30) - ) + ); } diff --git a/apps/dnd-cli/src/steps/CombatStep.tsx b/apps/dnd-cli/src/steps/CombatStep.tsx index d38b9a33..6ec9375f 100644 --- a/apps/dnd-cli/src/steps/CombatStep.tsx +++ b/apps/dnd-cli/src/steps/CombatStep.tsx @@ -1,18 +1,18 @@ -import { Box, Text, useInput } from 'ink' -import { useState } from 'react' -import { DEFAULTS } from '../lib/defaults.js' -import type { HitDiceType } from '../types.js' +import { Box, Text, useInput } from 'ink'; +import { useState } from 'react'; +import { DEFAULTS } from '../lib/defaults.js'; +import type { HitDiceType } from '../types.js'; export interface CombatValues { - armorClass: number - speed: number - hitPointsMax: number - hitDiceType: HitDiceType + armorClass: number; + speed: number; + hitPointsMax: number; + hitDiceType: HitDiceType; } -const HIT_DICE: HitDiceType[] = ['d6', 'd8', 'd10', 'd12'] +const HIT_DICE: HitDiceType[] = ['d6', 'd8', 'd10', 'd12']; -type Phase = 'ac' | 'speed' | 'hp' | 'dice' +type Phase = 'ac' | 'speed' | 'hp' | 'dice'; const PHASE_CONFIG: Record< Exclude, @@ -29,80 +29,80 @@ const PHASE_CONFIG: Record< hint: `default: ${DEFAULTS.speed}`, }, hp: { label: 'Hit Points (max)', defaultValue: 0, hint: 'required' }, -} +}; -const LABEL_WIDTH = 24 +const LABEL_WIDTH = 24; export function CombatStep({ onComplete, }: { - onComplete: (values: CombatValues) => void + onComplete: (values: CombatValues) => void; }) { - const [phase, setPhase] = useState('ac') - const [ac, setAc] = useState() - const [speed, setSpeed] = useState() - const [hp, setHp] = useState() + const [phase, setPhase] = useState('ac'); + const [ac, setAc] = useState(); + const [speed, setSpeed] = useState(); + const [hp, setHp] = useState(); const [diceIndex, setDiceIndex] = useState( HIT_DICE.indexOf(DEFAULTS.hitDiceType) - ) - const [buffer, setBuffer] = useState(String(DEFAULTS.armorClass)) - const [error, setError] = useState('') + ); + const [buffer, setBuffer] = useState(String(DEFAULTS.armorClass)); + const [error, setError] = useState(''); useInput((input, key) => { if (phase === 'dice') { if (key.leftArrow) - setDiceIndex((i) => (i - 1 + HIT_DICE.length) % HIT_DICE.length) - else if (key.rightArrow) setDiceIndex((i) => (i + 1) % HIT_DICE.length) + setDiceIndex((i) => (i - 1 + HIT_DICE.length) % HIT_DICE.length); + else if (key.rightArrow) setDiceIndex((i) => (i + 1) % HIT_DICE.length); else if (key.return) { onComplete({ armorClass: ac ?? DEFAULTS.armorClass, speed: speed ?? DEFAULTS.speed, hitPointsMax: hp ?? 0, hitDiceType: HIT_DICE[diceIndex], - }) + }); } - return + return; } if (key.return) { - const cfg = PHASE_CONFIG[phase] - const raw = buffer.trim() + const cfg = PHASE_CONFIG[phase]; + const raw = buffer.trim(); if (phase === 'hp' && !raw) { - setError('Required') - return + setError('Required'); + return; } const value = - parseInt(raw || String(cfg.defaultValue), 10) || cfg.defaultValue - setError('') + parseInt(raw || String(cfg.defaultValue), 10) || cfg.defaultValue; + setError(''); if (phase === 'ac') { - setAc(value) - setBuffer(String(DEFAULTS.speed)) - setPhase('speed') + setAc(value); + setBuffer(String(DEFAULTS.speed)); + setPhase('speed'); } else if (phase === 'speed') { - setSpeed(value) - setBuffer('') - setPhase('hp') + setSpeed(value); + setBuffer(''); + setPhase('hp'); } else if (phase === 'hp') { - setHp(value) - setPhase('dice') + setHp(value); + setPhase('dice'); } } else if (key.backspace || key.delete) { - setBuffer((b) => b.slice(0, -1)) - setError('') + setBuffer((b) => b.slice(0, -1)); + setError(''); } else if (input && !key.ctrl && !key.meta && /[0-9]/.test(input)) { - setBuffer((b) => b + input) - setError('') + setBuffer((b) => b + input); + setError(''); } - }) + }); - const completedPhases: Array<{ label: string; value: string }> = [] + const completedPhases: Array<{ label: string; value: string }> = []; if (ac !== undefined) - completedPhases.push({ label: 'Armor Class (AC)', value: String(ac) }) + completedPhases.push({ label: 'Armor Class (AC)', value: String(ac) }); if (speed !== undefined) - completedPhases.push({ label: 'Speed (ft)', value: String(speed) }) + completedPhases.push({ label: 'Speed (ft)', value: String(speed) }); if (hp !== undefined) - completedPhases.push({ label: 'Hit Points (max)', value: String(hp) }) + completedPhases.push({ label: 'Hit Points (max)', value: String(hp) }); return ( @@ -143,5 +143,5 @@ export function CombatStep({ Enter to confirm ({PHASE_CONFIG[phase].hint}) )} - ) + ); } diff --git a/apps/dnd-cli/src/steps/FilenameStep.tsx b/apps/dnd-cli/src/steps/FilenameStep.tsx index 37f6c42c..11cb9cba 100644 --- a/apps/dnd-cli/src/steps/FilenameStep.tsx +++ b/apps/dnd-cli/src/steps/FilenameStep.tsx @@ -1,36 +1,36 @@ -import { Box, Text, useInput } from 'ink' -import { useState } from 'react' +import { Box, Text, useInput } from 'ink'; +import { useState } from 'react'; export interface FilenameValues { - filename: string + filename: string; } export function FilenameStep({ defaultFilename, onComplete, }: { - defaultFilename: string - onComplete: (values: FilenameValues) => void + defaultFilename: string; + onComplete: (values: FilenameValues) => void; }) { - const [buffer, setBuffer] = useState(defaultFilename) - const [error, setError] = useState('') + const [buffer, setBuffer] = useState(defaultFilename); + const [error, setError] = useState(''); useInput((input, key) => { if (key.return) { - const filename = buffer.trim() || defaultFilename + const filename = buffer.trim() || defaultFilename; if (!filename) { - setError('Filename is required') - return + setError('Filename is required'); + return; } - onComplete({ filename }) + onComplete({ filename }); } else if (key.backspace || key.delete) { - setBuffer((b) => b.slice(0, -1)) - setError('') + setBuffer((b) => b.slice(0, -1)); + setError(''); } else if (input && !key.ctrl && !key.meta) { - setBuffer((b) => b + input) - setError('') + setBuffer((b) => b + input); + setError(''); } - }) + }); return ( @@ -54,5 +54,5 @@ export function FilenameStep({ Enter to write the file )} - ) + ); } diff --git a/apps/dnd-cli/src/steps/IdentityStep.tsx b/apps/dnd-cli/src/steps/IdentityStep.tsx index f6ecd209..a82a99c3 100644 --- a/apps/dnd-cli/src/steps/IdentityStep.tsx +++ b/apps/dnd-cli/src/steps/IdentityStep.tsx @@ -1,24 +1,24 @@ -import { Box, Text, useInput } from 'ink' -import { useState } from 'react' -import { DEFAULTS } from '../lib/defaults.js' +import { Box, Text, useInput } from 'ink'; +import { useState } from 'react'; +import { DEFAULTS } from '../lib/defaults.js'; export interface IdentityValues { - name: string - characterClass: string - subclass: string - level: number - species: string - background: string - experiencePoints: number + name: string; + characterClass: string; + subclass: string; + level: number; + species: string; + background: string; + experiencePoints: number; } interface FieldDef { - key: keyof IdentityValues - label: string - hint: string - defaultValue: string - required: boolean - numeric: boolean + key: keyof IdentityValues; + label: string; + hint: string; + defaultValue: string; + required: boolean; + numeric: boolean; } const FIELDS: FieldDef[] = [ @@ -78,48 +78,48 @@ const FIELDS: FieldDef[] = [ required: true, numeric: true, }, -] +]; -const LABEL_WIDTH = 24 +const LABEL_WIDTH = 24; export function IdentityStep({ onComplete, }: { - onComplete: (values: IdentityValues) => void + onComplete: (values: IdentityValues) => void; }) { - const [fieldIndex, setFieldIndex] = useState(0) - const [collected, setCollected] = useState>({}) - const [buffer, setBuffer] = useState(FIELDS[0].defaultValue) - const [error, setError] = useState('') + const [fieldIndex, setFieldIndex] = useState(0); + const [collected, setCollected] = useState>({}); + const [buffer, setBuffer] = useState(FIELDS[0].defaultValue); + const [error, setError] = useState(''); - const current = FIELDS[fieldIndex] + const current = FIELDS[fieldIndex]; useInput((input, key) => { if (key.return) { - const raw = buffer.trim() !== '' ? buffer.trim() : current.defaultValue + const raw = buffer.trim() !== '' ? buffer.trim() : current.defaultValue; if (current.required && !raw) { - setError('Required') - return + setError('Required'); + return; } - const value = current.numeric ? parseInt(raw || '0', 10) : raw - const next = { ...collected, [current.key]: value } + const value = current.numeric ? parseInt(raw || '0', 10) : raw; + const next = { ...collected, [current.key]: value }; if (fieldIndex < FIELDS.length - 1) { - setCollected(next) - setBuffer(FIELDS[fieldIndex + 1].defaultValue) - setFieldIndex((i) => i + 1) - setError('') + setCollected(next); + setBuffer(FIELDS[fieldIndex + 1].defaultValue); + setFieldIndex((i) => i + 1); + setError(''); } else { - onComplete(next as IdentityValues) + onComplete(next as IdentityValues); } } else if (key.backspace || key.delete) { - setBuffer((b) => b.slice(0, -1)) - setError('') + setBuffer((b) => b.slice(0, -1)); + setError(''); } else if (input && !key.ctrl && !key.meta) { - setBuffer((b) => b + input) - setError('') + setBuffer((b) => b + input); + setError(''); } - }) + }); return ( @@ -146,5 +146,5 @@ export function IdentityStep({ Enter to confirm )} - ) + ); } diff --git a/apps/dnd-cli/src/steps/ProficienciesStep.tsx b/apps/dnd-cli/src/steps/ProficienciesStep.tsx index 798b6a3c..0de2342e 100644 --- a/apps/dnd-cli/src/steps/ProficienciesStep.tsx +++ b/apps/dnd-cli/src/steps/ProficienciesStep.tsx @@ -2,73 +2,73 @@ import { ABILITY_DETAILS, ALL_ABILITIES, Skill, -} from '@ageorgedev/dnd-character-sheet/models' -import { Box, Text, useInput } from 'ink' -import { useState } from 'react' +} from '@ageorgedev/dnd-character-sheet/models'; +import { Box, Text, useInput } from 'ink'; +import { useState } from 'react'; const SAVING_THROW_LABELS = Object.fromEntries( ALL_ABILITIES.map((ability) => [ability, ABILITY_DETAILS[ability].label]) -) +); -const AllSkills = Object.keys(Skill) +const AllSkills = Object.keys(Skill); export interface ProficiencyValues { - savingThrowProficiencies: string[] - skillProficiencies: string[] + savingThrowProficiencies: string[]; + skillProficiencies: string[]; } export function ProficienciesStep({ onComplete, }: { - onComplete: (values: ProficiencyValues) => void + onComplete: (values: ProficiencyValues) => void; }) { const [phase, setPhase] = useState<'saving-throws' | 'skills'>( 'saving-throws' - ) - const [cursor, setCursor] = useState(0) - const [selectedST, setSelectedST] = useState>(new Set()) - const [selectedSkills, setSelectedSkills] = useState>(new Set()) + ); + const [cursor, setCursor] = useState(0); + const [selectedST, setSelectedST] = useState>(new Set()); + const [selectedSkills, setSelectedSkills] = useState>(new Set()); - const isST = phase === 'saving-throws' - const list = isST ? ALL_ABILITIES : AllSkills - const labels = (isST ? SAVING_THROW_LABELS : Skill) as Record - const selected = isST ? selectedST : selectedSkills + const isST = phase === 'saving-throws'; + const list = isST ? ALL_ABILITIES : AllSkills; + const labels = (isST ? SAVING_THROW_LABELS : Skill) as Record; + const selected = isST ? selectedST : selectedSkills; const setSelected = isST ? (fn: (s: Set) => Set) => setSelectedST(fn) - : (fn: (s: Set) => Set) => setSelectedSkills(fn) + : (fn: (s: Set) => Set) => setSelectedSkills(fn); useInput((input, key) => { if (key.upArrow) { - setCursor((c) => Math.max(0, c - 1)) + setCursor((c) => Math.max(0, c - 1)); } else if (key.downArrow) { - setCursor((c) => Math.min(list.length - 1, c + 1)) + setCursor((c) => Math.min(list.length - 1, c + 1)); } else if (input === ' ') { - const item = list[cursor] + const item = list[cursor]; setSelected((s) => { - const next = new Set(s) - if (next.has(item)) next.delete(item) - else next.add(item) - return next - }) + const next = new Set(s); + if (next.has(item)) next.delete(item); + else next.add(item); + return next; + }); } else if (key.return) { if (phase === 'saving-throws') { - setPhase('skills') - setCursor(0) + setPhase('skills'); + setCursor(0); } else { onComplete({ savingThrowProficiencies: Array.from(selectedST), skillProficiencies: Array.from(selectedSkills), - }) + }); } } - }) + }); const title = isST ? '── Step 4a: Saving Throw Proficiencies ──────────' - : '── Step 4b: Skill Proficiencies ─────────────────' + : '── Step 4b: Skill Proficiencies ─────────────────'; - const count = selected.size - const countLabel = count > 0 ? ` (${count} selected)` : ' (none selected)' + const count = selected.size; + const countLabel = count > 0 ? ` (${count} selected)` : ' (none selected)'; return ( @@ -77,8 +77,8 @@ export function ProficienciesStep({ {list.map((item, i) => { - const isCursor = i === cursor - const isSelected = selected.has(item) + const isCursor = i === cursor; + const isSelected = selected.has(item); return ( @@ -89,7 +89,7 @@ export function ProficienciesStep({ {labels[item]} - ) + ); })} @@ -99,5 +99,5 @@ export function ProficienciesStep({ - ) + ); } diff --git a/apps/dnd-cli/src/types.ts b/apps/dnd-cli/src/types.ts index b8dd6672..b70fedf0 100644 --- a/apps/dnd-cli/src/types.ts +++ b/apps/dnd-cli/src/types.ts @@ -1,25 +1,25 @@ -import type { Ability } from '@ageorgedev/dnd-character-sheet/models' +import type { Ability } from '@ageorgedev/dnd-character-sheet/models'; -export type HitDiceType = 'd6' | 'd8' | 'd10' | 'd12' +export type HitDiceType = 'd6' | 'd8' | 'd10' | 'd12'; export interface WizardState { - name: string - characterClass: string - subclass: string - level: number - species: string - background: string - experiencePoints: number - abilities: AbilityValues - armorClass: number - speed: number - hitPointsMax: number - hitDiceType: HitDiceType - savingThrowProficiencies: string[] - skillProficiencies: string[] - filename: string + name: string; + characterClass: string; + subclass: string; + level: number; + species: string; + background: string; + experiencePoints: number; + abilities: AbilityValues; + armorClass: number; + speed: number; + hitPointsMax: number; + hitDiceType: HitDiceType; + savingThrowProficiencies: string[]; + skillProficiencies: string[]; + filename: string; } export type AbilityValues = { - [key in Ability]: number -} + [key in Ability]: number; +}; diff --git a/apps/game-tools/src/components/GlobalProviders.tsx b/apps/game-tools/src/components/GlobalProviders.tsx index f2af2fb8..9f7816de 100644 --- a/apps/game-tools/src/components/GlobalProviders.tsx +++ b/apps/game-tools/src/components/GlobalProviders.tsx @@ -1,5 +1,5 @@ -import { ThemeProvider } from '@ageorgedev/design-system/theming/ThemeProvider' +import { ThemeProvider } from '@ageorgedev/design-system/theming/ThemeProvider'; export function GlobalProviders({ children }: React.PropsWithChildren) { - return {children} + return {children}; } diff --git a/apps/game-tools/src/router.tsx b/apps/game-tools/src/router.tsx index ff5154f2..dade4ff0 100644 --- a/apps/game-tools/src/router.tsx +++ b/apps/game-tools/src/router.tsx @@ -1,5 +1,5 @@ -import { createRouter as createTanStackRouter } from '@tanstack/react-router' -import { routeTree } from './routeTree.gen' +import { createRouter as createTanStackRouter } from '@tanstack/react-router'; +import { routeTree } from './routeTree.gen'; export function getRouter() { const router = createTanStackRouter({ @@ -8,12 +8,12 @@ export function getRouter() { scrollRestoration: true, defaultPreload: 'intent', defaultPreloadStaleTime: 0, - }) - return router + }); + return router; } declare module '@tanstack/react-router' { interface Register { - router: ReturnType + router: ReturnType; } } diff --git a/apps/game-tools/src/routes/__root.tsx b/apps/game-tools/src/routes/__root.tsx index 6d543b1f..b7c8bd24 100644 --- a/apps/game-tools/src/routes/__root.tsx +++ b/apps/game-tools/src/routes/__root.tsx @@ -1,15 +1,15 @@ -import { THEME_INIT_SCRIPT } from '@ageorgedev/design-system/theming/theme-init-script' +import { THEME_INIT_SCRIPT } from '@ageorgedev/design-system/theming/theme-init-script'; import { createRootRouteWithContext, HeadContent, Scripts, -} from '@tanstack/react-router' -import { GlobalProviders } from '../components/GlobalProviders' -import appCss from '../styles.css?url' +} from '@tanstack/react-router'; +import { GlobalProviders } from '../components/GlobalProviders'; +import appCss from '../styles.css?url'; type MyRouterContext = { - temp?: string -} + temp?: string; +}; export const Route = createRootRouteWithContext()({ head: () => ({ @@ -33,7 +33,7 @@ export const Route = createRootRouteWithContext()({ ], }), shellComponent: RootDocument, -}) +}); function RootDocument({ children }: { children: React.ReactNode }) { return ( @@ -48,5 +48,5 @@ function RootDocument({ children }: { children: React.ReactNode }) { - ) + ); } diff --git a/apps/game-tools/src/routes/_public.tsx b/apps/game-tools/src/routes/_public.tsx index 2a25ebf8..69d3b6c0 100644 --- a/apps/game-tools/src/routes/_public.tsx +++ b/apps/game-tools/src/routes/_public.tsx @@ -1,9 +1,9 @@ -import { ThemeSwitcher } from '@ageorgedev/design-system/theming/ThemeSwitcher' -import { createFileRoute, Link, Outlet } from '@tanstack/react-router' +import { ThemeSwitcher } from '@ageorgedev/design-system/theming/ThemeSwitcher'; +import { createFileRoute, Link, Outlet } from '@tanstack/react-router'; export const Route = createFileRoute('/_public')({ component: RouteComponent, -}) +}); function RouteComponent() { return ( @@ -26,5 +26,5 @@ function RouteComponent() { - ) + ); } diff --git a/apps/game-tools/src/routes/_public/dnd/characters/example.tsx b/apps/game-tools/src/routes/_public/dnd/characters/example.tsx index 824b4f31..c871c9e6 100644 --- a/apps/game-tools/src/routes/_public/dnd/characters/example.tsx +++ b/apps/game-tools/src/routes/_public/dnd/characters/example.tsx @@ -1,10 +1,10 @@ -import { ExampleSheet } from '@ageorgedev/dnd-character-sheet' -import { createFileRoute } from '@tanstack/react-router' +import { ExampleSheet } from '@ageorgedev/dnd-character-sheet'; +import { createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute('/_public/dnd/characters/example')({ component: RouteComponent, -}) +}); function RouteComponent() { - return + return ; } diff --git a/apps/game-tools/src/routes/_public/dnd/characters/index.tsx b/apps/game-tools/src/routes/_public/dnd/characters/index.tsx index 69c16990..0c057540 100644 --- a/apps/game-tools/src/routes/_public/dnd/characters/index.tsx +++ b/apps/game-tools/src/routes/_public/dnd/characters/index.tsx @@ -1,8 +1,8 @@ -import { createFileRoute, Link } from '@tanstack/react-router' +import { createFileRoute, Link } from '@tanstack/react-router'; export const Route = createFileRoute('/_public/dnd/characters/')({ component: RouteComponent, -}) +}); function RouteComponent() { return ( @@ -18,5 +18,5 @@ function RouteComponent() {

- ) + ); } diff --git a/apps/game-tools/src/routes/_public/index.tsx b/apps/game-tools/src/routes/_public/index.tsx index f2812d7f..28af8e50 100644 --- a/apps/game-tools/src/routes/_public/index.tsx +++ b/apps/game-tools/src/routes/_public/index.tsx @@ -1,8 +1,8 @@ -import { createFileRoute, Link } from '@tanstack/react-router' +import { createFileRoute, Link } from '@tanstack/react-router'; export const Route = createFileRoute('/_public/')({ component: RouteComponent, -}) +}); function RouteComponent() { return ( @@ -21,5 +21,5 @@ function RouteComponent() {
- ) + ); } diff --git a/apps/game-tools/vite.config.ts b/apps/game-tools/vite.config.ts index a1feeac3..ad2eac1b 100644 --- a/apps/game-tools/vite.config.ts +++ b/apps/game-tools/vite.config.ts @@ -1,7 +1,7 @@ -import tailwindcss from '@tailwindcss/vite' -import { tanstackStart } from '@tanstack/react-start/plugin/vite' -import react from '@vitejs/plugin-react' -import { defineConfig, searchForWorkspaceRoot } from 'vite' +import tailwindcss from '@tailwindcss/vite'; +import { tanstackStart } from '@tanstack/react-start/plugin/vite'; +import react from '@vitejs/plugin-react'; +import { defineConfig, searchForWorkspaceRoot } from 'vite'; const config = defineConfig({ cacheDir: '../../node_modules/.vite/game-tools', @@ -20,6 +20,6 @@ const config = defineConfig({ allow: [searchForWorkspaceRoot(process.cwd())], }, }, -}) +}); -export default config +export default config; diff --git a/apps/game-tools/vitest.config.ts b/apps/game-tools/vitest.config.ts index 9b6bd972..c641e500 100644 --- a/apps/game-tools/vitest.config.ts +++ b/apps/game-tools/vitest.config.ts @@ -1,5 +1,5 @@ -import react from '@vitejs/plugin-react' -import { defineConfig } from 'vitest/config' +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vitest/config'; export default defineConfig({ plugins: [react()], @@ -8,4 +8,4 @@ export default defineConfig({ setupFiles: ['@ageorgedev/testing-config/react-jsdom-test-setup.ts'], include: ['src/**/*.spec.{ts,tsx}'], }, -}) +}); diff --git a/biome.json b/biome.json index 6117eb23..31a0776c 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.4.16/schema.json", + "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true }, "files": { "includes": ["**", "!!**/dist", "!package.json"] }, "formatter": { @@ -36,12 +36,8 @@ "jsxQuoteStyle": "double", "quoteProperties": "asNeeded", "trailingCommas": "es5", - "semicolons": "asNeeded", - "arrowParentheses": "always", "bracketSameLine": false, - "quoteStyle": "single", - "attributePosition": "auto", - "bracketSpacing": true + "quoteStyle": "single" } }, "html": { diff --git a/packages/brand-components/src/TwLogo/TwLogo.spec.tsx b/packages/brand-components/src/TwLogo/TwLogo.spec.tsx index c89b5b8f..372b33b9 100644 --- a/packages/brand-components/src/TwLogo/TwLogo.spec.tsx +++ b/packages/brand-components/src/TwLogo/TwLogo.spec.tsx @@ -1,26 +1,26 @@ -import { render, screen } from '@testing-library/react' -import { describe, expect, it } from 'vitest' -import { TwLogo } from './TwLogo' +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { TwLogo } from './TwLogo'; describe('TwLogo', () => { it('renders an SVG element', () => { - const { container } = render() - expect(container.querySelector('svg')).toBeInTheDocument() - }) + const { container } = render(); + expect(container.querySelector('svg')).toBeInTheDocument(); + }); it('contains an accessible title', () => { - const { container } = render() - const title = container.querySelector('title') - expect(title?.textContent).toBe('ThoughtWorks') - }) + const { container } = render(); + const title = container.querySelector('title'); + expect(title?.textContent).toBe('ThoughtWorks'); + }); it('forwards className to the root SVG', () => { - const { container } = render() - expect(container.querySelector('svg')).toHaveClass('my-logo') - }) + const { container } = render(); + expect(container.querySelector('svg')).toHaveClass('my-logo'); + }); it('forwards data-testid so the element is queryable', () => { - render() - expect(screen.getByTestId('tw-logo')).toBeInTheDocument() - }) -}) + render(); + expect(screen.getByTestId('tw-logo')).toBeInTheDocument(); + }); +}); diff --git a/packages/brand-components/src/TwLogo/TwLogo.tsx b/packages/brand-components/src/TwLogo/TwLogo.tsx index 866ecbac..b1f8500a 100644 --- a/packages/brand-components/src/TwLogo/TwLogo.tsx +++ b/packages/brand-components/src/TwLogo/TwLogo.tsx @@ -1,4 +1,4 @@ -import { bar, text } from './styles' +import { bar, text } from './styles'; export function TwLogo(props: React.SVGProps) { return ( @@ -103,5 +103,5 @@ export function TwLogo(props: React.SVGProps) { - ) + ); } diff --git a/packages/brand-components/src/TwLogo/styles.ts b/packages/brand-components/src/TwLogo/styles.ts index 75e71730..8a41025b 100644 --- a/packages/brand-components/src/TwLogo/styles.ts +++ b/packages/brand-components/src/TwLogo/styles.ts @@ -1,2 +1,2 @@ -export const bar = 'fill-[#F2617A]' -export const text = 'fill-[#003D4F] dark:fill-[#EDF1F3]' +export const bar = 'fill-[#F2617A]'; +export const text = 'fill-[#003D4F] dark:fill-[#EDF1F3]'; diff --git a/packages/brand-components/src/index.ts b/packages/brand-components/src/index.ts index f9d0122e..dccac1ac 100644 --- a/packages/brand-components/src/index.ts +++ b/packages/brand-components/src/index.ts @@ -1 +1 @@ -export * from './TwLogo/TwLogo' +export * from './TwLogo/TwLogo'; diff --git a/packages/brand-components/vitest.config.ts b/packages/brand-components/vitest.config.ts index 9b6bd972..c641e500 100644 --- a/packages/brand-components/vitest.config.ts +++ b/packages/brand-components/vitest.config.ts @@ -1,5 +1,5 @@ -import react from '@vitejs/plugin-react' -import { defineConfig } from 'vitest/config' +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vitest/config'; export default defineConfig({ plugins: [react()], @@ -8,4 +8,4 @@ export default defineConfig({ setupFiles: ['@ageorgedev/testing-config/react-jsdom-test-setup.ts'], include: ['src/**/*.spec.{ts,tsx}'], }, -}) +}); diff --git a/packages/design-system/src/CodeBlock/CodeBlock.spec.tsx b/packages/design-system/src/CodeBlock/CodeBlock.spec.tsx index 4a388def..a508087f 100644 --- a/packages/design-system/src/CodeBlock/CodeBlock.spec.tsx +++ b/packages/design-system/src/CodeBlock/CodeBlock.spec.tsx @@ -1,11 +1,11 @@ -import { act, render } from '@testing-library/react' -import { describe, expect, it, vi } from 'vitest' -import type { Theme } from '../theming/models' -import { ThemeContext } from '../theming/ThemeContext' -import { ThemeProvider } from '../theming/ThemeProvider' -import { CodeBlock } from './CodeBlock' +import { act, render } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; +import type { Theme } from '../theming/models'; +import { ThemeContext } from '../theming/ThemeContext'; +import { ThemeProvider } from '../theming/ThemeProvider'; +import { CodeBlock } from './CodeBlock'; -vi.mock('../theming/ThemeProvider') +vi.mock('../theming/ThemeProvider'); async function setupTestSubject(ui: React.ReactElement, theme: Theme = 'dark') { return act(() => @@ -21,57 +21,57 @@ async function setupTestSubject(ui: React.ReactElement, theme: Theme = 'dark') { {ui} ) - ) + ); } describe('CodeBlock', () => { const defaultProps = { text: 'const hello = "world";', lang: 'typescript' as const, - } + }; it('renders code with default props', async () => { const { container } = await setupTestSubject( - ) - const codeElement = container.querySelector('code') - expect(codeElement).toBeInTheDocument() - }) + ); + const codeElement = container.querySelector('code'); + expect(codeElement).toBeInTheDocument(); + }); it('renders with different font sizes', async () => { const { container, rerender } = await setupTestSubject( - ) - const codeElement = container.querySelector('code') - expect(codeElement?.parentElement?.parentElement).toHaveClass('text-md') + ); + const codeElement = container.querySelector('code'); + expect(codeElement?.parentElement?.parentElement).toHaveClass('text-md'); rerender( - ) - const codeElementLarge = container.querySelector('code') + ); + const codeElementLarge = container.querySelector('code'); expect(codeElementLarge?.parentElement?.parentElement).toHaveClass( 'text-xl' - ) - }) + ); + }); it('renders with different languages', async () => { const { container } = await setupTestSubject( - ) - const codeElement = container.querySelector('code') - expect(codeElement).toBeInTheDocument() - expect(codeElement?.textContent).toBe('1const hello = "world";') - }) + ); + const codeElement = container.querySelector('code'); + expect(codeElement).toBeInTheDocument(); + expect(codeElement?.textContent).toBe('1const hello = "world";'); + }); it('renders with dark theme', async () => { const { container } = await setupTestSubject( , 'dark' - ) - const codeElement = container.querySelector('code') - expect(codeElement).toBeInTheDocument() - expect(codeElement?.textContent).toBe('1const hello = "world";') - }) -}) + ); + const codeElement = container.querySelector('code'); + expect(codeElement).toBeInTheDocument(); + expect(codeElement?.textContent).toBe('1const hello = "world";'); + }); +}); diff --git a/packages/design-system/src/CodeBlock/CodeBlock.stories.tsx b/packages/design-system/src/CodeBlock/CodeBlock.stories.tsx index f360466e..93c4dcaf 100644 --- a/packages/design-system/src/CodeBlock/CodeBlock.stories.tsx +++ b/packages/design-system/src/CodeBlock/CodeBlock.stories.tsx @@ -1,5 +1,5 @@ -import type { Meta, StoryObj } from '@storybook/react-vite' -import { CodeBlock } from './CodeBlock' +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { CodeBlock } from './CodeBlock'; export default { title: 'Molecules/CodeBlock', @@ -17,9 +17,9 @@ export default { control: 'text', }, }, -} satisfies Meta +} satisfies Meta; -type Story = StoryObj +type Story = StoryObj; const typescriptExample = `interface User { id: string; @@ -30,7 +30,7 @@ const typescriptExample = `interface User { function greetUser(user: User): string { return \`Hello \${user.name}!\`; -}` +}`; const htmlExample = ` @@ -42,7 +42,7 @@ const htmlExample = `

Welcome

This is an example HTML page.

-` +`; const cssExample = `.container { display: flex; @@ -57,7 +57,7 @@ const cssExample = `.container { border-radius: 0.25rem; background-color: #007bff; color: white; -}` +}`; const jsxExample = `function Button({ text, onClick }) { return ( @@ -68,7 +68,7 @@ const jsxExample = `function Button({ text, onClick }) { {text} ); -}` +}`; export const TypeScript: Story = { args: { @@ -76,7 +76,7 @@ export const TypeScript: Story = { text: typescriptExample, fontSize: 'normal', }, -} +}; export const HTML: Story = { args: { @@ -84,7 +84,7 @@ export const HTML: Story = { text: htmlExample, fontSize: 'normal', }, -} +}; export const CSS: Story = { args: { @@ -92,7 +92,7 @@ export const CSS: Story = { text: cssExample, fontSize: 'normal', }, -} +}; export const JSX: Story = { args: { @@ -100,7 +100,7 @@ export const JSX: Story = { text: jsxExample, fontSize: 'normal', }, -} +}; export const SmallFont: Story = { args: { @@ -108,7 +108,7 @@ export const SmallFont: Story = { text: typescriptExample, fontSize: 'small', }, -} +}; export const LargeFont: Story = { args: { @@ -116,7 +116,7 @@ export const LargeFont: Story = { text: typescriptExample, fontSize: 'large', }, -} +}; export const WithCustomClassName: Story = { args: { @@ -124,7 +124,7 @@ export const WithCustomClassName: Story = { text: typescriptExample, className: 'max-w-2xl', }, -} +}; export const InteractiveExample: Story = { args: { @@ -132,4 +132,4 @@ export const InteractiveExample: Story = { text: '// Try changing the language, font size, and code!', fontSize: 'normal', }, -} +}; diff --git a/packages/design-system/src/CodeBlock/CodeBlock.tsx b/packages/design-system/src/CodeBlock/CodeBlock.tsx index 257f1200..ef79ac26 100644 --- a/packages/design-system/src/CodeBlock/CodeBlock.tsx +++ b/packages/design-system/src/CodeBlock/CodeBlock.tsx @@ -1,20 +1,20 @@ -import { cn } from '@ageorgedev/toolbelt/cn' -import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' +import { cn } from '@ageorgedev/toolbelt/cn'; +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { darcula as darkTheme, solarizedlight as lightTheme, -} from 'react-syntax-highlighter/dist/esm/styles/prism' -import { Card } from '../cards/Card' -import { useTheme } from '../theming/ThemeContext' +} from 'react-syntax-highlighter/dist/esm/styles/prism'; +import { Card } from '../cards/Card'; +import { useTheme } from '../theming/ThemeContext'; type CodeBlockProps = { - text: string - lang?: 'typescript' | 'html' | 'css' | 'jsx' | 'tsx' | 'javascript' - fontSize?: 'normal' | 'small' | 'large' -} & React.HTMLProps + text: string; + lang?: 'typescript' | 'html' | 'css' | 'jsx' | 'tsx' | 'javascript'; + fontSize?: 'normal' | 'small' | 'large'; +} & React.HTMLProps; -const fixedLightTheme = fixFont(lightTheme) -const fixedDarkTheme = fixFont(darkTheme) +const fixedLightTheme = fixFont(lightTheme); +const fixedDarkTheme = fixFont(darkTheme); export function CodeBlock({ lang, @@ -23,8 +23,8 @@ export function CodeBlock({ className, ...otherProps }: CodeBlockProps) { - const { theme } = useTheme() - const codeTheme = theme === 'light' ? fixedLightTheme : fixedDarkTheme + const { theme } = useTheme(); + const codeTheme = theme === 'light' ? fixedLightTheme : fixedDarkTheme; return ( - ) + ); } const fontSizeMap: Record['fontSize'], string> = { large: 'text-xl large-desktop:text-2xl', normal: 'text-lg large-desktop:text-xl', small: 'text-md large-desktop:text-lg', -} +}; function fixFont(theme: { [k: string]: React.CSSProperties }) { - const styleKey1 = 'code[class*="language-"]' - const styleKey2 = 'pre[class*="language-"]' + const styleKey1 = 'code[class*="language-"]'; + const styleKey2 = 'pre[class*="language-"]'; return { ...theme, [styleKey1]: { @@ -62,5 +62,5 @@ function fixFont(theme: { [k: string]: React.CSSProperties }) { ...theme[styleKey2], fontFamily: '"Source Code Pro", monospace', }, - } + }; } diff --git a/packages/design-system/src/callouts/CalloutBadge.spec.tsx b/packages/design-system/src/callouts/CalloutBadge.spec.tsx index b9f62686..f764e4e8 100644 --- a/packages/design-system/src/callouts/CalloutBadge.spec.tsx +++ b/packages/design-system/src/callouts/CalloutBadge.spec.tsx @@ -1,81 +1,83 @@ -import { InfoIcon } from '@phosphor-icons/react' -import { render, screen } from '@testing-library/react' -import { describe, expect, it } from 'vitest' -import { CalloutBadge } from './CalloutBadge' +import { InfoIcon } from '@phosphor-icons/react'; +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { CalloutBadge } from './CalloutBadge'; describe('CalloutBadge', () => { const defaultProps = { text: 'Test Badge', icon: InfoIcon, - } + }; it('renders with default props', () => { - render() - expect(screen.getByText('Test Badge')).toBeInTheDocument() + render(); + expect(screen.getByText('Test Badge')).toBeInTheDocument(); expect(screen.getByText('Test Badge').parentElement).toHaveClass( 'before:bg-primary-surface-2' - ) + ); expect(screen.getByText('Test Badge').parentElement).toHaveClass( 'text-primary-onsurface-2' - ) - }) + ); + }); it('applies custom className', () => { - render() + render(); expect( screen.getByText('Test Badge').parentElement?.parentElement?.parentElement - ).toHaveClass('custom-class') - }) + ).toHaveClass('custom-class'); + }); it('renders with different types', () => { - const { rerender } = render() + const { rerender } = render( + + ); expect(screen.getByText('Test Badge').parentElement).toHaveClass( 'before:bg-primary-surface-2' - ) + ); expect(screen.getByText('Test Badge').parentElement).toHaveClass( 'text-primary-onsurface-2' - ) + ); - rerender() + rerender(); expect(screen.getByText('Test Badge').parentElement).toHaveClass( 'before:bg-primary-surface' - ) + ); expect(screen.getByText('Test Badge').parentElement).toHaveClass( 'text-primary-onsurface' - ) + ); - rerender() + rerender(); expect(screen.getByText('Test Badge').parentElement).toHaveClass( 'before:bg-primary-surface-0' - ) + ); expect(screen.getByText('Test Badge').parentElement).toHaveClass( 'text-primary-onsurface-0' - ) + ); - rerender() + rerender(); expect(screen.getByText('Test Badge').parentElement).toHaveClass( 'before:bg-info-surface' - ) + ); expect(screen.getByText('Test Badge').parentElement).toHaveClass( 'text-info-onsurface' - ) + ); - rerender() + rerender(); expect(screen.getByText('Test Badge').parentElement).toHaveClass( 'before:bg-destructive-surface' - ) + ); expect(screen.getByText('Test Badge').parentElement).toHaveClass( 'text-destructive-onsurface' - ) - }) + ); + }); it('renders with default padding when no className is provided', () => { - render() + render(); expect( screen.getByText('Test Badge').parentElement?.parentElement?.parentElement - ).toHaveClass('inline-block') + ).toHaveClass('inline-block'); expect( screen.getByText('Test Badge').parentElement?.parentElement?.parentElement - ).toHaveClass('p-5') - }) -}) + ).toHaveClass('p-5'); + }); +}); diff --git a/packages/design-system/src/callouts/CalloutBadge.stories.tsx b/packages/design-system/src/callouts/CalloutBadge.stories.tsx index 56f81653..d16c9e9e 100644 --- a/packages/design-system/src/callouts/CalloutBadge.stories.tsx +++ b/packages/design-system/src/callouts/CalloutBadge.stories.tsx @@ -13,9 +13,9 @@ import { StarIcon, TrophyIcon, UserIcon, -} from '@phosphor-icons/react/ssr' -import type { Meta, StoryObj } from '@storybook/react-vite' -import { CalloutBadge } from './CalloutBadge' +} from '@phosphor-icons/react/ssr'; +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { CalloutBadge } from './CalloutBadge'; export default { title: 'Atoms/Callout Badge', @@ -64,9 +64,9 @@ export default { }, }, }, -} satisfies Meta +} satisfies Meta; -type Story = StoryObj +type Story = StoryObj; export const Default: Story = { args: { @@ -74,7 +74,7 @@ export const Default: Story = { icon: AddressBookIcon, text: 'Default Badge', }, -} +}; export const Info1: Story = { args: { @@ -82,7 +82,7 @@ export const Info1: Story = { icon: AddressBookIcon, text: 'Info 1 Badge', }, -} +}; export const Info2: Story = { args: { @@ -90,7 +90,7 @@ export const Info2: Story = { icon: AirplaneInFlightIcon, text: 'Info 2 Badge', }, -} +}; export const Info3: Story = { args: { @@ -98,7 +98,7 @@ export const Info3: Story = { icon: SealWarningIcon, text: 'Info 3 Badge', }, -} +}; export const Danger: Story = { args: { @@ -106,7 +106,7 @@ export const Danger: Story = { icon: SkullIcon, text: 'Danger Badge', }, -} +}; export const Neutral: Story = { args: { @@ -114,7 +114,7 @@ export const Neutral: Story = { icon: PenNibIcon, text: 'Neutral Badge', }, -} +}; export const WithDifferentIcons: Story = { render: () => ( @@ -126,7 +126,7 @@ export const WithDifferentIcons: Story = { ), -} +}; export const WithLongText: Story = { args: { @@ -134,7 +134,7 @@ export const WithLongText: Story = { icon: HeartIcon, text: 'This is a callout badge with a longer text that might wrap', }, -} +}; export const WithCustomSize: Story = { args: { @@ -143,7 +143,7 @@ export const WithCustomSize: Story = { text: 'Custom Size', className: 'p-8', }, -} +}; export const InteractiveExample: Story = { args: { @@ -151,4 +151,4 @@ export const InteractiveExample: Story = { icon: TrophyIcon, text: 'Try changing the type, icon, and text!', }, -} +}; diff --git a/packages/design-system/src/callouts/CalloutBadge.tsx b/packages/design-system/src/callouts/CalloutBadge.tsx index 7d11a861..17f026b1 100644 --- a/packages/design-system/src/callouts/CalloutBadge.tsx +++ b/packages/design-system/src/callouts/CalloutBadge.tsx @@ -1,13 +1,13 @@ -import { cn } from '@ageorgedev/toolbelt/cn' -import type { Icon } from '@phosphor-icons/react' -import { InterfaceXl } from '../typography/typography-components' +import { cn } from '@ageorgedev/toolbelt/cn'; +import type { Icon } from '@phosphor-icons/react'; +import { InterfaceXl } from '../typography/typography-components'; type CalloutBadgeProps = { - text: string - icon: Icon - className?: string - type?: CalloutType -} + text: string; + icon: Icon; + className?: string; + type?: CalloutType; +}; export function CalloutBadge({ text, @@ -15,7 +15,7 @@ export function CalloutBadge({ type, className, }: CalloutBadgeProps) { - const typeClasses = StyleClasses[type ?? 'info1'] + const typeClasses = StyleClasses[type ?? 'info1']; return (
@@ -30,10 +30,10 @@ export function CalloutBadge({
- ) + ); } -type CalloutType = 'info1' | 'info2' | 'info3' | 'danger' | 'neutral' +type CalloutType = 'info1' | 'info2' | 'info3' | 'danger' | 'neutral'; const StyleClasses: Record = { info1: 'before:bg-primary-surface-2 text-primary-onsurface-2', @@ -41,4 +41,4 @@ const StyleClasses: Record = { info3: 'before:bg-primary-surface-0 text-primary-onsurface-0', neutral: 'before:bg-info-surface text-info-onsurface', // TODO danger: 'before:bg-destructive-surface text-destructive-onsurface', -} +}; diff --git a/packages/design-system/src/cards/Card.spec.tsx b/packages/design-system/src/cards/Card.spec.tsx index 9238ab7d..7a11d017 100644 --- a/packages/design-system/src/cards/Card.spec.tsx +++ b/packages/design-system/src/cards/Card.spec.tsx @@ -1,31 +1,31 @@ -import { render, screen } from '@testing-library/react' -import { describe, expect, it } from 'vitest' -import { Card } from './Card' +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { Card } from './Card'; describe('Card', () => { it('renders with default styles', () => { - render(Card content) - const card = screen.getByText('Card content') - expect(card).toBeInTheDocument() - expect(card).toHaveClass('shadow-normal') - expect(card).toHaveClass('border-line') - expect(card).toHaveClass('border-2') - expect(card).toHaveClass('mb-4') - }) + render(Card content); + const card = screen.getByText('Card content'); + expect(card).toBeInTheDocument(); + expect(card).toHaveClass('shadow-normal'); + expect(card).toHaveClass('border-line'); + expect(card).toHaveClass('border-2'); + expect(card).toHaveClass('mb-4'); + }); it('applies custom className', () => { - render(Card content) - const card = screen.getByText('Card content') - expect(card).toHaveClass('custom-class') - }) + render(Card content); + const card = screen.getByText('Card content'); + expect(card).toHaveClass('custom-class'); + }); it('forwards HTML props', () => { render( Card content - ) - const card = screen.getByTestId('test-card') - expect(card).toHaveAttribute('aria-label', 'Test Card') - }) -}) + ); + const card = screen.getByTestId('test-card'); + expect(card).toHaveAttribute('aria-label', 'Test Card'); + }); +}); diff --git a/packages/design-system/src/cards/Card.tsx b/packages/design-system/src/cards/Card.tsx index e455faf9..4aab817c 100644 --- a/packages/design-system/src/cards/Card.tsx +++ b/packages/design-system/src/cards/Card.tsx @@ -3,7 +3,7 @@ * Rectangular in shape */ -import { cn } from '@ageorgedev/toolbelt/cn' +import { cn } from '@ageorgedev/toolbelt/cn'; export function Card({ className, @@ -14,5 +14,5 @@ export function Card({ {...otherProps} className={cn('border-line mb-4 border-2 shadow-normal', className)} > - ) + ); } diff --git a/packages/design-system/src/cards/TiltCard.spec.tsx b/packages/design-system/src/cards/TiltCard.spec.tsx index b412ea3c..8981bf0b 100644 --- a/packages/design-system/src/cards/TiltCard.spec.tsx +++ b/packages/design-system/src/cards/TiltCard.spec.tsx @@ -1,106 +1,108 @@ -import { render, screen } from '@testing-library/react' -import { describe, expect, it } from 'vitest' -import { TiltCard } from './TiltCard' -import styles from './TiltCard.module.css' +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { TiltCard } from './TiltCard'; +import styles from './TiltCard.module.css'; describe('TiltCard', () => { it.skip('renders with default styles', () => { - render(Card content) - const card = screen.getByText('Card content') - expect(card).toBeInTheDocument() - expect(card.parentElement?.parentElement).toHaveClass('elv-raised-md') - expect(card.parentElement?.parentElement).toHaveClass(styles['skew-medium']) - }) + render(Card content); + const card = screen.getByText('Card content'); + expect(card).toBeInTheDocument(); + expect(card.parentElement?.parentElement).toHaveClass('elv-raised-md'); + expect(card.parentElement?.parentElement).toHaveClass( + styles['skew-medium'] + ); + }); it('applies custom className', () => { - render(Card content) - const card = screen.getByText('Card content') - expect(card).toHaveClass('custom-class') - }) + render(Card content); + const card = screen.getByText('Card content'); + expect(card).toHaveClass('custom-class'); + }); it('applies outerClassName', () => { - render(Card content) - const card = screen.getByText('Card content') - expect(card.parentElement?.parentElement).toHaveClass('outer-class') - }) + render(Card content); + const card = screen.getByText('Card content'); + expect(card.parentElement?.parentElement).toHaveClass('outer-class'); + }); it.skip('applies different shapes', () => { const { rerender } = render( Card content - ) + ); expect( screen.getByText('Card content').parentElement?.parentElement - ).toHaveClass(styles.trapRight) + ).toHaveClass(styles.trapRight); - rerender(Card content) + rerender(Card content); expect( screen.getByText('Card content').parentElement?.parentElement - ).toHaveClass(styles.trapLeft) + ).toHaveClass(styles.trapLeft); - rerender(Card content) + rerender(Card content); expect( screen.getByText('Card content').parentElement?.parentElement - ).toHaveClass(styles.triUpperRight) + ).toHaveClass(styles.triUpperRight); - rerender(Card content) + rerender(Card content); expect( screen.getByText('Card content').parentElement?.parentElement - ).toHaveClass(styles.triUpperLeft) - }) + ).toHaveClass(styles.triUpperLeft); + }); it.skip('applies different skew strengths', () => { const { rerender } = render( Card content - ) + ); expect( screen.getByText('Card content').parentElement?.parentElement - ).toHaveClass(styles['skew-small']) + ).toHaveClass(styles['skew-small']); - rerender(Card content) + rerender(Card content); expect( screen.getByText('Card content').parentElement?.parentElement - ).toHaveClass(styles['skew-medium']) + ).toHaveClass(styles['skew-medium']); - rerender(Card content) + rerender(Card content); expect( screen.getByText('Card content').parentElement?.parentElement - ).toHaveClass(styles['skew-large']) - }) + ).toHaveClass(styles['skew-large']); + }); it('applies interactive styles when interactive is true', () => { - render(Card content) + render(Card content); expect( screen.getByText('Card content').parentElement?.parentElement - ).toHaveClass('hover:elv-raised-lg') - }) + ).toHaveClass('hover:elv-raised-lg'); + }); it('applies different border styles', () => { - const { rerender } = render(Card content) + const { rerender } = render(Card content); expect(screen.getByText('Card content').parentElement).toHaveClass( 'p-thick-line' - ) + ); - rerender(Card content) + rerender(Card content); expect(screen.getByText('Card content').parentElement).toHaveClass( 'pb-thick-line' - ) + ); - rerender(Card content) + rerender(Card content); expect(screen.getByText('Card content').parentElement).not.toHaveClass( 'p-thick-line' - ) + ); expect(screen.getByText('Card content').parentElement).not.toHaveClass( 'pb-thick-line' - ) - }) + ); + }); it('forwards HTML props', () => { render( Card content - ) - const card = screen.getByTestId('test-card') - expect(card).toHaveAttribute('aria-label', 'Test Card') - }) -}) + ); + const card = screen.getByTestId('test-card'); + expect(card).toHaveAttribute('aria-label', 'Test Card'); + }); +}); diff --git a/packages/design-system/src/cards/TiltCard.stories.tsx b/packages/design-system/src/cards/TiltCard.stories.tsx index b12d3150..a71d75e6 100644 --- a/packages/design-system/src/cards/TiltCard.stories.tsx +++ b/packages/design-system/src/cards/TiltCard.stories.tsx @@ -1,5 +1,5 @@ -import type { Meta, StoryObj } from '@storybook/react-vite' -import { TiltCard } from './TiltCard' +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { TiltCard } from './TiltCard'; export default { title: 'Atoms/Tilt Card', @@ -21,9 +21,9 @@ export default { control: 'boolean', }, }, -} satisfies Meta +} satisfies Meta; -type Story = StoryObj +type Story = StoryObj; export const Default: Story = { args: { @@ -33,7 +33,7 @@ export const Default: Story = { border: 'all', children: 'Default Tilt Card', }, -} +}; export const TrapLeft: Story = { args: { @@ -41,7 +41,7 @@ export const TrapLeft: Story = { interactive: true, children: 'Trap Left Shape', }, -} +}; export const TriUpperRight: Story = { args: { @@ -49,7 +49,7 @@ export const TriUpperRight: Story = { interactive: true, children: 'Triangle Upper Right Shape', }, -} +}; export const TriUpperLeft: Story = { args: { @@ -57,7 +57,7 @@ export const TriUpperLeft: Story = { interactive: true, children: 'Triangle Upper Left Shape', }, -} +}; export const SmallSkew: Story = { args: { @@ -65,7 +65,7 @@ export const SmallSkew: Story = { skewStrength: 'small', children: 'Small Skew Strength', }, -} +}; export const LargeSkew: Story = { args: { @@ -73,7 +73,7 @@ export const LargeSkew: Story = { skewStrength: 'large', children: 'Large Skew Strength', }, -} +}; export const BottomBorderOnly: Story = { args: { @@ -81,7 +81,7 @@ export const BottomBorderOnly: Story = { border: 'bottom', children: 'Bottom Border Only', }, -} +}; export const NoBorder: Story = { args: { @@ -89,7 +89,7 @@ export const NoBorder: Story = { border: 'none', children: 'No Border', }, -} +}; export const NonInteractive: Story = { args: { @@ -97,7 +97,7 @@ export const NonInteractive: Story = { interactive: false, children: 'Non-Interactive Card', }, -} +}; export const WithCustomContent: Story = { args: { @@ -119,4 +119,4 @@ export const WithCustomContent: Story = { ), }, -} +}; diff --git a/packages/design-system/src/cards/TiltCard.tsx b/packages/design-system/src/cards/TiltCard.tsx index 82f48a6e..fd39de56 100644 --- a/packages/design-system/src/cards/TiltCard.tsx +++ b/packages/design-system/src/cards/TiltCard.tsx @@ -1,13 +1,13 @@ -import { cn } from '@ageorgedev/toolbelt/cn' -import styles from './TiltCard.module.css' +import { cn } from '@ageorgedev/toolbelt/cn'; +import styles from './TiltCard.module.css'; export type TiltCardProps = { - outerClassName?: string - shape?: 'trapRight' | 'trapLeft' | 'triUpperRight' | 'triUpperLeft' - interactive?: boolean - skewStrength?: 'small' | 'medium' | 'large' - border?: 'all' | 'bottom' | 'none' -} & React.HTMLProps + outerClassName?: string; + shape?: 'trapRight' | 'trapLeft' | 'triUpperRight' | 'triUpperLeft'; + interactive?: boolean; + skewStrength?: 'small' | 'medium' | 'large'; + border?: 'all' | 'bottom' | 'none'; +} & React.HTMLProps; export function TiltCard({ children, @@ -19,10 +19,10 @@ export function TiltCard({ border, ...htmlProps }: TiltCardProps) { - const skewClass = shape ? styles[shape] : '' - const skewStrengthClass = styles[`skew-${skewStrength ?? 'medium'}`] - const interC = interactive ? interactiveClasses : '' - const borderC = borderClassMap[border ?? 'all'] + const skewClass = shape ? styles[shape] : ''; + const skewStrengthClass = styles[`skew-${skewStrength ?? 'medium'}`]; + const interC = interactive ? interactiveClasses : ''; + const borderC = borderClassMap[border ?? 'all']; return (
- ) + ); } const interactiveClasses = - 'transition-all hover:elv-raised-lg hover:-translate-y-2 active:elv-raised-sm' + 'transition-all hover:elv-raised-lg hover:-translate-y-2 active:elv-raised-sm'; const borderClassMap: Record['border'], string> = { all: 'p-thick-line', bottom: 'pb-thick-line', none: '', -} +}; diff --git a/packages/design-system/src/logo/NameLogo.spec.tsx b/packages/design-system/src/logo/NameLogo.spec.tsx index b3b0ef4d..e46684b7 100644 --- a/packages/design-system/src/logo/NameLogo.spec.tsx +++ b/packages/design-system/src/logo/NameLogo.spec.tsx @@ -1,6 +1,6 @@ -import { render, screen } from '@testing-library/react' -import { describe, expect, it } from 'vitest' -import { NameLogo } from './NameLogo' +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { NameLogo } from './NameLogo'; import { bottomLeftShadow, bottomRightShadow, @@ -9,47 +9,47 @@ import { logoFont, logoWrapper, shadowColor, -} from './styles' +} from './styles'; describe('NameLogo', () => { it('renders with default props', () => { - render() - const wrapper = screen.getByText('Anish').parentElement - expect(wrapper).toHaveClass(logoWrapper) + render(); + const wrapper = screen.getByText('Anish').parentElement; + expect(wrapper).toHaveClass(logoWrapper); expect(screen.getByText('Anish')).toHaveClass( logoFont, bottomRightShadow, shadowColor, firstNameColor - ) + ); expect(screen.getByText('George')).toHaveClass( logoFont, bottomRightShadow, shadowColor, lastNameColor - ) - }) + ); + }); it('applies custom className', () => { - render() - const wrapper = screen.getByText('Anish').parentElement - expect(wrapper).toHaveClass('custom-class') - }) + render(); + const wrapper = screen.getByText('Anish').parentElement; + expect(wrapper).toHaveClass('custom-class'); + }); it('applies left shadow direction', () => { - render() - expect(screen.getByText('Anish')).toHaveClass(bottomLeftShadow) - expect(screen.getByText('George')).toHaveClass(bottomLeftShadow) - }) + render(); + expect(screen.getByText('Anish')).toHaveClass(bottomLeftShadow); + expect(screen.getByText('George')).toHaveClass(bottomLeftShadow); + }); it('applies right shadow direction', () => { - render() - expect(screen.getByText('Anish')).toHaveClass(bottomRightShadow) - expect(screen.getByText('George')).toHaveClass(bottomRightShadow) - }) + render(); + expect(screen.getByText('Anish')).toHaveClass(bottomRightShadow); + expect(screen.getByText('George')).toHaveClass(bottomRightShadow); + }); it('renders both names with correct spacing', () => { - render() - expect(screen.getByText('George')).toHaveClass('pl-[.5em]') - }) -}) + render(); + expect(screen.getByText('George')).toHaveClass('pl-[.5em]'); + }); +}); diff --git a/packages/design-system/src/logo/NameLogo.stories.tsx b/packages/design-system/src/logo/NameLogo.stories.tsx index da768dc1..f6439ce0 100644 --- a/packages/design-system/src/logo/NameLogo.stories.tsx +++ b/packages/design-system/src/logo/NameLogo.stories.tsx @@ -1,5 +1,5 @@ -import type { Meta, StoryObj } from '@storybook/react-vite' -import { NameLogo } from './NameLogo' +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { NameLogo } from './NameLogo'; export default { component: NameLogo, @@ -13,44 +13,44 @@ export default { control: 'text', }, }, -} satisfies Meta +} satisfies Meta; -type Story = StoryObj +type Story = StoryObj; export const Default: Story = { args: { className: 'text-[100px]', shadowDirection: 'right', }, -} +}; export const Small: Story = { args: { className: 'text-[50px]', shadowDirection: 'right', }, -} +}; export const Large: Story = { args: { className: 'text-[150px]', shadowDirection: 'right', }, -} +}; export const LeftShadow: Story = { args: { className: 'text-[100px]', shadowDirection: 'left', }, -} +}; export const CustomSize: Story = { args: { className: 'text-[75px]', shadowDirection: 'right', }, -} +}; export const WithCustomBackground: Story = { render: () => ( @@ -58,4 +58,4 @@ export const WithCustomBackground: Story = { ), -} +}; diff --git a/packages/design-system/src/logo/NameLogo.tsx b/packages/design-system/src/logo/NameLogo.tsx index 5030bb0c..8f174a41 100644 --- a/packages/design-system/src/logo/NameLogo.tsx +++ b/packages/design-system/src/logo/NameLogo.tsx @@ -1,4 +1,4 @@ -import { cn } from '@ageorgedev/toolbelt/cn' +import { cn } from '@ageorgedev/toolbelt/cn'; import { bottomLeftShadow, bottomRightShadow, @@ -7,16 +7,16 @@ import { logoFont, logoWrapper, shadowColor, -} from './styles' +} from './styles'; export type NameLogoProps = { - className?: string - shadowDirection?: 'left' | 'right' -} + className?: string; + shadowDirection?: 'left' | 'right'; +}; export function NameLogo({ className, shadowDirection }: NameLogoProps) { const shadowClass = - shadowDirection === 'left' ? bottomLeftShadow : bottomRightShadow + shadowDirection === 'left' ? bottomLeftShadow : bottomRightShadow; return (

@@ -34,5 +34,5 @@ export function NameLogo({ className, shadowDirection }: NameLogoProps) { George

- ) + ); } diff --git a/packages/design-system/src/logo/ShortNameLogo.spec.tsx b/packages/design-system/src/logo/ShortNameLogo.spec.tsx index af57c292..10f594de 100644 --- a/packages/design-system/src/logo/ShortNameLogo.spec.tsx +++ b/packages/design-system/src/logo/ShortNameLogo.spec.tsx @@ -1,25 +1,25 @@ -import { render, screen } from '@testing-library/react' -import { describe, expect, it } from 'vitest' -import { Heading3 } from '../typography/typography-components' -import { ShortNameLogo } from './ShortNameLogo' -import { firstNameColor, lastNameColor } from './styles' +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { Heading3 } from '../typography/typography-components'; +import { ShortNameLogo } from './ShortNameLogo'; +import { firstNameColor, lastNameColor } from './styles'; describe('ShortNameLogo', () => { it('renders with correct structure and styles', () => { - render() - const wrapper = screen.getByText('A').parentElement - expect(wrapper).toHaveClass(Heading3.classes) - expect(wrapper).toHaveClass('font-bold') - }) + render(); + const wrapper = screen.getByText('A').parentElement; + expect(wrapper).toHaveClass(Heading3.classes); + expect(wrapper).toHaveClass('font-bold'); + }); it('renders first initial with correct color', () => { - render() - expect(screen.getByText('A')).toHaveClass(firstNameColor) - }) + render(); + expect(screen.getByText('A')).toHaveClass(firstNameColor); + }); it('renders second initial with correct color and size', () => { - render() - expect(screen.getByText('G')).toHaveClass(lastNameColor) - expect(screen.getByText('G')).toHaveClass('text-[0.85em]') - }) -}) + render(); + expect(screen.getByText('G')).toHaveClass(lastNameColor); + expect(screen.getByText('G')).toHaveClass('text-[0.85em]'); + }); +}); diff --git a/packages/design-system/src/logo/ShortNameLogo.tsx b/packages/design-system/src/logo/ShortNameLogo.tsx index 1e95c898..42a38858 100644 --- a/packages/design-system/src/logo/ShortNameLogo.tsx +++ b/packages/design-system/src/logo/ShortNameLogo.tsx @@ -1,6 +1,6 @@ -import { cn } from '@ageorgedev/toolbelt/cn' -import { Heading3 } from '../typography/typography-components' -import { firstNameColor, lastNameColor } from './styles' +import { cn } from '@ageorgedev/toolbelt/cn'; +import { Heading3 } from '../typography/typography-components'; +import { firstNameColor, lastNameColor } from './styles'; export function ShortNameLogo() { return ( @@ -8,5 +8,5 @@ export function ShortNameLogo() { A G - ) + ); } diff --git a/packages/design-system/src/logo/styles.ts b/packages/design-system/src/logo/styles.ts index f22ae6d4..5aa8a67c 100644 --- a/packages/design-system/src/logo/styles.ts +++ b/packages/design-system/src/logo/styles.ts @@ -1,14 +1,14 @@ // export const firstNameColor = 'text-rc-timber-400 dark:text-rc-parchment-500'; -export const firstNameColor = 'text-secondary-foreground' -export const lastNameColor = 'text-primary-foreground' +export const firstNameColor = 'text-secondary-foreground'; +export const lastNameColor = 'text-primary-foreground'; -export const logoFont = `tracking-wide leading-[.75em]` +export const logoFont = `tracking-wide leading-[.75em]`; -export const bottomRightShadow = `drop-shadow-[.03em_.06em]` -export const bottomLeftShadow = `drop-shadow-[-.03em_.06em]` -export const shadowColor = `drop-shadow-rc-shadow-2 dark:drop-shadow-rc-shadow-4` +export const bottomRightShadow = `drop-shadow-[.03em_.06em]`; +export const bottomLeftShadow = `drop-shadow-[-.03em_.06em]`; +export const shadowColor = `drop-shadow-rc-shadow-2 dark:drop-shadow-rc-shadow-4`; export const logoWrapper = ` inline-block font-heading font-bold text-left pt-[.1em] pb-[.25em] px-[.1em] -` +`; diff --git a/packages/design-system/src/theming/ThemeContext.tsx b/packages/design-system/src/theming/ThemeContext.tsx index 0ee50f8d..912d45cc 100644 --- a/packages/design-system/src/theming/ThemeContext.tsx +++ b/packages/design-system/src/theming/ThemeContext.tsx @@ -1,18 +1,18 @@ -'use client' -import { createContext, useContext } from 'react' -import type { Theme } from './models' +'use client'; +import { createContext, useContext } from 'react'; +import type { Theme } from './models'; export type ThemeContext = { - theme: Theme - setTheme(newTheme: Theme): void -} + theme: Theme; + setTheme(newTheme: Theme): void; +}; export const ThemeContext = createContext({ theme: 'dark', setTheme: () => {}, -}) +}); /** Hooks */ export function useTheme() { - return useContext(ThemeContext) + return useContext(ThemeContext); } diff --git a/packages/design-system/src/theming/ThemeProvider.tsx b/packages/design-system/src/theming/ThemeProvider.tsx index aa543998..6a559e9f 100644 --- a/packages/design-system/src/theming/ThemeProvider.tsx +++ b/packages/design-system/src/theming/ThemeProvider.tsx @@ -1,41 +1,46 @@ -import { type PropsWithChildren, useCallback, useEffect, useState } from 'react' -import type { Theme } from './models' -import { ThemeContext } from './ThemeContext' +import { + type PropsWithChildren, + useCallback, + useEffect, + useState, +} from 'react'; +import type { Theme } from './models'; +import { ThemeContext } from './ThemeContext'; /** TODO: * 1. Make the switcher button do auto as well * 2. Improve the logic */ export function ThemeProvider({ children }: PropsWithChildren) { - const [mode, setMode] = useState('auto') - const [resolvedTheme, setResolvedTheme] = useState('dark') + const [mode, setMode] = useState('auto'); + const [resolvedTheme, setResolvedTheme] = useState('dark'); useEffect(() => { - const initialMode = getInitialMode() - setMode(initialMode) - setResolvedTheme(applyThemeMode(initialMode)) - }, []) + const initialMode = getInitialMode(); + setMode(initialMode); + setResolvedTheme(applyThemeMode(initialMode)); + }, []); /** Sync auto mode */ useEffect(() => { if (mode !== 'auto') { - return + return; } - const media = window.matchMedia('(prefers-color-scheme: dark)') + const media = window.matchMedia('(prefers-color-scheme: dark)'); const onChange = () => { - setResolvedTheme(applyThemeMode('auto')) - } + setResolvedTheme(applyThemeMode('auto')); + }; - media.addEventListener('change', onChange) + media.addEventListener('change', onChange); return () => { - media.removeEventListener('change', onChange) - } - }, [mode]) + media.removeEventListener('change', onChange); + }; + }, [mode]); const manuallySetTheme = useCallback((newTheme) => { - setMode(newTheme) - setResolvedTheme(applyThemeMode(newTheme)) - window.localStorage.setItem(STORAGE_THEME_KEY, newTheme) - }, []) + setMode(newTheme); + setResolvedTheme(applyThemeMode(newTheme)); + window.localStorage.setItem(STORAGE_THEME_KEY, newTheme); + }, []); return ( {children} - ) + ); } -type ThemeMode = Theme | 'auto' -const STORAGE_THEME_KEY = 'theme' +type ThemeMode = Theme | 'auto'; +const STORAGE_THEME_KEY = 'theme'; function getInitialMode(): ThemeMode { if (typeof window === 'undefined') { - return 'auto' + return 'auto'; } - const stored = window.localStorage.getItem(STORAGE_THEME_KEY) + const stored = window.localStorage.getItem(STORAGE_THEME_KEY); if (stored === 'light' || stored === 'dark' || stored === 'auto') { - return stored + return stored; } - return 'auto' + return 'auto'; } function applyThemeMode(mode: ThemeMode): Theme { - const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches - const resolved = mode === 'auto' ? (prefersDark ? 'dark' : 'light') : mode + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + const resolved = mode === 'auto' ? (prefersDark ? 'dark' : 'light') : mode; - document.documentElement.classList.remove('light', 'dark') - document.documentElement.classList.add(resolved) + document.documentElement.classList.remove('light', 'dark'); + document.documentElement.classList.add(resolved); if (mode === 'auto') { - document.documentElement.removeAttribute('data-theme') + document.documentElement.removeAttribute('data-theme'); } else { - document.documentElement.setAttribute('data-theme', mode) + document.documentElement.setAttribute('data-theme', mode); } - document.documentElement.style.colorScheme = resolved - return resolved + document.documentElement.style.colorScheme = resolved; + return resolved; } diff --git a/packages/design-system/src/theming/ThemeSwitcher.spec.tsx b/packages/design-system/src/theming/ThemeSwitcher.spec.tsx index 123e9a2b..c7d4afb9 100644 --- a/packages/design-system/src/theming/ThemeSwitcher.spec.tsx +++ b/packages/design-system/src/theming/ThemeSwitcher.spec.tsx @@ -1,43 +1,43 @@ -import { render, screen } from '@testing-library/react' -import userEvent from '@testing-library/user-event' -import { describe, expect, it, vi } from 'vitest' -import { ThemeContext } from './ThemeContext' -import { ThemeSwitcher } from './ThemeSwitcher' +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { describe, expect, it, vi } from 'vitest'; +import { ThemeContext } from './ThemeContext'; +import { ThemeSwitcher } from './ThemeSwitcher'; function renderWithTheme(theme: 'light' | 'dark', setTheme = vi.fn()) { return render( - ) + ); } describe('ThemeSwitcher', () => { it('shows moon icon in light mode', () => { - renderWithTheme('light') + renderWithTheme('light'); expect( screen.getByRole('button', { name: 'Switch to dark theme' }) - ).toBeInTheDocument() - }) + ).toBeInTheDocument(); + }); it.skip('shows sun icon in dark mode', () => { - renderWithTheme('dark') + renderWithTheme('dark'); expect( screen.getByRole('button', { name: 'Switch to light theme' }) - ).toBeInTheDocument() - }) + ).toBeInTheDocument(); + }); it('calls setTheme with "light" when in dark mode', async () => { - const setTheme = vi.fn() - renderWithTheme('dark', setTheme) - await userEvent.click(screen.getByRole('button')) - expect(setTheme).toHaveBeenCalledWith('light') - }) + const setTheme = vi.fn(); + renderWithTheme('dark', setTheme); + await userEvent.click(screen.getByRole('button')); + expect(setTheme).toHaveBeenCalledWith('light'); + }); it('calls setTheme with "dark" when in light mode', async () => { - const setTheme = vi.fn() - renderWithTheme('light', setTheme) - await userEvent.click(screen.getByRole('button')) - expect(setTheme).toHaveBeenCalledWith('dark') - }) -}) + const setTheme = vi.fn(); + renderWithTheme('light', setTheme); + await userEvent.click(screen.getByRole('button')); + expect(setTheme).toHaveBeenCalledWith('dark'); + }); +}); diff --git a/packages/design-system/src/theming/ThemeSwitcher.tsx b/packages/design-system/src/theming/ThemeSwitcher.tsx index aa01b6f4..633f2a5a 100644 --- a/packages/design-system/src/theming/ThemeSwitcher.tsx +++ b/packages/design-system/src/theming/ThemeSwitcher.tsx @@ -1,16 +1,16 @@ -'use client' +'use client'; -import { MoonIcon, SunIcon } from '@phosphor-icons/react/ssr' -import { useCallback } from 'react' -import { useTheme } from './ThemeContext' +import { MoonIcon, SunIcon } from '@phosphor-icons/react/ssr'; +import { useCallback } from 'react'; +import { useTheme } from './ThemeContext'; export function ThemeSwitcher() { - const { theme, setTheme } = useTheme() - const nextTheme = theme === 'dark' ? 'light' : 'dark' + const { theme, setTheme } = useTheme(); + const nextTheme = theme === 'dark' ? 'light' : 'dark'; const switchTheme = useCallback(() => { - setTheme(nextTheme) - }, [nextTheme, setTheme]) + setTheme(nextTheme); + }, [nextTheme, setTheme]); return ( - ) + ); } diff --git a/packages/design-system/src/theming/__mocks__/ThemeProvider.tsx b/packages/design-system/src/theming/__mocks__/ThemeProvider.tsx index 72abccf9..8cc0c929 100644 --- a/packages/design-system/src/theming/__mocks__/ThemeProvider.tsx +++ b/packages/design-system/src/theming/__mocks__/ThemeProvider.tsx @@ -1,30 +1,30 @@ -'use client' -import { useEffect, useState } from 'react' -import type { Theme } from '../models' -import { ThemeContext } from '../ThemeContext' +'use client'; +import { useEffect, useState } from 'react'; +import type { Theme } from '../models'; +import { ThemeContext } from '../ThemeContext'; export function ThemeProvider({ children, startingTheme = 'dark', }: React.PropsWithChildren<{ startingTheme?: Theme }>) { - const [theme, setThemeState] = useState(startingTheme) + const [theme, setThemeState] = useState(startingTheme); // Attach theme to class useEffect(() => { - const classList = document.documentElement.classList - classList.remove('light', 'dark') + const classList = document.documentElement.classList; + classList.remove('light', 'dark'); if (theme === 'dark') { - classList.add(theme) + classList.add(theme); } - }, [theme]) + }, [theme]); const setTheme = (newTheme: Theme) => { - setThemeState(newTheme) - } + setThemeState(newTheme); + }; return ( {children} - ) + ); } diff --git a/packages/design-system/src/theming/models.ts b/packages/design-system/src/theming/models.ts index 5f20d6ba..937ef152 100644 --- a/packages/design-system/src/theming/models.ts +++ b/packages/design-system/src/theming/models.ts @@ -1 +1 @@ -export type Theme = 'light' | 'dark' +export type Theme = 'light' | 'dark'; diff --git a/packages/design-system/src/theming/theme-init-script.ts b/packages/design-system/src/theming/theme-init-script.ts index b7c8a0b4..7459351f 100644 --- a/packages/design-system/src/theming/theme-init-script.ts +++ b/packages/design-system/src/theming/theme-init-script.ts @@ -1 +1 @@ -export const THEME_INIT_SCRIPT = `(function(){try{var stored=window.localStorage.getItem('theme');var mode=(stored==='light'||stored==='dark'||stored==='auto')?stored:'auto';var prefersDark=window.matchMedia('(prefers-color-scheme: dark)').matches;var resolved=mode==='auto'?(prefersDark?'dark':'light'):mode;var root=document.documentElement;root.classList.remove('light','dark');root.classList.add(resolved);if(mode==='auto'){root.removeAttribute('data-theme')}else{root.setAttribute('data-theme',mode)}root.style.colorScheme=resolved;}catch(e){}})();` +export const THEME_INIT_SCRIPT = `(function(){try{var stored=window.localStorage.getItem('theme');var mode=(stored==='light'||stored==='dark'||stored==='auto')?stored:'auto';var prefersDark=window.matchMedia('(prefers-color-scheme: dark)').matches;var resolved=mode==='auto'?(prefersDark?'dark':'light'):mode;var root=document.documentElement;root.classList.remove('light','dark');root.classList.add(resolved);if(mode==='auto'){root.removeAttribute('data-theme')}else{root.setAttribute('data-theme',mode)}root.style.colorScheme=resolved;}catch(e){}})();`; diff --git a/packages/design-system/src/theming/theme-state.spec.ts b/packages/design-system/src/theming/theme-state.spec.ts index e4cea103..a95de143 100644 --- a/packages/design-system/src/theming/theme-state.spec.ts +++ b/packages/design-system/src/theming/theme-state.spec.ts @@ -1,51 +1,51 @@ -import { describe, expect, it } from 'vitest' -import { initialThemeState, themeReducer } from './theme-state' +import { describe, expect, it } from 'vitest'; +import { initialThemeState, themeReducer } from './theme-state'; describe('themeReducer', () => { it('setByUser sets theme and marks under user control', () => { const state = themeReducer(initialThemeState, { type: 'setByUser', theme: 'dark', - }) - expect(state.theme).toBe('dark') - expect(state.underUserControl).toBe(true) - }) + }); + expect(state.theme).toBe('dark'); + expect(state.underUserControl).toBe(true); + }); it('setBySystem updates theme when not under user control', () => { const state = themeReducer(initialThemeState, { type: 'setBySystem', theme: 'dark', - }) - expect(state.theme).toBe('dark') - expect(state.underUserControl).toBe(false) - }) + }); + expect(state.theme).toBe('dark'); + expect(state.underUserControl).toBe(false); + }); it('setBySystem is ignored when under user control', () => { const userState = themeReducer(initialThemeState, { type: 'setByUser', theme: 'dark', - }) + }); const state = themeReducer(userState, { type: 'setBySystem', theme: 'light', - }) - expect(state.theme).toBe('dark') - expect(state.underUserControl).toBe(true) - }) + }); + expect(state.theme).toBe('dark'); + expect(state.underUserControl).toBe(true); + }); it('setByDom updates theme when theme is non-null', () => { const state = themeReducer(initialThemeState, { type: 'setByDom', theme: 'dark', - }) - expect(state.theme).toBe('dark') - }) + }); + expect(state.theme).toBe('dark'); + }); it('setByDom is ignored when theme is null', () => { const state = themeReducer(initialThemeState, { type: 'setByDom', theme: null, - }) - expect(state).toBe(initialThemeState) - }) -}) + }); + expect(state).toBe(initialThemeState); + }); +}); diff --git a/packages/design-system/src/theming/theme-state.ts b/packages/design-system/src/theming/theme-state.ts index 304fa936..be7cd8f3 100644 --- a/packages/design-system/src/theming/theme-state.ts +++ b/packages/design-system/src/theming/theme-state.ts @@ -1,24 +1,24 @@ -import type { Theme } from './models' +import type { Theme } from './models'; export type ThemeState = { - theme: Theme - underUserControl: boolean -} + theme: Theme; + underUserControl: boolean; +}; export type ThemeAction = | { - type: 'setByUser' | 'setBySystem' - theme: Theme + type: 'setByUser' | 'setBySystem'; + theme: Theme; } | { - type: 'setByDom' - theme: Theme | null - } + type: 'setByDom'; + theme: Theme | null; + }; export const initialThemeState: ThemeState = { theme: 'light', underUserControl: false, -} +}; export function themeReducer(state: ThemeState, action: ThemeAction) { switch (action.type) { @@ -26,24 +26,24 @@ export function themeReducer(state: ThemeState, action: ThemeAction) { return { theme: action.theme, underUserControl: true, - } + }; case 'setBySystem': if (state.underUserControl) { - return state + return state; } else { return { ...state, theme: action.theme, - } + }; } case 'setByDom': if (action.theme !== null) { return { ...state, theme: action.theme, - } + }; } else { - return state + return state; } } } diff --git a/packages/design-system/src/typography/Text.spec.tsx b/packages/design-system/src/typography/Text.spec.tsx index 4c891e48..abf5eb67 100644 --- a/packages/design-system/src/typography/Text.spec.tsx +++ b/packages/design-system/src/typography/Text.spec.tsx @@ -1,77 +1,77 @@ -import { render, screen } from '@testing-library/react' -import { describe, expect, it } from 'vitest' -import { TYPOGRAPHY_CLASSES } from './core-type-classes' -import { Text, TextBuilder } from './Text' +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { TYPOGRAPHY_CLASSES } from './core-type-classes'; +import { Text, TextBuilder } from './Text'; describe('Text', () => { it('renders with default tag based on variant', () => { - render(Heading 1) - const el = screen.getByText('Heading 1') - expect(el).toBeInTheDocument() - expect(el.tagName).toBe('H1') - expect(el).toHaveClass(TYPOGRAPHY_CLASSES.h1) - }) + render(Heading 1); + const el = screen.getByText('Heading 1'); + expect(el).toBeInTheDocument(); + expect(el.tagName).toBe('H1'); + expect(el).toHaveClass(TYPOGRAPHY_CLASSES.h1); + }); it('renders with custom tag', () => { render( Heading 1 as span - ) - const el = screen.getByText('Heading 1 as span') - expect(el).toBeInTheDocument() - expect(el.tagName).toBe('SPAN') - expect(el).toHaveClass(TYPOGRAPHY_CLASSES.h1) - }) + ); + const el = screen.getByText('Heading 1 as span'); + expect(el).toBeInTheDocument(); + expect(el.tagName).toBe('SPAN'); + expect(el).toHaveClass(TYPOGRAPHY_CLASSES.h1); + }); it('renders body variant with paragraph tag by default', () => { - render(Body text) - const el = screen.getByText('Body text') - expect(el).toBeInTheDocument() - expect(el.tagName).toBe('P') - expect(el).toHaveClass(TYPOGRAPHY_CLASSES.body) - }) + render(Body text); + const el = screen.getByText('Body text'); + expect(el).toBeInTheDocument(); + expect(el.tagName).toBe('P'); + expect(el).toHaveClass(TYPOGRAPHY_CLASSES.body); + }); it('renders interface variant with span tag by default', () => { - render(Interface text) - const el = screen.getByText('Interface text') - expect(el).toBeInTheDocument() - expect(el.tagName).toBe('SPAN') - expect(el).toHaveClass(TYPOGRAPHY_CLASSES.interface) - }) + render(Interface text); + const el = screen.getByText('Interface text'); + expect(el).toBeInTheDocument(); + expect(el.tagName).toBe('SPAN'); + expect(el).toHaveClass(TYPOGRAPHY_CLASSES.interface); + }); it('applies custom className', () => { render( Heading 1 - ) - const el = screen.getByText('Heading 1') - expect(el).toHaveClass('custom-class') - }) -}) + ); + const el = screen.getByText('Heading 1'); + expect(el).toHaveClass('custom-class'); + }); +}); describe('TextBuilder', () => { it('creates a component with the specified variant', () => { - const H1Text = TextBuilder('h1') - render(Heading 1) - const el = screen.getByText('Heading 1') - expect(el).toBeInTheDocument() - expect(el.tagName).toBe('H1') - expect(el).toHaveClass(TYPOGRAPHY_CLASSES.h1) - }) + const H1Text = TextBuilder('h1'); + render(Heading 1); + const el = screen.getByText('Heading 1'); + expect(el).toBeInTheDocument(); + expect(el.tagName).toBe('H1'); + expect(el).toHaveClass(TYPOGRAPHY_CLASSES.h1); + }); it('allows overriding the tag', () => { - const H1Text = TextBuilder('h1') - render(Heading 1 as span) - const el = screen.getByText('Heading 1 as span') - expect(el).toBeInTheDocument() - expect(el.tagName).toBe('SPAN') - expect(el).toHaveClass(TYPOGRAPHY_CLASSES.h1) - }) + const H1Text = TextBuilder('h1'); + render(Heading 1 as span); + const el = screen.getByText('Heading 1 as span'); + expect(el).toBeInTheDocument(); + expect(el.tagName).toBe('SPAN'); + expect(el).toHaveClass(TYPOGRAPHY_CLASSES.h1); + }); it('exposes the typography classes', () => { - const H1Text = TextBuilder('h1') - expect(H1Text.classes).toBe(TYPOGRAPHY_CLASSES.h1) - }) -}) + const H1Text = TextBuilder('h1'); + expect(H1Text.classes).toBe(TYPOGRAPHY_CLASSES.h1); + }); +}); diff --git a/packages/design-system/src/typography/Text.tsx b/packages/design-system/src/typography/Text.tsx index 79ab120c..a05e2b7a 100644 --- a/packages/design-system/src/typography/Text.tsx +++ b/packages/design-system/src/typography/Text.tsx @@ -1,6 +1,9 @@ -import { cn } from '@ageorgedev/toolbelt/cn' -import { test } from 'ramda' -import { TYPOGRAPHY_CLASSES, type TypographyVariant } from './core-type-classes' +import { cn } from '@ageorgedev/toolbelt/cn'; +import { test } from 'ramda'; +import { + TYPOGRAPHY_CLASSES, + type TypographyVariant, +} from './core-type-classes'; export type TextTag = | 'h1' @@ -14,12 +17,12 @@ export type TextTag = | 'em' | 'b' | 'small' - | 'li' + | 'li'; type TextProps = { - variant: TypographyVariant - as?: TextTag -} + variant: TypographyVariant; + as?: TextTag; +}; export function Text({ variant, @@ -28,15 +31,15 @@ export function Text({ children, ...otherProps }: React.PropsWithChildren & React.HTMLAttributes) { - const Tag = tag ?? getDefaultTag(variant) + const Tag = tag ?? getDefaultTag(variant); - const classes = cn(TYPOGRAPHY_CLASSES[variant], className) + const classes = cn(TYPOGRAPHY_CLASSES[variant], className); return ( {children} - ) + ); } export function TextBuilder(variant: TypographyVariant) { @@ -48,33 +51,33 @@ export function TextBuilder(variant: TypographyVariant) { {children} - ) + ); } - Component.classes = TYPOGRAPHY_CLASSES[variant] - return Component + Component.classes = TYPOGRAPHY_CLASSES[variant]; + return Component; } function getDefaultTag(variant: TypographyVariant): TextTag { - const initial = defaultTag[variant] + const initial = defaultTag[variant]; if (initial) { - return initial + return initial; } if (isBodyVariant(variant) || isPBodyVariant(variant)) { - return 'p' + return 'p'; } if (isInterfaceVariant(variant)) { - return 'span' + return 'span'; } - return 'span' + return 'span'; } -const isBodyVariant = test(/^body.*$/) -const isPBodyVariant = test(/^p-body.*$/) -const isInterfaceVariant = test(/^interface.*$/) +const isBodyVariant = test(/^body.*$/); +const isPBodyVariant = test(/^p-body.*$/); +const isInterfaceVariant = test(/^interface.*$/); const defaultTag: Partial> = { h1: 'h1', @@ -83,4 +86,4 @@ const defaultTag: Partial> = { h4: 'h4', h5: 'h5', h6: 'h6', -} +}; diff --git a/packages/design-system/src/typography/core-type-classes.ts b/packages/design-system/src/typography/core-type-classes.ts index 69d1684c..f771f08b 100644 --- a/packages/design-system/src/typography/core-type-classes.ts +++ b/packages/design-system/src/typography/core-type-classes.ts @@ -1,10 +1,10 @@ -import { mapObjIndexed } from 'ramda' +import { mapObjIndexed } from 'ramda'; -const headingFont = 'font-heading' -const bodyFont = 'font-body' -const interfaceFont = 'font-interface' +const headingFont = 'font-heading'; +const bodyFont = 'font-body'; +const interfaceFont = 'font-interface'; -const withFont = (font: string) => (v: string) => `${font} ${v}` +const withFont = (font: string) => (v: string) => `${font} ${v}`; const headingClasses = mapObjIndexed(withFont(headingFont), { h1: 'text-4xl tablet:text-5xl desktop:text-6xl', @@ -13,7 +13,7 @@ const headingClasses = mapObjIndexed(withFont(headingFont), { h4: 'text-xl tablet:text-2xl desktop:text-3xl', h5: 'text-lg tablet:text-xl desktop:text-2xl', h6: 'text-md tablet:text-lg desktop:text-xl', -}) +}); const bodyClasses = mapObjIndexed(withFont(bodyFont), { 'body-xl': 'text-xl', @@ -22,7 +22,7 @@ const bodyClasses = mapObjIndexed(withFont(bodyFont), { body: 'text-base', 'body-sm': 'text-sm', 'body-xs': 'text-xs', -}) +}); const presentationBodyClasses = mapObjIndexed(withFont(bodyFont), { 'p-body-lg': 'text-4xl', @@ -30,7 +30,7 @@ const presentationBodyClasses = mapObjIndexed(withFont(bodyFont), { 'p-body': 'text-2xl', 'p-body-sm': 'text-xl', 'p-body-xs': 'text-lg', -}) +}); // TODO: cn will remove leading-none if added before the font-size class. Refactor this file to work correctly with cn const interfaceClasses = mapObjIndexed(withFont(interfaceFont), { @@ -40,13 +40,13 @@ const interfaceClasses = mapObjIndexed(withFont(interfaceFont), { 'interface-md': 'text-md leading-none', interface: 'leading-none', 'interface-sm': 'text-sm leading-none', -}) +}); export const TYPOGRAPHY_CLASSES = { ...headingClasses, ...bodyClasses, ...interfaceClasses, ...presentationBodyClasses, -} +}; -export type TypographyVariant = keyof typeof TYPOGRAPHY_CLASSES +export type TypographyVariant = keyof typeof TYPOGRAPHY_CLASSES; diff --git a/packages/design-system/src/typography/typography-components.spec.tsx b/packages/design-system/src/typography/typography-components.spec.tsx index 996e6b05..11843a70 100644 --- a/packages/design-system/src/typography/typography-components.spec.tsx +++ b/packages/design-system/src/typography/typography-components.spec.tsx @@ -1,5 +1,5 @@ -import { render } from '@testing-library/react' -import { describe, expect, it } from 'vitest' +import { render } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; import { Body, BodyLg, @@ -19,7 +19,7 @@ import { InterfaceMd, InterfaceSm, InterfaceXl, -} from './typography-components' +} from './typography-components'; describe('Typography', () => { it('should add add correct styling for heading types', async () => { @@ -32,10 +32,10 @@ describe('Typography', () => { Heading 5 Heading 6 - ) + ); - expect(baseElement).toMatchSnapshot() - }) + expect(baseElement).toMatchSnapshot(); + }); it('should add add correct styling for body types', async () => { const { baseElement } = await render( @@ -47,10 +47,10 @@ describe('Typography', () => { Body Sm Body Xs - ) + ); - expect(baseElement).toMatchSnapshot() - }) + expect(baseElement).toMatchSnapshot(); + }); it('should add add correct styling for interface types', async () => { const { baseElement } = await render( @@ -62,8 +62,8 @@ describe('Typography', () => { Interface Interface Sm - ) + ); - expect(baseElement).toMatchSnapshot() - }) -}) + expect(baseElement).toMatchSnapshot(); + }); +}); diff --git a/packages/design-system/src/typography/typography-components.tsx b/packages/design-system/src/typography/typography-components.tsx index e6e28426..b469a1c2 100644 --- a/packages/design-system/src/typography/typography-components.tsx +++ b/packages/design-system/src/typography/typography-components.tsx @@ -1,28 +1,28 @@ -import { TextBuilder } from './Text' +import { TextBuilder } from './Text'; -export const Heading1 = TextBuilder('h1') -export const Heading2 = TextBuilder('h2') -export const Heading3 = TextBuilder('h3') -export const Heading4 = TextBuilder('h4') -export const Heading5 = TextBuilder('h5') -export const Heading6 = TextBuilder('h6') +export const Heading1 = TextBuilder('h1'); +export const Heading2 = TextBuilder('h2'); +export const Heading3 = TextBuilder('h3'); +export const Heading4 = TextBuilder('h4'); +export const Heading5 = TextBuilder('h5'); +export const Heading6 = TextBuilder('h6'); -export const BodyXl = TextBuilder('body-xl') -export const BodyLg = TextBuilder('body-lg') -export const BodyMd = TextBuilder('body-md') -export const Body = TextBuilder('body') -export const BodySm = TextBuilder('body-sm') -export const BodyXs = TextBuilder('body-xs') +export const BodyXl = TextBuilder('body-xl'); +export const BodyLg = TextBuilder('body-lg'); +export const BodyMd = TextBuilder('body-md'); +export const Body = TextBuilder('body'); +export const BodySm = TextBuilder('body-sm'); +export const BodyXs = TextBuilder('body-xs'); -export const Interface2Xl = TextBuilder('interface-2xl') -export const InterfaceXl = TextBuilder('interface-xl') -export const InterfaceLg = TextBuilder('interface-lg') -export const InterfaceMd = TextBuilder('interface-md') -export const Interface = TextBuilder('interface') -export const InterfaceSm = TextBuilder('interface-sm') +export const Interface2Xl = TextBuilder('interface-2xl'); +export const InterfaceXl = TextBuilder('interface-xl'); +export const InterfaceLg = TextBuilder('interface-lg'); +export const InterfaceMd = TextBuilder('interface-md'); +export const Interface = TextBuilder('interface'); +export const InterfaceSm = TextBuilder('interface-sm'); -export const PBodyLg = TextBuilder('p-body-lg') -export const PBodyMd = TextBuilder('p-body-md') -export const PBody = TextBuilder('p-body') -export const PBodySm = TextBuilder('p-body-sm') -export const PBodyXs = TextBuilder('p-body-xs') +export const PBodyLg = TextBuilder('p-body-lg'); +export const PBodyMd = TextBuilder('p-body-md'); +export const PBody = TextBuilder('p-body'); +export const PBodySm = TextBuilder('p-body-sm'); +export const PBodyXs = TextBuilder('p-body-xs'); diff --git a/packages/design-system/src/typography/typography.stories.tsx b/packages/design-system/src/typography/typography.stories.tsx index 9b139e38..50f69b18 100644 --- a/packages/design-system/src/typography/typography.stories.tsx +++ b/packages/design-system/src/typography/typography.stories.tsx @@ -1,5 +1,5 @@ -import type { Meta, StoryObj } from '@storybook/react-vite' -import { Text } from './Text' +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { Text } from './Text'; import { Body, BodyLg, @@ -24,7 +24,7 @@ import { PBodyMd, PBodySm, PBodyXs, -} from './typography-components' +} from './typography-components'; export default { title: 'Atoms/Typography', @@ -63,9 +63,9 @@ export default { options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'span', 'div'], }, }, -} satisfies Meta +} satisfies Meta; -type Story = StoryObj +type Story = StoryObj; export const Headings: Story = { render: () => ( @@ -78,7 +78,7 @@ export const Headings: Story = { Heading Level 6 ), -} +}; export const BodyText: Story = { render: () => ( @@ -91,7 +91,7 @@ export const BodyText: Story = { Body Extra Small Text ), -} +}; export const InterfaceText: Story = { render: () => ( @@ -116,7 +116,7 @@ export const InterfaceText: Story = { ), -} +}; export const ParagraphText: Story = { render: () => ( @@ -147,7 +147,7 @@ export const ParagraphText: Story = { ), -} +}; export const TextWithCustomTag: Story = { render: () => ( @@ -169,7 +169,7 @@ export const TextWithCustomTag: Story = { ), -} +}; export const InteractiveExample: Story = { args: { @@ -177,4 +177,4 @@ export const InteractiveExample: Story = { children: 'This is an interactive example. Try changing the variant and tag!', }, -} +}; diff --git a/packages/design-system/src/ui/button.stories.tsx b/packages/design-system/src/ui/button.stories.tsx index 60616b59..b4b2ba8d 100644 --- a/packages/design-system/src/ui/button.stories.tsx +++ b/packages/design-system/src/ui/button.stories.tsx @@ -1,12 +1,12 @@ -import type { Meta, StoryObj } from '@storybook/react-vite' -import { Button } from './button' +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { Button } from './button'; export default { title: 'UI/Button', component: Button, -} satisfies Meta +} satisfies Meta; -type Story = StoryObj +type Story = StoryObj; export const Default: Story = { args: { @@ -14,7 +14,7 @@ export const Default: Story = { variant: 'default', size: 'default', }, -} +}; export const Destructive: Story = { args: { @@ -22,7 +22,7 @@ export const Destructive: Story = { variant: 'destructive', size: 'default', }, -} +}; export const Outline: Story = { args: { @@ -30,7 +30,7 @@ export const Outline: Story = { variant: 'outline', size: 'default', }, -} +}; export const Secondary: Story = { args: { @@ -38,7 +38,7 @@ export const Secondary: Story = { variant: 'secondary', size: 'default', }, -} +}; export const Ghost: Story = { args: { @@ -46,7 +46,7 @@ export const Ghost: Story = { variant: 'ghost', size: 'default', }, -} +}; export const Link: Story = { args: { @@ -54,7 +54,7 @@ export const Link: Story = { variant: 'link', size: 'default', }, -} +}; export const SizeSmall: Story = { args: { @@ -62,7 +62,7 @@ export const SizeSmall: Story = { variant: 'default', size: 'sm', }, -} +}; export const SizeLarge: Story = { args: { @@ -70,7 +70,7 @@ export const SizeLarge: Story = { variant: 'default', size: 'lg', }, -} +}; // TODO export const SizeIcon: Story = { @@ -79,4 +79,4 @@ export const SizeIcon: Story = { variant: 'default', size: 'icon', }, -} +}; diff --git a/packages/design-system/src/ui/button.tsx b/packages/design-system/src/ui/button.tsx index c0c4d2d3..821195bf 100644 --- a/packages/design-system/src/ui/button.tsx +++ b/packages/design-system/src/ui/button.tsx @@ -1,7 +1,7 @@ -import { cn } from '@ageorgedev/toolbelt/cn' -import { Slot } from '@radix-ui/react-slot' -import { cva, type VariantProps } from 'class-variance-authority' -import { Body, BodyLg, BodySm } from '../typography/typography-components' +import { cn } from '@ageorgedev/toolbelt/cn'; +import { Slot } from '@radix-ui/react-slot'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { Body, BodyLg, BodySm } from '../typography/typography-components'; // TODO: I think it would be better to separate color and style? we may need, for example, a link styled destructive const buttonVariants = cva( @@ -33,7 +33,7 @@ const buttonVariants = cva( size: 'default', }, } -) +); function Button({ className, @@ -43,9 +43,9 @@ function Button({ ...props }: React.ComponentProps<'button'> & VariantProps & { - asChild?: boolean + asChild?: boolean; }) { - const Comp = asChild ? Slot : 'button' + const Comp = asChild ? Slot : 'button'; return ( - ) + ); } -export { Button, buttonVariants } +export { Button, buttonVariants }; diff --git a/packages/design-system/vitest.config.ts b/packages/design-system/vitest.config.ts index 9b6bd972..c641e500 100644 --- a/packages/design-system/vitest.config.ts +++ b/packages/design-system/vitest.config.ts @@ -1,5 +1,5 @@ -import react from '@vitejs/plugin-react' -import { defineConfig } from 'vitest/config' +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vitest/config'; export default defineConfig({ plugins: [react()], @@ -8,4 +8,4 @@ export default defineConfig({ setupFiles: ['@ageorgedev/testing-config/react-jsdom-test-setup.ts'], include: ['src/**/*.spec.{ts,tsx}'], }, -}) +}); diff --git a/packages/dnd-character-sheet/src/characters/example-wizard.data.ts b/packages/dnd-character-sheet/src/characters/example-wizard.data.ts index 9fd6e572..5e8a82c5 100644 --- a/packages/dnd-character-sheet/src/characters/example-wizard.data.ts +++ b/packages/dnd-character-sheet/src/characters/example-wizard.data.ts @@ -1,6 +1,6 @@ -import { Ability } from '../lib/models/abilities' -import type { Character } from '../lib/models/character' -import { Skill } from '../lib/models/skills' +import { Ability } from '../lib/models/abilities'; +import type { Character } from '../lib/models/character'; +import { Skill } from '../lib/models/skills'; export const exampleWizard: Character = { name: 'Seraphina Ashveil', @@ -252,4 +252,4 @@ export const exampleWizard: Character = { }, ], }, -} +}; diff --git a/packages/dnd-character-sheet/src/characters/example.tsx b/packages/dnd-character-sheet/src/characters/example.tsx index 56f323e0..a280c67a 100644 --- a/packages/dnd-character-sheet/src/characters/example.tsx +++ b/packages/dnd-character-sheet/src/characters/example.tsx @@ -1,6 +1,6 @@ -import { StandardCharacterSheet } from '../components/StandardCharacterSheet/StandardCharacterSheet' -import { exampleWizard } from './example-wizard.data' +import { StandardCharacterSheet } from '../components/StandardCharacterSheet/StandardCharacterSheet'; +import { exampleWizard } from './example-wizard.data'; export function ExampleSheet() { - return + return ; } diff --git a/packages/dnd-character-sheet/src/components/AbilityBox/AbilityBox.tsx b/packages/dnd-character-sheet/src/components/AbilityBox/AbilityBox.tsx index 50c49363..a001911f 100644 --- a/packages/dnd-character-sheet/src/components/AbilityBox/AbilityBox.tsx +++ b/packages/dnd-character-sheet/src/components/AbilityBox/AbilityBox.tsx @@ -1,36 +1,36 @@ -import { ABILITY_DETAILS, type Ability } from '../../lib/models/abilities' -import { AbilitySkillGrouping } from '../../lib/models/skills' -import { useCharacter } from '../CharacterSheet' -import { BigNumber } from '../layout/BigNumber' -import type { CheckedState } from '../layout/checkables' -import { CircleCheck } from '../layout/checkables' -import { Panel } from '../layout/Panel' -import { PanelTitle } from '../layout/PanelTitle' +import { ABILITY_DETAILS, type Ability } from '../../lib/models/abilities'; +import { AbilitySkillGrouping } from '../../lib/models/skills'; +import { useCharacter } from '../CharacterSheet'; +import { BigNumber } from '../layout/BigNumber'; +import type { CheckedState } from '../layout/checkables'; +import { CircleCheck } from '../layout/checkables'; +import { Panel } from '../layout/Panel'; +import { PanelTitle } from '../layout/PanelTitle'; function formatMod(mod: number): string { - return mod >= 0 ? `+${mod}` : `${mod}` + return mod >= 0 ? `+${mod}` : `${mod}`; } function proficiencyState( isProficient: boolean, hasExpertise: boolean ): CheckedState { - if (hasExpertise) return 'special' - if (isProficient) return true - return false + if (hasExpertise) return 'special'; + if (isProficient) return true; + return false; } interface AbilityBoxProps { - ability: Ability + ability: Ability; } export function AbilityBox({ ability }: AbilityBoxProps) { - const { character, derived } = useCharacter() - const score = character.abilities[ability] - const mod = derived.abilityModifiers[ability] - const savingThrow = derived.savingThrows[ability] - const isSaveProficient = character.savingThrowProficiencies.includes(ability) - const skills = AbilitySkillGrouping[ability] + const { character, derived } = useCharacter(); + const score = character.abilities[ability]; + const mod = derived.abilityModifiers[ability]; + const savingThrow = derived.savingThrows[ability]; + const isSaveProficient = character.savingThrowProficiencies.includes(ability); + const skills = AbilitySkillGrouping[ability]; return ( {skills.map((skill) => { - const isProficient = character.skillProficiencies.includes(skill) - const hasExpertise = character.skillExpertise.includes(skill) - const total = derived.skills[skill] + const isProficient = character.skillProficiencies.includes(skill); + const hasExpertise = character.skillExpertise.includes(skill); + const total = derived.skills[skill]; return ( {skill} - ) + ); })} )} - ) + ); } function SkillGrid({ children }: React.PropsWithChildren) { return (
{children}
- ) + ); } function SkillRow({ @@ -86,8 +86,8 @@ function SkillRow({ modifier, children, }: React.PropsWithChildren<{ - checkedState: CheckedState - modifier: number + checkedState: CheckedState; + modifier: number; }>) { return ( <> @@ -95,5 +95,5 @@ function SkillRow({ {formatMod(modifier)} {children} - ) + ); } diff --git a/packages/dnd-character-sheet/src/components/AttackList/AttackList.tsx b/packages/dnd-character-sheet/src/components/AttackList/AttackList.tsx index 2848419d..36c4a14f 100644 --- a/packages/dnd-character-sheet/src/components/AttackList/AttackList.tsx +++ b/packages/dnd-character-sheet/src/components/AttackList/AttackList.tsx @@ -1,15 +1,15 @@ -import { ABILITY_DETAILS } from '../../lib/models/abilities' -import type { Attack, AttackDamage } from '../../lib/models/attacks' -import type { DerivedStats } from '../../lib/models/derived-stats' -import { useCharacter } from '../CharacterSheet' -import { DiamondCheck } from '../layout/checkables' -import { Panel } from '../layout/Panel' -import { PanelTitle } from '../layout/PanelTitle' +import { ABILITY_DETAILS } from '../../lib/models/abilities'; +import type { Attack, AttackDamage } from '../../lib/models/attacks'; +import type { DerivedStats } from '../../lib/models/derived-stats'; +import { useCharacter } from '../CharacterSheet'; +import { DiamondCheck } from '../layout/checkables'; +import { Panel } from '../layout/Panel'; +import { PanelTitle } from '../layout/PanelTitle'; export function AttackList() { - const { character, derived } = useCharacter() + const { character, derived } = useCharacter(); - if (character.attacks.length === 0) return null + if (character.attacks.length === 0) return null; return ( @@ -30,7 +30,7 @@ export function AttackList() { const { bonusText, damageBonus } = calcAttackBonus( attack, derived - ) + ); return ( {attack.notes ?? '—'} - ) + ); })} - ) + ); } function Th({ children }: { children: React.ReactNode }) { @@ -65,17 +65,17 @@ function Th({ children }: { children: React.ReactNode }) { {children} - ) + ); } function Td({ children, className = '', }: { - children: React.ReactNode - className?: string + children: React.ReactNode; + className?: string; }) { - return {children} + return {children}; } function calcAttackBonus( @@ -84,40 +84,40 @@ function calcAttackBonus( ): { bonusText: string; damageBonus: number } { switch (attack.kind) { case 'weapon': { - const abilityMod = derived.abilityModifiers[attack.ability] - const profBonus = attack.notProficient ? 0 : derived.proficiencyBonus - const base = abilityMod + profBonus + (attack.attackBonusMod ?? 0) + const abilityMod = derived.abilityModifiers[attack.ability]; + const profBonus = attack.notProficient ? 0 : derived.proficiencyBonus; + const base = abilityMod + profBonus + (attack.attackBonusMod ?? 0); return { bonusText: formatBonus(base), damageBonus: abilityMod + (attack.attackBonusMod ?? 0), - } + }; } case 'spell-with-attack': { - let spellAttackBonus = derived.spellAttackBonus ?? 0 + let spellAttackBonus = derived.spellAttackBonus ?? 0; if (attack.ability) { - const abilityMod = derived.abilityModifiers[attack.ability] - const profBonus = attack.notProficient ? 0 : derived.proficiencyBonus - spellAttackBonus = abilityMod + profBonus + const abilityMod = derived.abilityModifiers[attack.ability]; + const profBonus = attack.notProficient ? 0 : derived.proficiencyBonus; + spellAttackBonus = abilityMod + profBonus; } - const totalAttackBonus = spellAttackBonus + (attack.attackBonusMod ?? 0) + const totalAttackBonus = spellAttackBonus + (attack.attackBonusMod ?? 0); return { bonusText: formatBonus(totalAttackBonus), damageBonus: 0 + (attack.attackBonusMod ?? 0), - } + }; } case 'spell-with-save': { - let spellSaveDC = derived.spellSaveDC ?? 0 + let spellSaveDC = derived.spellSaveDC ?? 0; if (attack.ability) { - const abilityMod = derived.abilityModifiers[attack.ability] - const profBonus = attack.notProficient ? 0 : derived.proficiencyBonus - spellSaveDC = 8 + abilityMod + profBonus + const abilityMod = derived.abilityModifiers[attack.ability]; + const profBonus = attack.notProficient ? 0 : derived.proficiencyBonus; + spellSaveDC = 8 + abilityMod + profBonus; } return { bonusText: `${ABILITY_DETAILS[attack.saveAbility].shortName} save, DC ${spellSaveDC}`, damageBonus: 0 + (attack.attackBonusMod ?? 0), - } + }; } } } @@ -125,12 +125,12 @@ function calcAttackBonus( function formatDamage(entries: AttackDamage[], mod: number): string { return entries .map((entry) => { - const suffix = entry.disableModifier ? '' : formatBonus(mod) - return `${entry.dice}${suffix} ${entry.type}` + const suffix = entry.disableModifier ? '' : formatBonus(mod); + return `${entry.dice}${suffix} ${entry.type}`; }) - .join(' + ') + .join(' + '); } function formatBonus(n: number): string { - return n === 0 ? '' : n > 0 ? `+${n}` : `${n}` + return n === 0 ? '' : n > 0 ? `+${n}` : `${n}`; } diff --git a/packages/dnd-character-sheet/src/components/BackstoryBlock/BackstoryBlock.tsx b/packages/dnd-character-sheet/src/components/BackstoryBlock/BackstoryBlock.tsx index 1b13a722..7db2f8ff 100644 --- a/packages/dnd-character-sheet/src/components/BackstoryBlock/BackstoryBlock.tsx +++ b/packages/dnd-character-sheet/src/components/BackstoryBlock/BackstoryBlock.tsx @@ -1,16 +1,16 @@ -import { useCharacter } from '../CharacterSheet' -import { Panel } from '../layout/Panel' -import { PanelTitle } from '../layout/PanelTitle' +import { useCharacter } from '../CharacterSheet'; +import { Panel } from '../layout/Panel'; +import { PanelTitle } from '../layout/PanelTitle'; const PERSONALITY_FIELDS = [ { key: 'traits', label: 'Personality Traits' }, { key: 'ideals', label: 'Ideals' }, { key: 'bonds', label: 'Bonds' }, { key: 'flaws', label: 'Flaws' }, -] as const +] as const; export function BackstoryBlock() { - const { character } = useCharacter() + const { character } = useCharacter(); return ( @@ -66,5 +66,5 @@ export function BackstoryBlock() { )} - ) + ); } diff --git a/packages/dnd-character-sheet/src/components/CharacterSheet.tsx b/packages/dnd-character-sheet/src/components/CharacterSheet.tsx index 82cbb7b9..0b098b06 100644 --- a/packages/dnd-character-sheet/src/components/CharacterSheet.tsx +++ b/packages/dnd-character-sheet/src/components/CharacterSheet.tsx @@ -1,39 +1,39 @@ -import { createContext, useContext, useMemo } from 'react' -import { calculateStats } from '../lib/calculate-derived-stats' -import type { Character } from '../lib/models/character' -import type { DerivedStats } from '../lib/models/derived-stats' +import { createContext, useContext, useMemo } from 'react'; +import { calculateStats } from '../lib/calculate-derived-stats'; +import type { Character } from '../lib/models/character'; +import type { DerivedStats } from '../lib/models/derived-stats'; interface CharacterContextValue { - character: Character - derived: DerivedStats + character: Character; + derived: DerivedStats; } -const CharacterContext = createContext(null) +const CharacterContext = createContext(null); export function useCharacter(): CharacterContextValue { - const ctx = useContext(CharacterContext) + const ctx = useContext(CharacterContext); if (!ctx) { throw new Error( 'useCharacter must be used inside a provider' - ) + ); } - return ctx + return ctx; } interface CharacterSheetProps { - data: Character - children: React.ReactNode + data: Character; + children: React.ReactNode; } export function CharacterSheet({ data, children }: CharacterSheetProps) { const value = useMemo( () => ({ character: data, derived: calculateStats(data) }), [data] - ) + ); return ( {children} - ) + ); } diff --git a/packages/dnd-character-sheet/src/components/ClassFeatures/ClassFeatures.tsx b/packages/dnd-character-sheet/src/components/ClassFeatures/ClassFeatures.tsx index 6cc0b1ab..67e7a912 100644 --- a/packages/dnd-character-sheet/src/components/ClassFeatures/ClassFeatures.tsx +++ b/packages/dnd-character-sheet/src/components/ClassFeatures/ClassFeatures.tsx @@ -1,9 +1,9 @@ -import { useCharacter } from '../CharacterSheet' -import { Panel } from '../layout/Panel' -import { PanelTitle } from '../layout/PanelTitle' +import { useCharacter } from '../CharacterSheet'; +import { Panel } from '../layout/Panel'; +import { PanelTitle } from '../layout/PanelTitle'; export function ClassFeatures() { - const { character } = useCharacter() + const { character } = useCharacter(); return ( @@ -27,5 +27,5 @@ export function ClassFeatures() { ))} - ) + ); } diff --git a/packages/dnd-character-sheet/src/components/CombatRow/CombatRow.tsx b/packages/dnd-character-sheet/src/components/CombatRow/CombatRow.tsx index dd1a7087..23303f4b 100644 --- a/packages/dnd-character-sheet/src/components/CombatRow/CombatRow.tsx +++ b/packages/dnd-character-sheet/src/components/CombatRow/CombatRow.tsx @@ -1,28 +1,28 @@ -import { useCharacter } from '../CharacterSheet' -import { Panel } from '../layout/Panel' -import { PanelTitle } from '../layout/PanelTitle' +import { useCharacter } from '../CharacterSheet'; +import { Panel } from '../layout/Panel'; +import { PanelTitle } from '../layout/PanelTitle'; function formatMod(mod: number): string { - return mod >= 0 ? `+${mod}` : `${mod}` + return mod >= 0 ? `+${mod}` : `${mod}`; } function StatCell({ label, children, }: { - label: string - children: React.ReactNode + label: string; + children: React.ReactNode; }) { return ( {label} {children} - ) + ); } export function CombatRow() { - const { character, derived } = useCharacter() + const { character, derived } = useCharacter(); return (
@@ -33,5 +33,5 @@ export function CombatRow() { {derived.passivePerception}
- ) + ); } diff --git a/packages/dnd-character-sheet/src/components/EquipmentBlock/EquipmentBlock.tsx b/packages/dnd-character-sheet/src/components/EquipmentBlock/EquipmentBlock.tsx index a303338c..f7c28dd2 100644 --- a/packages/dnd-character-sheet/src/components/EquipmentBlock/EquipmentBlock.tsx +++ b/packages/dnd-character-sheet/src/components/EquipmentBlock/EquipmentBlock.tsx @@ -1,6 +1,6 @@ -import { useCharacter } from '../CharacterSheet' -import { Panel } from '../layout/Panel' -import { PanelTitle } from '../layout/PanelTitle' +import { useCharacter } from '../CharacterSheet'; +import { Panel } from '../layout/Panel'; +import { PanelTitle } from '../layout/PanelTitle'; const CURRENCY = [ { key: 'pp', label: 'PP' }, @@ -8,10 +8,10 @@ const CURRENCY = [ { key: 'ep', label: 'EP' }, { key: 'sp', label: 'SP' }, { key: 'cp', label: 'CP' }, -] as const +] as const; export function EquipmentBlock() { - const { character } = useCharacter() + const { character } = useCharacter(); return ( @@ -47,5 +47,5 @@ export function EquipmentBlock() { ))} - ) + ); } diff --git a/packages/dnd-character-sheet/src/components/EquipmentTraining/EquipmentTraining.tsx b/packages/dnd-character-sheet/src/components/EquipmentTraining/EquipmentTraining.tsx index 576afca7..2fd03797 100644 --- a/packages/dnd-character-sheet/src/components/EquipmentTraining/EquipmentTraining.tsx +++ b/packages/dnd-character-sheet/src/components/EquipmentTraining/EquipmentTraining.tsx @@ -1,18 +1,18 @@ -import { cn } from '@ageorgedev/toolbelt/cn' -import { useCharacter } from '../CharacterSheet' -import { DiamondCheck } from '../layout/checkables' -import { Panel } from '../layout/Panel' -import { PanelTitle } from '../layout/PanelTitle' +import { cn } from '@ageorgedev/toolbelt/cn'; +import { useCharacter } from '../CharacterSheet'; +import { DiamondCheck } from '../layout/checkables'; +import { Panel } from '../layout/Panel'; +import { PanelTitle } from '../layout/PanelTitle'; const ARMOR_TYPES: { key: string; label: string }[] = [ { key: 'Light armor', label: 'Light' }, { key: 'Medium armor', label: 'Medium' }, { key: 'Heavy armor', label: 'Heavy' }, { key: 'Shields', label: 'Shields' }, -] +]; export function EquipmentTraining() { - const { character } = useCharacter() + const { character } = useCharacter(); return ( @@ -22,13 +22,13 @@ export function EquipmentTraining() {
Armor Training {ARMOR_TYPES.map(({ key, label }) => { - const trained = character.armorProficiencies.includes(key) + const trained = character.armorProficiencies.includes(key); return (
{label}
- ) + ); })}
@@ -42,7 +42,7 @@ export function EquipmentTraining() {

{character.toolProficiencies.join(', ')}

- ) + ); } function SectionTitle({ @@ -57,5 +57,5 @@ function SectionTitle({ className )} /> - ) + ); } diff --git a/packages/dnd-character-sheet/src/components/Feats/Feats.tsx b/packages/dnd-character-sheet/src/components/Feats/Feats.tsx index 180f9c26..b210963b 100644 --- a/packages/dnd-character-sheet/src/components/Feats/Feats.tsx +++ b/packages/dnd-character-sheet/src/components/Feats/Feats.tsx @@ -1,10 +1,10 @@ -import { useCharacter } from '../CharacterSheet' -import { Panel } from '../layout/Panel' -import { PanelTitle } from '../layout/PanelTitle' +import { useCharacter } from '../CharacterSheet'; +import { Panel } from '../layout/Panel'; +import { PanelTitle } from '../layout/PanelTitle'; export function Feats() { - const { character } = useCharacter() - const feats = character.feats ?? [] + const { character } = useCharacter(); + const feats = character.feats ?? []; return ( @@ -34,5 +34,5 @@ export function Feats() { )} - ) + ); } diff --git a/packages/dnd-character-sheet/src/components/HeroicInspiration/HeroicInspiration.tsx b/packages/dnd-character-sheet/src/components/HeroicInspiration/HeroicInspiration.tsx index ccde51f3..44e358d0 100644 --- a/packages/dnd-character-sheet/src/components/HeroicInspiration/HeroicInspiration.tsx +++ b/packages/dnd-character-sheet/src/components/HeroicInspiration/HeroicInspiration.tsx @@ -1,6 +1,6 @@ -import { DiamondCheck } from '../layout/checkables' -import { Panel } from '../layout/Panel' -import { PanelTitle } from '../layout/PanelTitle' +import { DiamondCheck } from '../layout/checkables'; +import { Panel } from '../layout/Panel'; +import { PanelTitle } from '../layout/PanelTitle'; export function HeroicInspiration() { return ( @@ -14,5 +14,5 @@ export function HeroicInspiration() { - ) + ); } diff --git a/packages/dnd-character-sheet/src/components/ProficiencyBlock/ProficiencyBlock.tsx b/packages/dnd-character-sheet/src/components/ProficiencyBlock/ProficiencyBlock.tsx index fa9e87a8..ed9d20a2 100644 --- a/packages/dnd-character-sheet/src/components/ProficiencyBlock/ProficiencyBlock.tsx +++ b/packages/dnd-character-sheet/src/components/ProficiencyBlock/ProficiencyBlock.tsx @@ -1,10 +1,10 @@ -import { useCharacter } from '../CharacterSheet' -import { BigNumber } from '../layout/BigNumber' -import { Panel } from '../layout/Panel' -import { PanelTitle } from '../layout/PanelTitle' +import { useCharacter } from '../CharacterSheet'; +import { BigNumber } from '../layout/BigNumber'; +import { Panel } from '../layout/Panel'; +import { PanelTitle } from '../layout/PanelTitle'; export function ProficiencyBlock() { - const { derived } = useCharacter() + const { derived } = useCharacter(); return ( +{derived.proficiencyBonus} - ) + ); } diff --git a/packages/dnd-character-sheet/src/components/SheetHeader/SheetHeader.tsx b/packages/dnd-character-sheet/src/components/SheetHeader/SheetHeader.tsx index 3460425a..84690745 100644 --- a/packages/dnd-character-sheet/src/components/SheetHeader/SheetHeader.tsx +++ b/packages/dnd-character-sheet/src/components/SheetHeader/SheetHeader.tsx @@ -1,11 +1,11 @@ -import { cn } from '@ageorgedev/toolbelt/cn' -import { useCharacter } from '../CharacterSheet' -import { BigNumber } from '../layout/BigNumber' -import { DiamondCheck } from '../layout/checkables' -import { VerticalDivider } from '../layout/dividers' -import { Panel } from '../layout/Panel' -import { PanelTitle } from '../layout/PanelTitle' -import { VerticalSubPanel } from '../layout/SubPanel' +import { cn } from '@ageorgedev/toolbelt/cn'; +import { useCharacter } from '../CharacterSheet'; +import { BigNumber } from '../layout/BigNumber'; +import { DiamondCheck } from '../layout/checkables'; +import { VerticalDivider } from '../layout/dividers'; +import { Panel } from '../layout/Panel'; +import { PanelTitle } from '../layout/PanelTitle'; +import { VerticalSubPanel } from '../layout/SubPanel'; export function SheetHeader() { return ( @@ -15,11 +15,11 @@ export function SheetHeader() { - ) + ); } function NameBlock() { - const { character } = useCharacter() + const { character } = useCharacter(); return ( {character.species} {character.subclass} - ) + ); } function LevelBlock() { - const { character } = useCharacter() + const { character } = useCharacter(); return (
@@ -45,11 +45,11 @@ function LevelBlock() {
Xp
- ) + ); } function ArmorBlock() { - const { character } = useCharacter() + const { character } = useCharacter(); return ( Shield - ) + ); } function HealthAndDeathBlock() { - const { character } = useCharacter() + const { character } = useCharacter(); return ( @@ -119,7 +119,7 @@ function HealthAndDeathBlock() { - ) + ); } /** Smaller utils */ @@ -128,22 +128,22 @@ function NameField({ label, children, }: { - label: string - children: React.ReactNode + label: string; + children: React.ReactNode; }) { return (
{children} {label}
- ) + ); } function LabelUnder({ className, ...props }: React.HTMLAttributes) { - return + return ; } function BasicLabel({ @@ -155,5 +155,5 @@ function BasicLabel({
{children}
- ) + ); } diff --git a/packages/dnd-character-sheet/src/components/SpeciesTraits/SpeciesTraits.tsx b/packages/dnd-character-sheet/src/components/SpeciesTraits/SpeciesTraits.tsx index 57fdf7e7..c0906be2 100644 --- a/packages/dnd-character-sheet/src/components/SpeciesTraits/SpeciesTraits.tsx +++ b/packages/dnd-character-sheet/src/components/SpeciesTraits/SpeciesTraits.tsx @@ -1,10 +1,10 @@ -import { useCharacter } from '../CharacterSheet' -import { Panel } from '../layout/Panel' -import { PanelTitle } from '../layout/PanelTitle' +import { useCharacter } from '../CharacterSheet'; +import { Panel } from '../layout/Panel'; +import { PanelTitle } from '../layout/PanelTitle'; export function SpeciesTraits() { - const { character } = useCharacter() - const traits = character.speciesTraits ?? [] + const { character } = useCharacter(); + const traits = character.speciesTraits ?? []; return ( @@ -34,5 +34,5 @@ export function SpeciesTraits() { )} - ) + ); } diff --git a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellLevelSection.tsx b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellLevelSection.tsx index 135e947d..ead36292 100644 --- a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellLevelSection.tsx +++ b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellLevelSection.tsx @@ -1,13 +1,13 @@ -import type { Spell } from '../../lib/models/spellcasting' -import { SpellRow } from './SpellRow' +import type { Spell } from '../../lib/models/spellcasting'; +import { SpellRow } from './SpellRow'; interface SpellLevelSectionProps { - level: number - spells: Spell[] + level: number; + spells: Spell[]; } export function SpellLevelSection({ level, spells }: SpellLevelSectionProps) { - const label = level === 0 ? 'Cantrips' : `Level ${level}` + const label = level === 0 ? 'Cantrips' : `Level ${level}`; return (
@@ -34,5 +34,5 @@ export function SpellLevelSection({ level, spells }: SpellLevelSectionProps) {
)} - ) + ); } diff --git a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellRow.tsx b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellRow.tsx index 4d6b7a1a..532c4e46 100644 --- a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellRow.tsx +++ b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellRow.tsx @@ -1,21 +1,23 @@ -import { ABILITY_DETAILS } from '../../lib/models/abilities' -import type { Spell } from '../../lib/models/spellcasting' -import { CircleCheck } from '../layout/checkables' +import { ABILITY_DETAILS } from '../../lib/models/abilities'; +import type { Spell } from '../../lib/models/spellcasting'; +import { CircleCheck } from '../layout/checkables'; function ComponentFlags({ spell }: { spell: Spell }) { - const { components } = spell - if (!components) return null - const flags: string[] = [] - if (components.verbal) flags.push('V') - if (components.somatic) flags.push('S') - if (components.materialConsumed) flags.push('M*') + const { components } = spell; + if (!components) return null; + const flags: string[] = []; + if (components.verbal) flags.push('V'); + if (components.somatic) flags.push('S'); + if (components.materialConsumed) flags.push('M*'); else if ( 'materialConsumed' in components && components.materialConsumed === false ) - flags.push('M') - if (flags.length === 0) return null - return {flags.join(' ')} + flags.push('M'); + if (flags.length === 0) return null; + return ( + {flags.join(' ')} + ); } function Badge({ children }: { children: React.ReactNode }) { @@ -23,12 +25,12 @@ function Badge({ children }: { children: React.ReactNode }) { {children} - ) + ); } interface SpellRowProps { - spell: Spell - showPrepared: boolean + spell: Spell; + showPrepared: boolean; } export function SpellRow({ spell, showPrepared }: SpellRowProps) { @@ -66,5 +68,5 @@ export function SpellRow({ spell, showPrepared }: SpellRowProps) { - ) + ); } diff --git a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingBlock.tsx b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingBlock.tsx index e49f9c3e..3c2a8997 100644 --- a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingBlock.tsx +++ b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingBlock.tsx @@ -1,35 +1,35 @@ -import type { Spell } from '../../lib/models/spellcasting' -import { useCharacter } from '../CharacterSheet' -import { Panel } from '../layout/Panel' -import { PanelTitle } from '../layout/PanelTitle' -import { SpellcastingHeader } from './SpellcastingHeader' -import { SpellLevelSection } from './SpellLevelSection' +import type { Spell } from '../../lib/models/spellcasting'; +import { useCharacter } from '../CharacterSheet'; +import { Panel } from '../layout/Panel'; +import { PanelTitle } from '../layout/PanelTitle'; +import { SpellcastingHeader } from './SpellcastingHeader'; +import { SpellLevelSection } from './SpellLevelSection'; export function SpellcastingBlock() { - const { character } = useCharacter() - const { spellcasting } = character - if (!spellcasting) return null + const { character } = useCharacter(); + const { spellcasting } = character; + if (!spellcasting) return null; // Collect all levels that have spells or defined slots - const spellsByLevel = new Map() + const spellsByLevel = new Map(); for (const spell of spellcasting.spells) { - const bucket = spellsByLevel.get(spell.level) ?? [] - bucket.push(spell) - spellsByLevel.set(spell.level, bucket) + const bucket = spellsByLevel.get(spell.level) ?? []; + bucket.push(spell); + spellsByLevel.set(spell.level, bucket); } // Ensure levels with slots but no spells still appear if (spellcasting.slots) { for (const levelStr of Object.keys(spellcasting.slots)) { - const level = Number(levelStr) + const level = Number(levelStr); if (!spellsByLevel.has(level)) { - spellsByLevel.set(level, []) + spellsByLevel.set(level, []); } } } - const sortedLevels = [...spellsByLevel.keys()].sort((a, b) => a - b) + const sortedLevels = [...spellsByLevel.keys()].sort((a, b) => a - b); return ( @@ -45,5 +45,5 @@ export function SpellcastingBlock() { ))} - ) + ); } diff --git a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingHeader.tsx b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingHeader.tsx index 3e5ae4ca..acca919a 100644 --- a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingHeader.tsx +++ b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingHeader.tsx @@ -1,6 +1,6 @@ // biome-ignore-all lint/suspicious/noArrayIndexKey: Not important -import { useCharacter } from '../CharacterSheet' -import { CircleCheck } from '../layout/checkables' +import { useCharacter } from '../CharacterSheet'; +import { CircleCheck } from '../layout/checkables'; const ABILITY_LABELS: Record = { strength: 'Strength', @@ -9,24 +9,24 @@ const ABILITY_LABELS: Record = { intelligence: 'Intelligence', wisdom: 'Wisdom', charisma: 'Charisma', -} +}; function formatBonus(n: number): string { - return n >= 0 ? `+${n}` : `${n}` + return n >= 0 ? `+${n}` : `${n}`; } export function SpellcastingHeader() { - const { character, derived } = useCharacter() - const { spellcasting } = character - if (!spellcasting) return null + const { character, derived } = useCharacter(); + const { spellcasting } = character; + if (!spellcasting) return null; const abilityLabel = - ABILITY_LABELS[spellcasting.ability] ?? spellcasting.ability + ABILITY_LABELS[spellcasting.ability] ?? spellcasting.ability; const slotLevels = spellcasting.slots ? (Object.entries(spellcasting.slots) as [string, number][]).sort( ([a], [b]) => Number(a) - Number(b) ) - : [] + : []; return (
@@ -97,5 +97,5 @@ export function SpellcastingHeader() {
)} - ) + ); } diff --git a/packages/dnd-character-sheet/src/components/SpellcastingBlock/index.ts b/packages/dnd-character-sheet/src/components/SpellcastingBlock/index.ts index 9195fbd0..50f18988 100644 --- a/packages/dnd-character-sheet/src/components/SpellcastingBlock/index.ts +++ b/packages/dnd-character-sheet/src/components/SpellcastingBlock/index.ts @@ -1 +1 @@ -export { SpellcastingBlock } from './SpellcastingBlock' +export { SpellcastingBlock } from './SpellcastingBlock'; diff --git a/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx b/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx index 8dc563d9..e684d66a 100644 --- a/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx +++ b/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx @@ -1,23 +1,23 @@ -import { Ability } from '../../lib/models/abilities' -import type { Character } from '../../lib/models/character' -import { AbilityBox } from '../AbilityBox/AbilityBox' -import { AttackList } from '../AttackList/AttackList' -import { BackstoryBlock } from '../BackstoryBlock/BackstoryBlock' -import { CharacterSheet } from '../CharacterSheet' -import { ClassFeatures } from '../ClassFeatures/ClassFeatures' -import { CombatRow } from '../CombatRow/CombatRow' -import { EquipmentBlock } from '../EquipmentBlock/EquipmentBlock' -import { EquipmentTraining } from '../EquipmentTraining/EquipmentTraining' -import { Feats } from '../Feats/Feats' -import { HeroicInspiration } from '../HeroicInspiration/HeroicInspiration' -import { Page } from '../layout/Page' -import { ProficiencyBlock } from '../ProficiencyBlock/ProficiencyBlock' -import { SheetHeader } from '../SheetHeader/SheetHeader' -import { SpeciesTraits } from '../SpeciesTraits/SpeciesTraits' -import { SpellcastingBlock } from '../SpellcastingBlock' +import { Ability } from '../../lib/models/abilities'; +import type { Character } from '../../lib/models/character'; +import { AbilityBox } from '../AbilityBox/AbilityBox'; +import { AttackList } from '../AttackList/AttackList'; +import { BackstoryBlock } from '../BackstoryBlock/BackstoryBlock'; +import { CharacterSheet } from '../CharacterSheet'; +import { ClassFeatures } from '../ClassFeatures/ClassFeatures'; +import { CombatRow } from '../CombatRow/CombatRow'; +import { EquipmentBlock } from '../EquipmentBlock/EquipmentBlock'; +import { EquipmentTraining } from '../EquipmentTraining/EquipmentTraining'; +import { Feats } from '../Feats/Feats'; +import { HeroicInspiration } from '../HeroicInspiration/HeroicInspiration'; +import { Page } from '../layout/Page'; +import { ProficiencyBlock } from '../ProficiencyBlock/ProficiencyBlock'; +import { SheetHeader } from '../SheetHeader/SheetHeader'; +import { SpeciesTraits } from '../SpeciesTraits/SpeciesTraits'; +import { SpellcastingBlock } from '../SpellcastingBlock'; interface Props { - data: Character + data: Character; } export function StandardCharacterSheet({ data }: Props) { @@ -68,5 +68,5 @@ export function StandardCharacterSheet({ data }: Props) {
- ) + ); } diff --git a/packages/dnd-character-sheet/src/components/layout/BigNumber.tsx b/packages/dnd-character-sheet/src/components/layout/BigNumber.tsx index dae6b214..ed9e7058 100644 --- a/packages/dnd-character-sheet/src/components/layout/BigNumber.tsx +++ b/packages/dnd-character-sheet/src/components/layout/BigNumber.tsx @@ -1,4 +1,4 @@ -import { cn } from '@ageorgedev/toolbelt/cn' +import { cn } from '@ageorgedev/toolbelt/cn'; export function BigNumber({ className, @@ -9,5 +9,5 @@ export function BigNumber({ className={cn('text-4xl leading-none lining-nums', className)} {...props} /> - ) + ); } diff --git a/packages/dnd-character-sheet/src/components/layout/Page.tsx b/packages/dnd-character-sheet/src/components/layout/Page.tsx index ada584b7..bf930d88 100644 --- a/packages/dnd-character-sheet/src/components/layout/Page.tsx +++ b/packages/dnd-character-sheet/src/components/layout/Page.tsx @@ -1,5 +1,5 @@ -import { cn } from '@ageorgedev/toolbelt/cn' -import styles from './Page.module.css' +import { cn } from '@ageorgedev/toolbelt/cn'; +import styles from './Page.module.css'; export function Page({ children }: React.PropsWithChildren) { return ( @@ -8,5 +8,5 @@ export function Page({ children }: React.PropsWithChildren) { {children} - ) + ); } diff --git a/packages/dnd-character-sheet/src/components/layout/Panel.tsx b/packages/dnd-character-sheet/src/components/layout/Panel.tsx index 8522966d..43c82aed 100644 --- a/packages/dnd-character-sheet/src/components/layout/Panel.tsx +++ b/packages/dnd-character-sheet/src/components/layout/Panel.tsx @@ -1,17 +1,17 @@ -import { cn } from '@ageorgedev/toolbelt/cn' -import { useMemo } from 'react' -import styles from './Panel.module.css' +import { cn } from '@ageorgedev/toolbelt/cn'; +import { useMemo } from 'react'; +import styles from './Panel.module.css'; -type Corner = 'scooped' | 'regular' +type Corner = 'scooped' | 'regular'; export type PanelProps = { - outerClasses?: string + outerClasses?: string; - topLeftCorner?: Corner - topRightCorner?: Corner - bottomRightCorner?: Corner - bottomLeftCorner?: Corner -} & React.HTMLAttributes + topLeftCorner?: Corner; + topRightCorner?: Corner; + bottomRightCorner?: Corner; + bottomLeftCorner?: Corner; +} & React.HTMLAttributes; export function Panel({ topLeftCorner = 'regular', @@ -38,7 +38,7 @@ export function Panel({ : styles.regularBottomLeft, ], [topLeftCorner, topRightCorner, bottomRightCorner, bottomLeftCorner] - ) + ); return (
@@ -52,5 +52,5 @@ export function Panel({ )} />
- ) + ); } diff --git a/packages/dnd-character-sheet/src/components/layout/PanelTitle.tsx b/packages/dnd-character-sheet/src/components/layout/PanelTitle.tsx index 18006e18..9c9d4929 100644 --- a/packages/dnd-character-sheet/src/components/layout/PanelTitle.tsx +++ b/packages/dnd-character-sheet/src/components/layout/PanelTitle.tsx @@ -1,4 +1,4 @@ -import { cn } from '@ageorgedev/toolbelt/cn' +import { cn } from '@ageorgedev/toolbelt/cn'; export function PanelTitle({ children, @@ -15,5 +15,5 @@ export function PanelTitle({ > {children} - ) + ); } diff --git a/packages/dnd-character-sheet/src/components/layout/SubPanel.tsx b/packages/dnd-character-sheet/src/components/layout/SubPanel.tsx index f67a97a7..baa81ae4 100644 --- a/packages/dnd-character-sheet/src/components/layout/SubPanel.tsx +++ b/packages/dnd-character-sheet/src/components/layout/SubPanel.tsx @@ -1,5 +1,5 @@ -import { cn } from '@ageorgedev/toolbelt/cn' -import styles from './SubPanel.module.css' +import { cn } from '@ageorgedev/toolbelt/cn'; +import styles from './SubPanel.module.css'; export function VerticalSubPanel({ className, @@ -7,7 +7,7 @@ export function VerticalSubPanel({ }: React.HtmlHTMLAttributes) { return (
- ) + ); } export function HorizontalSubPanel({ @@ -16,5 +16,5 @@ export function HorizontalSubPanel({ }: React.HtmlHTMLAttributes) { return (
- ) + ); } diff --git a/packages/dnd-character-sheet/src/components/layout/checkables.tsx b/packages/dnd-character-sheet/src/components/layout/checkables.tsx index d63beef8..7a340ed3 100644 --- a/packages/dnd-character-sheet/src/components/layout/checkables.tsx +++ b/packages/dnd-character-sheet/src/components/layout/checkables.tsx @@ -1,19 +1,19 @@ -import { cn } from '@ageorgedev/toolbelt/cn' +import { cn } from '@ageorgedev/toolbelt/cn'; export type CheckableProps = { - checked?: CheckedState -} & React.HTMLAttributes + checked?: CheckedState; +} & React.HTMLAttributes; export function CircleCheck({ className, ...props }: CheckableProps) { - return + return ; } export function DiamondCheck({ className, ...props }: CheckableProps) { - return + return ; } function BaseCheck({ checked = false, className, ...props }: CheckableProps) { - const fill = CHECK_FILL[String(checked) as keyof typeof CHECK_FILL] + const fill = CHECK_FILL[String(checked) as keyof typeof CHECK_FILL]; return ( - ) + ); } -export type CheckedState = boolean | 'special' | undefined +export type CheckedState = boolean | 'special' | undefined; const CHECK_FILL: Record<'true' | 'false' | 'special', string> = { true: 'bg-primary-surface border-primary-foreground', special: 'bg-secondary-surface-2 border-secondary-foreground', false: '', -} +}; diff --git a/packages/dnd-character-sheet/src/components/layout/dividers.tsx b/packages/dnd-character-sheet/src/components/layout/dividers.tsx index 02f0b7bf..15010ce2 100644 --- a/packages/dnd-character-sheet/src/components/layout/dividers.tsx +++ b/packages/dnd-character-sheet/src/components/layout/dividers.tsx @@ -1,7 +1,9 @@ export function VerticalDivider() { - return
+ return ( +
+ ); } export function HorizontalDivider() { - return
+ return
; } diff --git a/packages/dnd-character-sheet/src/index.ts b/packages/dnd-character-sheet/src/index.ts index 0c4bf30f..821b38aa 100644 --- a/packages/dnd-character-sheet/src/index.ts +++ b/packages/dnd-character-sheet/src/index.ts @@ -1,5 +1,5 @@ // Assembled character sheets -export { ExampleSheet } from './characters/example' +export { ExampleSheet } from './characters/example'; // Standard assembled sheet -export { StandardCharacterSheet } from './components/StandardCharacterSheet/StandardCharacterSheet' +export { StandardCharacterSheet } from './components/StandardCharacterSheet/StandardCharacterSheet'; diff --git a/packages/dnd-character-sheet/src/lib/calculate-derived-stats.ts b/packages/dnd-character-sheet/src/lib/calculate-derived-stats.ts index f4ab6b6d..93370e71 100644 --- a/packages/dnd-character-sheet/src/lib/calculate-derived-stats.ts +++ b/packages/dnd-character-sheet/src/lib/calculate-derived-stats.ts @@ -1,54 +1,54 @@ -import { Ability, ALL_ABILITIES } from './models/abilities' -import type { Character } from './models/character' -import type { DerivedStats } from './models/derived-stats' -import { AbilitySkillGrouping, Skill } from './models/skills' +import { Ability, ALL_ABILITIES } from './models/abilities'; +import type { Character } from './models/character'; +import type { DerivedStats } from './models/derived-stats'; +import { AbilitySkillGrouping, Skill } from './models/skills'; function abilityModifier(score: number): number { - return Math.floor((score - 10) / 2) + return Math.floor((score - 10) / 2); } function proficiencyBonus(level: number): number { - if (level <= 4) return 2 - if (level <= 8) return 3 - if (level <= 12) return 4 - if (level <= 16) return 5 - return 6 + if (level <= 4) return 2; + if (level <= 8) return 3; + if (level <= 12) return 4; + if (level <= 16) return 5; + return 6; } export function calculateStats(character: Character): DerivedStats { - const profBonus = proficiencyBonus(character.level) + const profBonus = proficiencyBonus(character.level); const abilityModifiers = Object.fromEntries( ALL_ABILITIES.map((name) => [ name, abilityModifier(character.abilities[name]), ]) - ) as DerivedStats['abilityModifiers'] + ) as DerivedStats['abilityModifiers']; const savingThrows = Object.fromEntries( ALL_ABILITIES.map((name) => { - const isProficient = character.savingThrowProficiencies.includes(name) - return [name, abilityModifiers[name] + (isProficient ? profBonus : 0)] + const isProficient = character.savingThrowProficiencies.includes(name); + return [name, abilityModifiers[name] + (isProficient ? profBonus : 0)]; }) - ) as DerivedStats['savingThrows'] + ) as DerivedStats['savingThrows']; const skills = Object.fromEntries( Object.entries(AbilitySkillGrouping).flatMap(([ability, skills]) => { return skills.map((skill) => { - const isProficient = character.skillProficiencies.includes(skill) - const hasExpertise = character.skillExpertise.includes(skill) + const isProficient = character.skillProficiencies.includes(skill); + const hasExpertise = character.skillExpertise.includes(skill); const bonus = abilityModifiers[ability as Ability] + (isProficient ? profBonus : 0) + - (hasExpertise ? profBonus : 0) - return [skill, bonus] as const - }) + (hasExpertise ? profBonus : 0); + return [skill, bonus] as const; + }); }) - ) as DerivedStats['skills'] + ) as DerivedStats['skills']; - const initiative = abilityModifiers[Ability.Dexterity] + const initiative = abilityModifiers[Ability.Dexterity]; - const passivePerception = 10 + skills[Skill.Perception] + const passivePerception = 10 + skills[Skill.Perception]; const spellcasting = character.spellcasting ? { @@ -57,7 +57,7 @@ export function calculateStats(character: Character): DerivedStats { spellAttackBonus: profBonus + abilityModifiers[character.spellcasting.ability], } - : {} + : {}; return { abilityModifiers, @@ -67,5 +67,5 @@ export function calculateStats(character: Character): DerivedStats { initiative, passivePerception, ...spellcasting, - } + }; } diff --git a/packages/dnd-character-sheet/src/lib/models/abilities.ts b/packages/dnd-character-sheet/src/lib/models/abilities.ts index cbea3dce..c418d710 100644 --- a/packages/dnd-character-sheet/src/lib/models/abilities.ts +++ b/packages/dnd-character-sheet/src/lib/models/abilities.ts @@ -5,9 +5,9 @@ export const Ability = { Intelligence: 'INT', Wisdom: 'WIS', Charisma: 'CHA', -} as const +} as const; -export type Ability = (typeof Ability)[keyof typeof Ability] +export type Ability = (typeof Ability)[keyof typeof Ability]; export const ALL_ABILITIES: Ability[] = [ Ability.Strength, @@ -16,7 +16,7 @@ export const ALL_ABILITIES: Ability[] = [ Ability.Intelligence, Ability.Wisdom, Ability.Charisma, -] +]; export const ABILITY_DETAILS: Record< Ability, @@ -28,4 +28,4 @@ export const ABILITY_DETAILS: Record< [Ability.Intelligence]: { label: 'Intelligence', shortName: 'Int.' }, [Ability.Wisdom]: { label: 'Wisdom', shortName: 'Wis.' }, [Ability.Charisma]: { label: 'Charisma', shortName: 'Cha.' }, -} +}; diff --git a/packages/dnd-character-sheet/src/lib/models/attacks.ts b/packages/dnd-character-sheet/src/lib/models/attacks.ts index 7618d0bb..8961034e 100644 --- a/packages/dnd-character-sheet/src/lib/models/attacks.ts +++ b/packages/dnd-character-sheet/src/lib/models/attacks.ts @@ -1,33 +1,33 @@ -import type { Ability } from './abilities' +import type { Ability } from './abilities'; export type Attack = { - name: string - damage: AttackDamage[] - masteryProperty?: string - attackBonusMod?: number - damageMod?: number - notProficient?: boolean - notes?: string -} & (WeaponAttack | SpellWithAttack | SpellWithSave) + name: string; + damage: AttackDamage[]; + masteryProperty?: string; + attackBonusMod?: number; + damageMod?: number; + notProficient?: boolean; + notes?: string; +} & (WeaponAttack | SpellWithAttack | SpellWithSave); export type SpellWithSave = { - kind: 'spell-with-save' - ability?: Ability - saveAbility: Ability -} + kind: 'spell-with-save'; + ability?: Ability; + saveAbility: Ability; +}; export type SpellWithAttack = { - kind: 'spell-with-attack' - ability?: Ability -} + kind: 'spell-with-attack'; + ability?: Ability; +}; export type WeaponAttack = { - kind: 'weapon' - ability: typeof Ability.Strength | typeof Ability.Dexterity -} + kind: 'weapon'; + ability: typeof Ability.Strength | typeof Ability.Dexterity; +}; export type AttackDamage = { - dice: string - type: string - disableModifier?: boolean -} + dice: string; + type: string; + disableModifier?: boolean; +}; diff --git a/packages/dnd-character-sheet/src/lib/models/character.ts b/packages/dnd-character-sheet/src/lib/models/character.ts index 0a7fad95..17022dfc 100644 --- a/packages/dnd-character-sheet/src/lib/models/character.ts +++ b/packages/dnd-character-sheet/src/lib/models/character.ts @@ -1,82 +1,82 @@ -import type { Ability } from './abilities' -import type { Attack } from './attacks' -import type { Feature } from './feature' -import type { Skill } from './skills' -import type { Spellcasting } from './spellcasting' +import type { Ability } from './abilities'; +import type { Attack } from './attacks'; +import type { Feature } from './feature'; +import type { Skill } from './skills'; +import type { Spellcasting } from './spellcasting'; export interface Character { // Identity - name: string - class: string - subclass?: string - level: number - species: string - background: string - alignment?: string - experiencePoints: number + name: string; + class: string; + subclass?: string; + level: number; + species: string; + background: string; + alignment?: string; + experiencePoints: number; // Ability scores - abilities: Record + abilities: Record; // Proficiencies - savingThrowProficiencies: Ability[] - skillProficiencies: Skill[] - skillExpertise: Skill[] + savingThrowProficiencies: Ability[]; + skillProficiencies: Skill[]; + skillExpertise: Skill[]; // Combat - armorClass: number - speed: number + armorClass: number; + speed: number; hitPoints: { - maximum: number - current?: number - temporary?: number - } + maximum: number; + current?: number; + temporary?: number; + }; // Hit dice hitDice: { - dieType: 'd6' | 'd8' | 'd10' | 'd12' - total: number - } + dieType: 'd6' | 'd8' | 'd10' | 'd12'; + total: number; + }; // Attacks - attacks: Attack[] + attacks: Attack[]; // Equipment - equipment: string[] + equipment: string[]; currency: { - cp: number - sp: number - ep: number - gp: number - pp: number - } + cp: number; + sp: number; + ep: number; + gp: number; + pp: number; + }; // Features - features: Feature[] - speciesTraits?: Feature[] - feats?: Feature[] + features: Feature[]; + speciesTraits?: Feature[]; + feats?: Feature[]; // Size - size?: string + size?: string; // Proficiency text lists - armorProficiencies: string[] - weaponProficiencies: string[] - toolProficiencies: string[] - languages: string[] + armorProficiencies: string[]; + weaponProficiencies: string[]; + toolProficiencies: string[]; + languages: string[]; // Personality personality: { - traits: string - ideals: string - bonds: string - flaws: string - } + traits: string; + ideals: string; + bonds: string; + flaws: string; + }; // Freeform - backstory?: string - notes?: string + backstory?: string; + notes?: string; // Spellcasting - spellcasting?: Spellcasting + spellcasting?: Spellcasting; } diff --git a/packages/dnd-character-sheet/src/lib/models/derived-stats.ts b/packages/dnd-character-sheet/src/lib/models/derived-stats.ts index 6c3c79e8..f56708c8 100644 --- a/packages/dnd-character-sheet/src/lib/models/derived-stats.ts +++ b/packages/dnd-character-sheet/src/lib/models/derived-stats.ts @@ -1,13 +1,13 @@ -import type { Ability } from './abilities' -import type { Skill } from './skills' +import type { Ability } from './abilities'; +import type { Skill } from './skills'; export interface DerivedStats { - abilityModifiers: Record - proficiencyBonus: number - savingThrows: Record - skills: Record - passivePerception: number - initiative: number - spellSaveDC?: number - spellAttackBonus?: number + abilityModifiers: Record; + proficiencyBonus: number; + savingThrows: Record; + skills: Record; + passivePerception: number; + initiative: number; + spellSaveDC?: number; + spellAttackBonus?: number; } diff --git a/packages/dnd-character-sheet/src/lib/models/feature.ts b/packages/dnd-character-sheet/src/lib/models/feature.ts index 2b0f0105..62b80a4a 100644 --- a/packages/dnd-character-sheet/src/lib/models/feature.ts +++ b/packages/dnd-character-sheet/src/lib/models/feature.ts @@ -1,5 +1,5 @@ export interface Feature { - name: string - source: string - description: string + name: string; + source: string; + description: string; } diff --git a/packages/dnd-character-sheet/src/lib/models/index.ts b/packages/dnd-character-sheet/src/lib/models/index.ts index 2ecb6d6c..1196e277 100644 --- a/packages/dnd-character-sheet/src/lib/models/index.ts +++ b/packages/dnd-character-sheet/src/lib/models/index.ts @@ -1,4 +1,4 @@ -export { ABILITY_DETAILS, Ability, ALL_ABILITIES } from './abilities' -export type { Character } from './character' -export { AbilitySkillGrouping, Skill } from './skills' -export type { Spell } from './spellcasting' +export { ABILITY_DETAILS, Ability, ALL_ABILITIES } from './abilities'; +export type { Character } from './character'; +export { AbilitySkillGrouping, Skill } from './skills'; +export type { Spell } from './spellcasting'; diff --git a/packages/dnd-character-sheet/src/lib/models/skills.ts b/packages/dnd-character-sheet/src/lib/models/skills.ts index eefcc142..2afc931d 100644 --- a/packages/dnd-character-sheet/src/lib/models/skills.ts +++ b/packages/dnd-character-sheet/src/lib/models/skills.ts @@ -1,4 +1,4 @@ -import { Ability } from './abilities' +import { Ability } from './abilities'; export const Skill = { Acrobatics: 'Acrobatics', @@ -19,9 +19,9 @@ export const Skill = { SleightOfHand: 'Sleight of Hand', Stealth: 'Stealth', Survival: 'Survival', -} as const +} as const; -export type Skill = (typeof Skill)[keyof typeof Skill] +export type Skill = (typeof Skill)[keyof typeof Skill]; export const AbilitySkillGrouping: Record = { [Ability.Strength]: [Skill.Athletics], @@ -47,4 +47,4 @@ export const AbilitySkillGrouping: Record = { Skill.Performance, Skill.Persuasion, ], -} +}; diff --git a/packages/dnd-character-sheet/src/lib/models/spellcasting.ts b/packages/dnd-character-sheet/src/lib/models/spellcasting.ts index d7662ba8..2607e1f3 100644 --- a/packages/dnd-character-sheet/src/lib/models/spellcasting.ts +++ b/packages/dnd-character-sheet/src/lib/models/spellcasting.ts @@ -1,4 +1,4 @@ -import type { Ability } from './abilities' +import type { Ability } from './abilities'; type SpellSchool = | 'Abjuration' @@ -8,33 +8,33 @@ type SpellSchool = | 'Evocation' | 'Illusion' | 'Necromancy' - | 'Transmutation' + | 'Transmutation'; interface SpellComponents { - verbal?: boolean - somatic?: boolean - materialConsumed?: boolean + verbal?: boolean; + somatic?: boolean; + materialConsumed?: boolean; } export interface Spell { - name: string - level: number - castingTime: string - range: string - duration: string - school?: SpellSchool - components?: SpellComponents - concentration?: boolean - ritual?: boolean - freeUses?: number - alwaysPrepared?: boolean - alternativeAbility?: Ability - notes?: string + name: string; + level: number; + castingTime: string; + range: string; + duration: string; + school?: SpellSchool; + components?: SpellComponents; + concentration?: boolean; + ritual?: boolean; + freeUses?: number; + alwaysPrepared?: boolean; + alternativeAbility?: Ability; + notes?: string; } export interface Spellcasting { - ability: Ability - slots?: Partial> - pactMagic?: { level: number; slots: number } - spells: Spell[] + ability: Ability; + slots?: Partial>; + pactMagic?: { level: number; slots: number }; + spells: Spell[]; } diff --git a/packages/foundation-styles/postcss.config.js b/packages/foundation-styles/postcss.config.js index 60bf9096..8984cd25 100644 --- a/packages/foundation-styles/postcss.config.js +++ b/packages/foundation-styles/postcss.config.js @@ -8,4 +8,4 @@ export default { 'postcss-nesting': {}, '@tailwindcss/postcss': {}, }, -} +}; diff --git a/packages/reveal-framework/src/components/deck-styles.ts b/packages/reveal-framework/src/components/deck-styles.ts index 30d4962d..971be68d 100644 --- a/packages/reveal-framework/src/components/deck-styles.ts +++ b/packages/reveal-framework/src/components/deck-styles.ts @@ -1,14 +1,14 @@ -import styles from './deck.module.css' +import styles from './deck.module.css'; -export const slideMainGap = 'gap-5' -export const slideXMargin = 'px-6' -export const slideWithFooterBottomMargin = 'pb-6' -export const slideWithoutFooterBottomMargin = 'pb-9' -export const slideMainReset = `h-full ${slideXMargin} relative` -export const slideContentReset = 'h-full' +export const slideMainGap = 'gap-5'; +export const slideXMargin = 'px-6'; +export const slideWithFooterBottomMargin = 'pb-6'; +export const slideWithoutFooterBottomMargin = 'pb-9'; +export const slideMainReset = `h-full ${slideXMargin} relative`; +export const slideContentReset = 'h-full'; -export const slideCenterStyles = `flex flex-col justify-center items-center` +export const slideCenterStyles = `flex flex-col justify-center items-center`; -export const slideHeaderGrid = `grid place-items-center gap-6 large-desktop:gap-7 ${styles.slideHeaderGrid}` +export const slideHeaderGrid = `grid place-items-center gap-6 large-desktop:gap-7 ${styles.slideHeaderGrid}`; -export const middleDivider = styles.middleDivider +export const middleDivider = styles.middleDivider; diff --git a/packages/reveal-framework/src/components/slide-components.tsx b/packages/reveal-framework/src/components/slide-components.tsx index 9d4556a1..253b233d 100644 --- a/packages/reveal-framework/src/components/slide-components.tsx +++ b/packages/reveal-framework/src/components/slide-components.tsx @@ -1,9 +1,9 @@ -import { CalloutBadge } from '@ageorgedev/design-system/callouts/CalloutBadge' +import { CalloutBadge } from '@ageorgedev/design-system/callouts/CalloutBadge'; import { TiltCard, type TiltCardProps, -} from '@ageorgedev/design-system/cards/TiltCard' -import { cn } from '@ageorgedev/toolbelt/cn' +} from '@ageorgedev/design-system/cards/TiltCard'; +import { cn } from '@ageorgedev/toolbelt/cn'; import { BracketsAngleIcon, ExamIcon, @@ -12,19 +12,19 @@ import { MegaphoneIcon, PenNibIcon, WarningIcon, -} from '@phosphor-icons/react' +} from '@phosphor-icons/react'; type ImportantNoteProps = React.PropsWithChildren<{ - shape?: TiltCardProps['shape'] - className?: string - type?: NoteType -}> + shape?: TiltCardProps['shape']; + className?: string; + type?: NoteType; +}>; -type NoteType = 'tip' | 'danger' +type NoteType = 'tip' | 'danger'; export function ImportantNote(props: ImportantNoteProps) { - const noteType = props.type ?? 'tip' - const { iconClasses, mainClasses, Icon } = NoteConfigs[noteType] + const noteType = props.type ?? 'tip'; + const { iconClasses, mainClasses, Icon } = NoteConfigs[noteType]; return (
@@ -32,7 +32,7 @@ export function ImportantNote(props: ImportantNoteProps) {
{props.children}
- ) + ); } const NoteConfigs: Record< @@ -49,18 +49,18 @@ const NoteConfigs: Record< iconClasses: '', Icon: WarningIcon, }, -} +}; export function CalloutForVD() { - return + return ; } export function CalloutForAdvancedTopic() { - return + return ; } export function CalloutForPersonalOpinion() { - return + return ; } export function PointSeperator({ className }: { className?: string }) { @@ -71,5 +71,5 @@ export function PointSeperator({ className }: { className?: string }) { className )} /> - ) + ); } diff --git a/packages/reveal-framework/src/components/slide-layout-builders.spec.tsx b/packages/reveal-framework/src/components/slide-layout-builders.spec.tsx index f2ae4ec7..9cd5bb89 100644 --- a/packages/reveal-framework/src/components/slide-layout-builders.spec.tsx +++ b/packages/reveal-framework/src/components/slide-layout-builders.spec.tsx @@ -1,33 +1,33 @@ -import { render, screen } from '@testing-library/react' -import { describe, expect, it } from 'vitest' +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; import { ComparisonRow, DeckFooter, SlideMediaRow, -} from './slide-layout-builders' +} from './slide-layout-builders'; describe('SlideMediaRow', () => { it('renders children', () => { - render(slide content) - expect(screen.getByText('slide content')).toBeInTheDocument() - }) + render(slide content); + expect(screen.getByText('slide content')).toBeInTheDocument(); + }); it('merges extra className with base styles', () => { const { container } = render( content - ) - expect(container.firstChild).toHaveClass('extra-class') - }) -}) + ); + expect(container.firstChild).toHaveClass('extra-class'); + }); +}); describe('DeckFooter', () => { it('renders a footer element', () => { - render(footer text) - const footer = screen.getByRole('contentinfo') - expect(footer).toBeInTheDocument() - expect(footer).toHaveTextContent('footer text') - }) -}) + render(footer text); + const footer = screen.getByRole('contentinfo'); + expect(footer).toBeInTheDocument(); + expect(footer).toHaveTextContent('footer text'); + }); +}); describe('ComparisonRow', () => { it('renders left and right content', () => { @@ -36,15 +36,15 @@ describe('ComparisonRow', () => { left={Left side} right={Right side} /> - ) - expect(screen.getByText('Left side')).toBeInTheDocument() - expect(screen.getByText('Right side')).toBeInTheDocument() - }) + ); + expect(screen.getByText('Left side')).toBeInTheDocument(); + expect(screen.getByText('Right side')).toBeInTheDocument(); + }); it('merges extra className', () => { const { container } = render( - ) - expect(container.firstChild).toHaveClass('custom-row') - }) -}) + ); + expect(container.firstChild).toHaveClass('custom-row'); + }); +}); diff --git a/packages/reveal-framework/src/components/slide-layout-builders.tsx b/packages/reveal-framework/src/components/slide-layout-builders.tsx index 4d8c9539..58304c46 100644 --- a/packages/reveal-framework/src/components/slide-layout-builders.tsx +++ b/packages/reveal-framework/src/components/slide-layout-builders.tsx @@ -1,5 +1,5 @@ -import { cn } from '@ageorgedev/toolbelt/cn' -import { middleDivider, slideMainGap } from './deck-styles' +import { cn } from '@ageorgedev/toolbelt/cn'; +import { middleDivider, slideMainGap } from './deck-styles'; export function SlideMediaRow({ className, @@ -17,7 +17,7 @@ export function SlideMediaRow({ > {children}
- ) + ); } export function DeckFooter(props: React.PropsWithChildren) { @@ -25,7 +25,7 @@ export function DeckFooter(props: React.PropsWithChildren) {
{props.children}
- ) + ); } export function ComparisonRow({ @@ -34,8 +34,8 @@ export function ComparisonRow({ right, ...otherProps }: { - left: React.ReactNode - right: React.ReactNode + left: React.ReactNode; + right: React.ReactNode; } & Omit, 'children'>) { return (
{left}
{right}
- ) + ); } diff --git a/packages/reveal-framework/src/components/slide-types.tsx b/packages/reveal-framework/src/components/slide-types.tsx index 6eee8710..d2f58930 100644 --- a/packages/reveal-framework/src/components/slide-types.tsx +++ b/packages/reveal-framework/src/components/slide-types.tsx @@ -1,6 +1,6 @@ -import { cn } from '@ageorgedev/toolbelt/cn' -import { Slide } from '@revealjs/react' -import React from 'react' +import { cn } from '@ageorgedev/toolbelt/cn'; +import { Slide } from '@revealjs/react'; +import React from 'react'; import { slideCenterStyles, slideContentReset, @@ -8,18 +8,18 @@ import { slideMainGap, slideMainReset, slideWithoutFooterBottomMargin, -} from './deck-styles' +} from './deck-styles'; import { CalloutForAdvancedTopic, CalloutForPersonalOpinion, CalloutForVD, -} from './slide-components' +} from './slide-components'; -type CalloutType = 'UX' | 'Advanced' | 'Opinion' +type CalloutType = 'UX' | 'Advanced' | 'Opinion'; type SlideWithCallout = { - callout?: CalloutType | CalloutType[] -} + callout?: CalloutType | CalloutType[]; +}; export function SlideTypeCenter( props: React.PropsWithChildren @@ -38,7 +38,7 @@ export function SlideTypeCenter( {props.children}
- ) + ); } export function SlideTypeRegular( @@ -62,16 +62,16 @@ export function SlideTypeRegular(
- ) + ); } function TopLeftPosition({ callout, }: React.PropsWithChildren) { - const calloutTypes = Array.isArray(callout) ? callout : [callout] + const calloutTypes = Array.isArray(callout) ? callout : [callout]; const calloutComponents = calloutTypes.map((c) => c ? CalloutMap[c] : React.Fragment - ) + ); return (
{calloutComponents.map((Comp, i) => ( @@ -79,11 +79,11 @@ function TopLeftPosition({ ))}
- ) + ); } const CalloutMap = { UX: CalloutForVD, Advanced: CalloutForAdvancedTopic, Opinion: CalloutForPersonalOpinion, -} +}; diff --git a/packages/reveal-framework/src/globals.ts b/packages/reveal-framework/src/globals.ts index f0740205..d2227fb7 100644 --- a/packages/reveal-framework/src/globals.ts +++ b/packages/reveal-framework/src/globals.ts @@ -1,2 +1,2 @@ -import 'reveal.js/reveal.css' -import './styles/reveal-theme.css' +import 'reveal.js/reveal.css'; +import './styles/reveal-theme.css'; diff --git a/packages/reveal-framework/src/hooks/useRevealFramework.spec.ts b/packages/reveal-framework/src/hooks/useRevealFramework.spec.ts index 7a72e7e5..1bf3cb32 100644 --- a/packages/reveal-framework/src/hooks/useRevealFramework.spec.ts +++ b/packages/reveal-framework/src/hooks/useRevealFramework.spec.ts @@ -1,46 +1,46 @@ -import { renderHook, waitFor } from '@testing-library/react' -import type { RefObject } from 'react' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { renderHook, waitFor } from '@testing-library/react'; +import type { RefObject } from 'react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; -const mockInitialize = vi.fn().mockResolvedValue(undefined) +const mockInitialize = vi.fn().mockResolvedValue(undefined); vi.mock('reveal.js', () => ({ // Regular function (not arrow) so it can be called with `new` // biome-ignore lint/suspicious/noExplicitAny: constructor mock requires any default: vi.fn(function (this: any) { - this.initialize = mockInitialize + this.initialize = mockInitialize; }), -})) +})); // Import after mock is hoisted -const { useRevealFramework } = await import('./useRevealFramework') +const { useRevealFramework } = await import('./useRevealFramework'); -import Reveal from 'reveal.js' +import Reveal from 'reveal.js'; describe('useRevealFramework', () => { beforeEach(() => { - vi.mocked(Reveal as unknown as () => void).mockClear() - mockInitialize.mockClear() - }) + vi.mocked(Reveal as unknown as () => void).mockClear(); + mockInitialize.mockClear(); + }); it('initialises a Reveal deck when the ref element is available', async () => { - const div = document.createElement('div') - const ref = { current: div } as RefObject + const div = document.createElement('div'); + const ref = { current: div } as RefObject; - renderHook(() => useRevealFramework(ref)) + renderHook(() => useRevealFramework(ref)); await waitFor(() => { - expect(Reveal).toHaveBeenCalledWith(div, expect.any(Object)) - }) - expect(mockInitialize).toHaveBeenCalled() - }) + expect(Reveal).toHaveBeenCalledWith(div, expect.any(Object)); + }); + expect(mockInitialize).toHaveBeenCalled(); + }); it('does nothing when the ref has no element', async () => { - const ref = { current: null } as unknown as RefObject + const ref = { current: null } as unknown as RefObject; - renderHook(() => useRevealFramework(ref)) + renderHook(() => useRevealFramework(ref)); - await new Promise((r) => setTimeout(r, 50)) - expect(Reveal).not.toHaveBeenCalled() - }) -}) + await new Promise((r) => setTimeout(r, 50)); + expect(Reveal).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/reveal-framework/src/hooks/useRevealFramework.ts b/packages/reveal-framework/src/hooks/useRevealFramework.ts index e83c651a..08bbf568 100644 --- a/packages/reveal-framework/src/hooks/useRevealFramework.ts +++ b/packages/reveal-framework/src/hooks/useRevealFramework.ts @@ -1,29 +1,29 @@ -import { type RefObject, useEffect, useRef } from 'react' -import type { RevealApi } from 'reveal.js' +import { type RefObject, useEffect, useRef } from 'react'; +import type { RevealApi } from 'reveal.js'; export function useRevealFramework(elementRef: RefObject) { - const revealInstance = useRef(null) + const revealInstance = useRef(null); // biome-ignore lint/correctness/useExhaustiveDependencies: thats how it works useEffect(() => { - ;(async () => { - if (!elementRef.current) return + (async () => { + if (!elementRef.current) return; - const Reveal = (await import('reveal.js')).default + const Reveal = (await import('reveal.js')).default; const deck = new Reveal(elementRef.current, { embedded: true, keyboardCondition: 'focused', controlsTutorial: false, disableLayout: true, transition: 'fade', - }) + }); deck.initialize().then((_) => { // Hack for dev // deck.slide(6, 1); - revealInstance.current = deck - }) - })() - }, []) + revealInstance.current = deck; + }); + })(); + }, []); - return [revealInstance] as const + return [revealInstance] as const; } diff --git a/packages/reveal-framework/src/index.ts b/packages/reveal-framework/src/index.ts index bc9ab171..fda56c57 100644 --- a/packages/reveal-framework/src/index.ts +++ b/packages/reveal-framework/src/index.ts @@ -1,8 +1,8 @@ -export * from './components/slide-components' -export * from './components/slide-layout-builders' -export * from './components/slide-types' -export * from './hooks/useRevealFramework' +export * from './components/slide-components'; +export * from './components/slide-layout-builders'; +export * from './components/slide-types'; +export * from './hooks/useRevealFramework'; -import * as Presentation from '@revealjs/react' +import * as Presentation from '@revealjs/react'; -export { Presentation } +export { Presentation }; diff --git a/packages/reveal-framework/vitest.config.ts b/packages/reveal-framework/vitest.config.ts index 9b6bd972..c641e500 100644 --- a/packages/reveal-framework/vitest.config.ts +++ b/packages/reveal-framework/vitest.config.ts @@ -1,5 +1,5 @@ -import react from '@vitejs/plugin-react' -import { defineConfig } from 'vitest/config' +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vitest/config'; export default defineConfig({ plugins: [react()], @@ -8,4 +8,4 @@ export default defineConfig({ setupFiles: ['@ageorgedev/testing-config/react-jsdom-test-setup.ts'], include: ['src/**/*.spec.{ts,tsx}'], }, -}) +}); diff --git a/packages/talk-tailwind/src/diagrams/D01TailwindFilesize.tsx b/packages/talk-tailwind/src/diagrams/D01TailwindFilesize.tsx index 30fd967a..6d067c0f 100644 --- a/packages/talk-tailwind/src/diagrams/D01TailwindFilesize.tsx +++ b/packages/talk-tailwind/src/diagrams/D01TailwindFilesize.tsx @@ -1,4 +1,4 @@ -import { cn } from '@ageorgedev/toolbelt/cn' +import { cn } from '@ageorgedev/toolbelt/cn'; export function D01TailwindFilesize({ className, @@ -103,5 +103,5 @@ export function D01TailwindFilesize({ - ) + ); } diff --git a/packages/talk-tailwind/src/diagrams/D02CssPipeline.tsx b/packages/talk-tailwind/src/diagrams/D02CssPipeline.tsx index 1c76b1d5..d39851cf 100644 --- a/packages/talk-tailwind/src/diagrams/D02CssPipeline.tsx +++ b/packages/talk-tailwind/src/diagrams/D02CssPipeline.tsx @@ -1,4 +1,4 @@ -import { cn } from '@ageorgedev/toolbelt/cn' +import { cn } from '@ageorgedev/toolbelt/cn'; export function D02CssPipeline({ className, @@ -186,5 +186,5 @@ export function D02CssPipeline({ - ) + ); } diff --git a/packages/talk-tailwind/src/index.tsx b/packages/talk-tailwind/src/index.tsx index 4ebc4a8d..79122961 100644 --- a/packages/talk-tailwind/src/index.tsx +++ b/packages/talk-tailwind/src/index.tsx @@ -1,18 +1,18 @@ -import { TwLogo } from '@ageorgedev/brand-components' -import { BodyXl } from '@ageorgedev/design-system/typography/typography-components' -import { DeckFooter, Presentation } from '@ageorgedev/reveal-framework' -import { IconContext } from '@phosphor-icons/react' -import { S01Intro } from './slides/s01-intro' -import { S02Fundamentals } from './slides/s02-fundamentals' -import { S03AntiPatterns } from './slides/s03-anti-patterns' -import { S04LeanConfig } from './slides/s04-lean-config' -import { S04BCognitiveLoad } from './slides/s04b-cognitive-load' -import { S05ClassManagement } from './slides/s05-class-mgt' -import { S06Plugins } from './slides/s06-plugins' -import { S07OverrideTheming } from './slides/s07-override-theming' -import { S08DarkMode } from './slides/s08-dark-mode' -import { S09CssInJs } from './slides/s09-css-in-js' -import { S10Outro } from './slides/s10-outro' +import { TwLogo } from '@ageorgedev/brand-components'; +import { BodyXl } from '@ageorgedev/design-system/typography/typography-components'; +import { DeckFooter, Presentation } from '@ageorgedev/reveal-framework'; +import { IconContext } from '@phosphor-icons/react'; +import { S01Intro } from './slides/s01-intro'; +import { S02Fundamentals } from './slides/s02-fundamentals'; +import { S03AntiPatterns } from './slides/s03-anti-patterns'; +import { S04LeanConfig } from './slides/s04-lean-config'; +import { S04BCognitiveLoad } from './slides/s04b-cognitive-load'; +import { S05ClassManagement } from './slides/s05-class-mgt'; +import { S06Plugins } from './slides/s06-plugins'; +import { S07OverrideTheming } from './slides/s07-override-theming'; +import { S08DarkMode } from './slides/s08-dark-mode'; +import { S09CssInJs } from './slides/s09-css-in-js'; +import { S10Outro } from './slides/s10-outro'; export function TalkTailwind() { return ( @@ -42,7 +42,7 @@ export function TalkTailwind() { - ) + ); } const presentationConfig: Presentation.DeckProps['config'] = { @@ -51,6 +51,6 @@ const presentationConfig: Presentation.DeckProps['config'] = { controlsTutorial: false, disableLayout: true, transition: 'fade', -} +}; -export default TalkTailwind +export default TalkTailwind; diff --git a/packages/talk-tailwind/src/slides/s01-intro.tsx b/packages/talk-tailwind/src/slides/s01-intro.tsx index 2c104906..94b8c29f 100644 --- a/packages/talk-tailwind/src/slides/s01-intro.tsx +++ b/packages/talk-tailwind/src/slides/s01-intro.tsx @@ -1,4 +1,4 @@ -import { NameLogo } from '@ageorgedev/design-system/logo/NameLogo' +import { NameLogo } from '@ageorgedev/design-system/logo/NameLogo'; import { Heading1, Heading2, @@ -7,7 +7,7 @@ import { PBody, PBodyLg, PBodyMd, -} from '@ageorgedev/design-system/typography/typography-components' +} from '@ageorgedev/design-system/typography/typography-components'; import { CalloutForAdvancedTopic, CalloutForPersonalOpinion, @@ -16,7 +16,7 @@ import { Presentation, SlideTypeCenter, SlideTypeRegular, -} from '@ageorgedev/reveal-framework' +} from '@ageorgedev/reveal-framework'; export function S01Intro() { return ( @@ -124,5 +124,5 @@ export function S01Intro() { CSS-in-JS - ) + ); } diff --git a/packages/talk-tailwind/src/slides/s02-fundamentals.tsx b/packages/talk-tailwind/src/slides/s02-fundamentals.tsx index d5934474..e4197e77 100644 --- a/packages/talk-tailwind/src/slides/s02-fundamentals.tsx +++ b/packages/talk-tailwind/src/slides/s02-fundamentals.tsx @@ -1,10 +1,10 @@ -import { CodeBlock } from '@ageorgedev/design-system/CodeBlock/CodeBlock' +import { CodeBlock } from '@ageorgedev/design-system/CodeBlock/CodeBlock'; import { Heading1, Heading3, PBody, PBodyMd, -} from '@ageorgedev/design-system/typography/typography-components' +} from '@ageorgedev/design-system/typography/typography-components'; import { ComparisonRow, ImportantNote, @@ -12,8 +12,8 @@ import { SlideMediaRow, SlideTypeCenter, SlideTypeRegular, -} from '@ageorgedev/reveal-framework' -import { D01TailwindFilesize } from '../diagrams/D01TailwindFilesize' +} from '@ageorgedev/reveal-framework'; +import { D01TailwindFilesize } from '../diagrams/D01TailwindFilesize'; export function S02Fundamentals() { return ( @@ -172,7 +172,7 @@ export function S02Fundamentals() { - ) + ); } // Code snippets @@ -180,12 +180,12 @@ export function S02Fundamentals() { const simpleButtonTraditional = ` -` +`; const simpleButtonTailwind = `` +`; const simpleButtonComputedStyles = `.text-white { color: white; } @@ -206,7 +206,7 @@ const simpleButtonComputedStyles = `.text-white { color: white; } .mb-2 { margin-bottom: 0.5rem; -}` +}`; const tailwindConfigFile = `// tailwind.config.js @@ -229,7 +229,7 @@ export default { massive: "70px", }, }, -}` +}`; const configDerivedHtml = ` @@ -237,18 +237,18 @@ const configDerivedHtml = ` -` +`; const exampleHtml = `

Hello world!

-` +`; const sourceStyleFile = `/* styles.css */ @tailwind base; @tailwind components; -@tailwind utilities;` +@tailwind utilities;`; const computedStyleFile = `/* dist/styles.css */ @@ -262,4 +262,4 @@ const computedStyleFile = `/* dist/styles.css */ .text-purple { --tw-text-opacity: 1; color: rgb(63 60 187 / var(--tw-text-opacity)); -}` +}`; diff --git a/packages/talk-tailwind/src/slides/s03-anti-patterns.tsx b/packages/talk-tailwind/src/slides/s03-anti-patterns.tsx index 18d8e4c1..f052c94b 100644 --- a/packages/talk-tailwind/src/slides/s03-anti-patterns.tsx +++ b/packages/talk-tailwind/src/slides/s03-anti-patterns.tsx @@ -1,16 +1,16 @@ -import { CodeBlock } from '@ageorgedev/design-system/CodeBlock/CodeBlock' +import { CodeBlock } from '@ageorgedev/design-system/CodeBlock/CodeBlock'; import { Heading1, Heading2, PBody, PBodyMd, -} from '@ageorgedev/design-system/typography/typography-components' +} from '@ageorgedev/design-system/typography/typography-components'; import { PointSeperator, SlideTypeCenter, SlideTypeRegular, -} from '@ageorgedev/reveal-framework' -import { ArrowRightIcon } from '@phosphor-icons/react' +} from '@ageorgedev/reveal-framework'; +import { ArrowRightIcon } from '@phosphor-icons/react'; export function S03AntiPatterns() { return ( @@ -90,7 +90,7 @@ export function S03AntiPatterns() { >
- ) + ); } // Code blocks @@ -104,7 +104,7 @@ const applyAntiPatternSourceStyles = `/* style.css */ .my-btn:hover { @apply bg-blue-900; -}` +}`; const applyAntiPatternSourceComputedStyles = `/* dist/style.css */ @@ -125,21 +125,21 @@ const applyAntiPatternSourceComputedStyles = `/* dist/style.css */ .my-btn:hover { --tw-bg-opacity: 1; background-color: rgb(30 58 138 / var(--tw-bg-opacity)); -}` +}`; const applyAntiPatternHtml = ` -` +`; const oneOffHtml = `
Search -
` +`; const oneOffComputedCss = `.mt-\\[3px\\] { margin-top: 3px; -}` +}`; const oneOffBadHtml = `
Oh my god, my eyes!! -
` +`; diff --git a/packages/talk-tailwind/src/slides/s04-lean-config.tsx b/packages/talk-tailwind/src/slides/s04-lean-config.tsx index 64273e0d..16bb46be 100644 --- a/packages/talk-tailwind/src/slides/s04-lean-config.tsx +++ b/packages/talk-tailwind/src/slides/s04-lean-config.tsx @@ -1,16 +1,16 @@ -import { CodeBlock } from '@ageorgedev/design-system/CodeBlock/CodeBlock' +import { CodeBlock } from '@ageorgedev/design-system/CodeBlock/CodeBlock'; import { Heading1, Heading2, Heading3, Interface, PBody, -} from '@ageorgedev/design-system/typography/typography-components' +} from '@ageorgedev/design-system/typography/typography-components'; import { ComparisonRow, SlideTypeCenter, SlideTypeRegular, -} from '@ageorgedev/reveal-framework' +} from '@ageorgedev/reveal-framework'; export function S04LeanConfig() { return ( @@ -199,7 +199,7 @@ export function S04LeanConfig() { > - ) + ); } // Code blocks @@ -216,7 +216,7 @@ export default { }, }, }, -};` +};`; const overridingConfig = `/* tailwind.config.js */ @@ -233,7 +233,7 @@ export default { // ... non-specified sets picked up // from tailwind defaults }, -};` +};`; const removingCorePlugins = `/* tailwind.config.js */ @@ -244,7 +244,7 @@ export default { zIndex: false, // we don't mess with z-index animation: false // will be custom anyway }, -};` +};`; function RSpaceBar({ className, name }: { className?: string; name: string }) { return ( @@ -252,7 +252,7 @@ function RSpaceBar({ className, name }: { className?: string; name: string }) { {name} - ) + ); } function LSpaceBar({ className, name }: { className?: string; name: string }) { @@ -261,9 +261,9 @@ function LSpaceBar({ className, name }: { className?: string; name: string }) { {name} - ) + ); } function Bar({ className }: { className?: string }) { - return
+ return
; } diff --git a/packages/talk-tailwind/src/slides/s04b-cognitive-load.tsx b/packages/talk-tailwind/src/slides/s04b-cognitive-load.tsx index e39245b0..981115d0 100644 --- a/packages/talk-tailwind/src/slides/s04b-cognitive-load.tsx +++ b/packages/talk-tailwind/src/slides/s04b-cognitive-load.tsx @@ -1,4 +1,4 @@ -import { CodeBlock } from '@ageorgedev/design-system/CodeBlock/CodeBlock' +import { CodeBlock } from '@ageorgedev/design-system/CodeBlock/CodeBlock'; import { Heading1, Heading2, @@ -6,9 +6,12 @@ import { PBody, PBodyLg, PBodyMd, -} from '@ageorgedev/design-system/typography/typography-components' -import { SlideTypeCenter, SlideTypeRegular } from '@ageorgedev/reveal-framework' -import { ArrowRightIcon } from '@phosphor-icons/react' +} from '@ageorgedev/design-system/typography/typography-components'; +import { + SlideTypeCenter, + SlideTypeRegular, +} from '@ageorgedev/reveal-framework'; +import { ArrowRightIcon } from '@phosphor-icons/react'; export function S04BCognitiveLoad() { return ( @@ -69,7 +72,7 @@ export function S04BCognitiveLoad() { Over time, devs adapt to it, but the cost is present - ) + ); } const imaginaryCSS = `.imaginary-selector { @@ -82,9 +85,9 @@ const imaginaryCSS = `.imaginary-selector { display: flex; justify-content: center; -}` +}`; const imaginaryHTML = `
-
` +`; diff --git a/packages/talk-tailwind/src/slides/s05-class-mgt.tsx b/packages/talk-tailwind/src/slides/s05-class-mgt.tsx index dc1bff95..3e05dbd5 100644 --- a/packages/talk-tailwind/src/slides/s05-class-mgt.tsx +++ b/packages/talk-tailwind/src/slides/s05-class-mgt.tsx @@ -1,16 +1,16 @@ -import { CodeBlock } from '@ageorgedev/design-system/CodeBlock/CodeBlock' +import { CodeBlock } from '@ageorgedev/design-system/CodeBlock/CodeBlock'; import { Heading1, Heading2, Heading3, Interface2Xl, PBody, -} from '@ageorgedev/design-system/typography/typography-components' +} from '@ageorgedev/design-system/typography/typography-components'; import { ComparisonRow, SlideTypeCenter, SlideTypeRegular, -} from '@ageorgedev/reveal-framework' +} from '@ageorgedev/reveal-framework'; export function S05ClassManagement() { return ( @@ -90,7 +90,7 @@ export function S05ClassManagement() { - ) + ); } // Code blocks @@ -104,7 +104,7 @@ const MoreStyles = cls("shadow-sm px-2", otherStyles); const Variants = { primary: 'bg-primary-300 hover:bg-primary-400', secondary: 'bg-secondary-200 hover:text-white' -}` +}`; const reactComponent = `export function Card({ className, ...otherProps @@ -117,16 +117,16 @@ const reactComponent = `export function Card({ \${className ?? ''}\`} /> ); -}` +}`; const angularComponent = `@Directive({ selector: '[appCard]', }) export class CardDirective { @HostBinding('class') classes = 'shadow border-gray border-2 mb-4'; -}` +}`; -const angularComponentExample = `
Notes
` +const angularComponentExample = `
Notes
`; const advancedComponent = `type Heading1Props = React.PropsWithChildren & React.HTMLAttributes; @@ -143,7 +143,7 @@ function Heading1({ ); } -Heading1.classes = 'text-2xl font-bold md:text-3xl';` +Heading1.classes = 'text-2xl font-bold md:text-3xl';`; const advancedComponentUsage = `Hello There @@ -152,7 +152,7 @@ const advancedComponentUsage = `Hello There

A section with common styling

Pretty nifty

-` +`; const fullTailwindWithoutComponents = `
User Avatar @@ -175,7 +175,7 @@ const fullTailwindWithoutComponents = `
@@ -197,4 +197,4 @@ const fixedTailwindWithComponents = ` Photographer
-` +`; diff --git a/packages/talk-tailwind/src/slides/s06-plugins.tsx b/packages/talk-tailwind/src/slides/s06-plugins.tsx index 63f5479c..6bbabee9 100644 --- a/packages/talk-tailwind/src/slides/s06-plugins.tsx +++ b/packages/talk-tailwind/src/slides/s06-plugins.tsx @@ -1,17 +1,17 @@ -import { CodeBlock } from '@ageorgedev/design-system/CodeBlock/CodeBlock' +import { CodeBlock } from '@ageorgedev/design-system/CodeBlock/CodeBlock'; import { Heading1, Heading2, Heading3, PBody, PBodyMd, -} from '@ageorgedev/design-system/typography/typography-components' +} from '@ageorgedev/design-system/typography/typography-components'; import { ComparisonRow, ImportantNote, SlideTypeCenter, SlideTypeRegular, -} from '@ageorgedev/reveal-framework' +} from '@ageorgedev/reveal-framework'; export function S06Plugins() { return ( @@ -161,7 +161,7 @@ export function S06Plugins() { > - ) + ); } const basicPluginInstallation = `/* tailwind.config.js */ @@ -174,7 +174,7 @@ module.exports = { // Add your custom styles here }), ] -}` +}`; const typographyPlugin = `plugin(({ addBase, theme }) => { addBase({ @@ -195,11 +195,11 @@ const typographyPlugin = `plugin(({ addBase, theme }) => { textTransform: 'uppercase', } }) -})` +})`; const typographyUsage = ` Some content -` +`; const ngInvalidPlugin = `plugin(({ addVariant }) => { // Overwrite the default invalid:* modifier to support angular classes @@ -209,9 +209,9 @@ const ngInvalidPlugin = `plugin(({ addVariant }) => { addVariant('group-invalid', [':merge(.group).ng-invalid.ng-touched &', ':merge(.group):invalid &']); addVariant('peer-invalid', [':merge(.peer).ng-invalid.ng-touched ~ &', ':merge(.peer):invalid ~ &']); }); -` +`; -const ngInvalidPluginUsage = `` +const ngInvalidPluginUsage = ``; const layerStylesCss = `@tailwind base; @tailwind components; @@ -235,14 +235,14 @@ const layerStylesCss = `@tailwind base; .area-header { grid-area: header; } .area-main { grid-area: main; } -}` +}`; const layerStylesHtml = `
header
main
footer
-
` +`; const matchComponentsPlugin = `const gridPlugin = plugin(({ matchComponents, theme }) => { matchComponents( @@ -256,7 +256,7 @@ const matchComponentsPlugin = `const gridPlugin = plugin(({ matchComponents, the }, { values: theme("layouts") } ); -});` +});`; const matchComponentsLayout = `plugins: [gridPlugin], theme: { @@ -277,4 +277,4 @@ theme: { ], }, }, -},` +},`; diff --git a/packages/talk-tailwind/src/slides/s07-override-theming.tsx b/packages/talk-tailwind/src/slides/s07-override-theming.tsx index cd2a7d0a..2b447238 100644 --- a/packages/talk-tailwind/src/slides/s07-override-theming.tsx +++ b/packages/talk-tailwind/src/slides/s07-override-theming.tsx @@ -1,17 +1,17 @@ -import { CodeBlock } from '@ageorgedev/design-system/CodeBlock/CodeBlock' +import { CodeBlock } from '@ageorgedev/design-system/CodeBlock/CodeBlock'; import { Heading1, Heading2, Heading3, PBody, PBodyMd, -} from '@ageorgedev/design-system/typography/typography-components' +} from '@ageorgedev/design-system/typography/typography-components'; import { ComparisonRow, ImportantNote, SlideTypeCenter, SlideTypeRegular, -} from '@ageorgedev/reveal-framework' +} from '@ageorgedev/reveal-framework'; export function S07OverrideTheming() { return ( @@ -132,7 +132,7 @@ export function S07OverrideTheming() { > - ) + ); } const plainColorsConfig = `/* tailwind.config.js */ @@ -152,7 +152,7 @@ export default { } } }, -};` +};`; const colorVarsRoot = `/* styles.css */ @layer base { @@ -161,7 +161,7 @@ const colorVarsRoot = `/* styles.css */ --color-primary-base: hsl(321 12% 29%); --color-primary-dark: hsl(119 22% 44%); } -}` +}`; const colorVarsUsage = `/* tailwind.config.js */ @@ -180,23 +180,23 @@ export default { } } }, -};` +};`; const colorVarsWithDefaults = `primary: { DEFAULT: "var(--color-primary-base, hsl(321 12% 29%))", light: "var(--color-primary-light, hsl(321 12% 48%))", dark: "var(--color-primary-dark, hsl(119 22% 44%))", -}` +}`; -const tailwindColorOpacityModifiers = `
...
` +const tailwindColorOpacityModifiers = `
...
`; const colorVarsRootChannels = `:root { --color-primary-base: 207 49% 65%; -}` +}`; const colorVarsWithDefaultsChannels = `primary: { DEFAULT: "hsl( var(--color-primary-base) / )", -}` +}`; const modernHslSyntax = `.selector { /* hsl( ) */ @@ -204,4 +204,4 @@ const modernHslSyntax = `.selector { /* hsl( / ) */ background: hsl(205 30% 90% / .4) -}` +}`; diff --git a/packages/talk-tailwind/src/slides/s08-dark-mode.tsx b/packages/talk-tailwind/src/slides/s08-dark-mode.tsx index cea4e950..2fafd679 100644 --- a/packages/talk-tailwind/src/slides/s08-dark-mode.tsx +++ b/packages/talk-tailwind/src/slides/s08-dark-mode.tsx @@ -1,4 +1,4 @@ -import { CodeBlock } from '@ageorgedev/design-system/CodeBlock/CodeBlock' +import { CodeBlock } from '@ageorgedev/design-system/CodeBlock/CodeBlock'; import { Heading1, Heading2, @@ -7,10 +7,13 @@ import { InterfaceXl, PBodyLg, PBodyXs, -} from '@ageorgedev/design-system/typography/typography-components' -import { SlideTypeCenter, SlideTypeRegular } from '@ageorgedev/reveal-framework' -import { cn } from '@ageorgedev/toolbelt/cn' -import { WarningIcon } from '@phosphor-icons/react' +} from '@ageorgedev/design-system/typography/typography-components'; +import { + SlideTypeCenter, + SlideTypeRegular, +} from '@ageorgedev/reveal-framework'; +import { cn } from '@ageorgedev/toolbelt/cn'; +import { WarningIcon } from '@phosphor-icons/react'; export function S08DarkMode() { return ( @@ -180,13 +183,13 @@ export function S08DarkMode() { - ) + ); } function Swatch({ className }: { className?: string }) { return (
- ) + ); } // Code blocks @@ -194,15 +197,15 @@ const darkModeConfig = `/* tailwind.config.js */ export default { darkMode: 'class' // ... rest of the config -}` +}`; const darkModeHtml = ` -` +`; const darkModeBasicUsage = `` +`; const rawColorsRoot = `:root { --rc-parchment-500: hsl(39 100% 89%); @@ -213,21 +216,21 @@ const rawColorsRoot = `:root { --rc-d-neutral-500: hsl(189 67% 6%); --rc-l-neutral-500: hsl(45 100% 99%); -}` +}`; const contextualColorsRootLight = `:root { --page-far: var(--rc-parchment-500); --accent: var(--rc-p-accent-500); --neutral: var(--rc-d-neutral-500); --neutral-inverse: var(--rc-l-neutral-500); -}` +}`; const contextualColorsRootDark = `:root.dark { --page-far: var(--rc-timber-300); --accent: var(--rc-p-accent-300); --neutral: var(--rc-l-neutral-500); --neutral-inverse: var(--rc-d-neutral-500); -}` +}`; const contextualColorsTailwindConfig = `/* tailwind.config.js */ @@ -240,13 +243,13 @@ export default { //... others } } -}` +}`; const contextualColorsUsage = `
-
` +`; const contextualColorsProblem = `
-
` +
`; diff --git a/packages/talk-tailwind/src/slides/s09-css-in-js.tsx b/packages/talk-tailwind/src/slides/s09-css-in-js.tsx index 2206a3cb..98caf4d3 100644 --- a/packages/talk-tailwind/src/slides/s09-css-in-js.tsx +++ b/packages/talk-tailwind/src/slides/s09-css-in-js.tsx @@ -1,19 +1,19 @@ -import { CodeBlock } from '@ageorgedev/design-system/CodeBlock/CodeBlock' +import { CodeBlock } from '@ageorgedev/design-system/CodeBlock/CodeBlock'; import { Heading1, Heading2, Heading3, PBody, PBodyMd, -} from '@ageorgedev/design-system/typography/typography-components' +} from '@ageorgedev/design-system/typography/typography-components'; import { ImportantNote, PointSeperator, SlideMediaRow, SlideTypeCenter, SlideTypeRegular, -} from '@ageorgedev/reveal-framework' -import { D02CssPipeline } from '../diagrams/D02CssPipeline' +} from '@ageorgedev/reveal-framework'; +import { D02CssPipeline } from '../diagrams/D02CssPipeline'; export function S09CssInJs() { return ( @@ -101,7 +101,7 @@ export function S09CssInJs() { - ) + ); } // Code blocks @@ -115,4 +115,4 @@ fullConfig.theme.width[4] // => '1rem' fullConfig.theme.screens.md -// => '768px'` +// => '768px'`; diff --git a/packages/talk-tailwind/src/slides/s10-outro.tsx b/packages/talk-tailwind/src/slides/s10-outro.tsx index 96a1ec78..b2de7fd6 100644 --- a/packages/talk-tailwind/src/slides/s10-outro.tsx +++ b/packages/talk-tailwind/src/slides/s10-outro.tsx @@ -5,12 +5,12 @@ import { PBody, PBodyLg, PBodyMd, -} from '@ageorgedev/design-system/typography/typography-components' +} from '@ageorgedev/design-system/typography/typography-components'; import { PointSeperator, SlideTypeCenter, SlideTypeRegular, -} from '@ageorgedev/reveal-framework' +} from '@ageorgedev/reveal-framework'; export function S10Outro() { return ( @@ -58,5 +58,5 @@ export function S10Outro() { - ) + ); } diff --git a/packages/testing-config/react-jsdom-test-setup.ts b/packages/testing-config/react-jsdom-test-setup.ts index db5e6424..7a5898c3 100644 --- a/packages/testing-config/react-jsdom-test-setup.ts +++ b/packages/testing-config/react-jsdom-test-setup.ts @@ -1,7 +1,7 @@ // import '@testing-library/jest-dom/vitest'; -import * as matchers from '@testing-library/jest-dom/matchers' -import { cleanup } from '@testing-library/react' -import { afterEach, expect } from 'vitest' +import * as matchers from '@testing-library/jest-dom/matchers'; +import { cleanup } from '@testing-library/react'; +import { afterEach, expect } from 'vitest'; -expect.extend(matchers) -afterEach(cleanup) +expect.extend(matchers); +afterEach(cleanup); diff --git a/packages/toolbelt/src/cn.spec.ts b/packages/toolbelt/src/cn.spec.ts index 14e148ef..a8b4aa0f 100644 --- a/packages/toolbelt/src/cn.spec.ts +++ b/packages/toolbelt/src/cn.spec.ts @@ -1,36 +1,36 @@ -import { describe, expect, it } from 'vitest' -import { cn } from './cn' +import { describe, expect, it } from 'vitest'; +import { cn } from './cn'; describe('cn', () => { it('merges multiple class strings', () => { - expect(cn('foo', 'bar')).toBe('foo bar') - }) + expect(cn('foo', 'bar')).toBe('foo bar'); + }); it('handles a single class', () => { - expect(cn('only')).toBe('only') - }) + expect(cn('only')).toBe('only'); + }); it('omits false conditions', () => { - expect(cn('base', false && 'conditional')).toBe('base') - }) + expect(cn('base', false && 'conditional')).toBe('base'); + }); it('includes truthy conditions', () => { - expect(cn('base', true && 'active')).toBe('base active') - }) + expect(cn('base', true && 'active')).toBe('base active'); + }); it('resolves Tailwind padding conflicts — last wins', () => { - expect(cn('p-4', 'p-8')).toBe('p-8') - }) + expect(cn('p-4', 'p-8')).toBe('p-8'); + }); it('resolves Tailwind text colour conflicts — last wins', () => { - expect(cn('text-red-500', 'text-blue-500')).toBe('text-blue-500') - }) + expect(cn('text-red-500', 'text-blue-500')).toBe('text-blue-500'); + }); it('accepts object syntax', () => { - expect(cn({ active: true, disabled: false })).toBe('active') - }) + expect(cn({ active: true, disabled: false })).toBe('active'); + }); it('accepts array syntax', () => { - expect(cn(['foo', 'bar'])).toBe('foo bar') - }) -}) + expect(cn(['foo', 'bar'])).toBe('foo bar'); + }); +}); diff --git a/packages/toolbelt/src/cn.ts b/packages/toolbelt/src/cn.ts index d32b0fe6..9ad0df42 100644 --- a/packages/toolbelt/src/cn.ts +++ b/packages/toolbelt/src/cn.ts @@ -1,6 +1,6 @@ -import { type ClassValue, clsx } from 'clsx' -import { twMerge } from 'tailwind-merge' +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) + return twMerge(clsx(inputs)); } diff --git a/packages/toolbelt/src/ramda-additions.spec.ts b/packages/toolbelt/src/ramda-additions.spec.ts index c24f0f91..0a9d389e 100644 --- a/packages/toolbelt/src/ramda-additions.spec.ts +++ b/packages/toolbelt/src/ramda-additions.spec.ts @@ -1,5 +1,5 @@ -import { describe, expect, it } from 'vitest' -import { mapKeys } from './ramda-additions' +import { describe, expect, it } from 'vitest'; +import { mapKeys } from './ramda-additions'; describe('mapKeys', () => { it('works directly', () => { @@ -8,11 +8,11 @@ describe('mapKeys', () => { name__test: 'test', age__test: 10, } - ) - }) + ); + }); it('works in curried format', () => { - const pref = mapKeys((k) => `test--${k}`) + const pref = mapKeys((k) => `test--${k}`); expect( pref({ a: { @@ -25,6 +25,6 @@ describe('mapKeys', () => { name: 'test', }, 'test--b': 'something', - }) - }) -}) + }); + }); +}); diff --git a/packages/toolbelt/src/ramda-additions.ts b/packages/toolbelt/src/ramda-additions.ts index e9deeac0..f047aeec 100644 --- a/packages/toolbelt/src/ramda-additions.ts +++ b/packages/toolbelt/src/ramda-additions.ts @@ -1,6 +1,6 @@ -import { compose, curryN, fromPairs, map, toPairs } from 'ramda' +import { compose, curryN, fromPairs, map, toPairs } from 'ramda'; -type KeyT = string +type KeyT = string; function _mapKeys( mapper: (key: KeyT) => string, @@ -10,6 +10,6 @@ function _mapKeys( fromPairs, map(([key, value]: [KeyT, S]) => [mapper(key), value] as const), toPairs - )(obj) + )(obj); } -export const mapKeys = curryN(2, _mapKeys) +export const mapKeys = curryN(2, _mapKeys); diff --git a/packages/toolbelt/vitest.config.ts b/packages/toolbelt/vitest.config.ts index daf21ceb..aacc4e15 100644 --- a/packages/toolbelt/vitest.config.ts +++ b/packages/toolbelt/vitest.config.ts @@ -1,8 +1,8 @@ -import { defineConfig } from 'vitest/config' +import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { environment: 'node', include: ['src/**/*.spec.ts'], }, -}) +}); diff --git a/packages/ts-config/css-modules.d.ts b/packages/ts-config/css-modules.d.ts index f5e4b38d..5a6040ca 100644 --- a/packages/ts-config/css-modules.d.ts +++ b/packages/ts-config/css-modules.d.ts @@ -1,13 +1,13 @@ declare module '*.module.css' { - const classes: { [key: string]: string } - export default classes + const classes: { [key: string]: string }; + export default classes; } declare module '*.css' { - const value: string - export default value + const value: string; + export default value; } declare module '*.css?url' { - const value: string - export default value + const value: string; + export default value; } diff --git a/packages/ts-config/react-globals.d.ts b/packages/ts-config/react-globals.d.ts index 10ad759a..38b48d57 100644 --- a/packages/ts-config/react-globals.d.ts +++ b/packages/ts-config/react-globals.d.ts @@ -1,14 +1,14 @@ // biome-ignore-all lint/suspicious/noExplicitAny: Easiest way to handle svg -import 'react' +import 'react'; declare module '*.svg' { - const content: any - export const ReactComponent: any - export default content + const content: any; + export const ReactComponent: any; + export default content; } declare module 'react' { interface CSSProperties { - [key: `--${string}`]: string | number + [key: `--${string}`]: string | number; } } diff --git a/syncpackrc.cjs b/syncpackrc.cjs index 4707154c..4f99f737 100644 --- a/syncpackrc.cjs +++ b/syncpackrc.cjs @@ -29,6 +29,6 @@ const config = { packages: ['**'], }, ], -} +}; -module.exports = config +module.exports = config; From 6d6a2459b0525f8124f8b6e30d18ef7abc88193e Mon Sep 17 00:00:00 2001 From: Anish George Date: Wed, 17 Jun 2026 16:19:54 +1000 Subject: [PATCH 02/29] feat: Initial cut of feature resources --- .../src/characters/example-wizard.data.ts | 16 +++++-- .../components/SheetHeader/SheetHeader.tsx | 22 +++++++-- .../src/lib/calculate-derived-stats.ts | 7 ++- .../src/lib/models/character.ts | 10 ++-- .../src/lib/models/derived-stats.ts | 1 + .../src/lib/models/feature.ts | 46 +++++++++++++++++++ 6 files changed, 92 insertions(+), 10 deletions(-) diff --git a/packages/dnd-character-sheet/src/characters/example-wizard.data.ts b/packages/dnd-character-sheet/src/characters/example-wizard.data.ts index 5e8a82c5..4cc1f8de 100644 --- a/packages/dnd-character-sheet/src/characters/example-wizard.data.ts +++ b/packages/dnd-character-sheet/src/characters/example-wizard.data.ts @@ -4,9 +4,19 @@ import { Skill } from '../lib/models/skills'; export const exampleWizard: Character = { name: 'Seraphina Ashveil', - class: 'Wizard / Warlock', - subclass: 'School of Evocation / The Archfey', - level: 7, + classes: [ + { + class: 'Wizard', + subclass: 'School of Evocation', + level: 4, + }, + { + class: 'Warlock', + subclass: 'The Archfey', + level: 3, + }, + ], + species: 'High Elf', background: 'Sage', alignment: 'Neutral Good', diff --git a/packages/dnd-character-sheet/src/components/SheetHeader/SheetHeader.tsx b/packages/dnd-character-sheet/src/components/SheetHeader/SheetHeader.tsx index 84690745..0049e1a6 100644 --- a/packages/dnd-character-sheet/src/components/SheetHeader/SheetHeader.tsx +++ b/packages/dnd-character-sheet/src/components/SheetHeader/SheetHeader.tsx @@ -28,9 +28,13 @@ function NameBlock() { >

{character.name}

{character.background} - {character.class} + + {joinItems(character.classes, 'class')} + {character.species} - {character.subclass} + + {joinItems(character.classes, 'subclass')} + ); } @@ -40,7 +44,9 @@ function LevelBlock() { return (
- {character.level} + + {character.classes.reduce((total, cls) => total + cls.level, 0)} + Level
Xp @@ -157,3 +163,13 @@ function BasicLabel({
); } + +function joinItems< + T extends Record, + K extends keyof T, +>(items: T[], key: K) { + return items + .map((item) => item[key]) + .filter((value) => value != null) + .join(' / '); +} diff --git a/packages/dnd-character-sheet/src/lib/calculate-derived-stats.ts b/packages/dnd-character-sheet/src/lib/calculate-derived-stats.ts index 93370e71..22404b9d 100644 --- a/packages/dnd-character-sheet/src/lib/calculate-derived-stats.ts +++ b/packages/dnd-character-sheet/src/lib/calculate-derived-stats.ts @@ -16,7 +16,11 @@ function proficiencyBonus(level: number): number { } export function calculateStats(character: Character): DerivedStats { - const profBonus = proficiencyBonus(character.level); + const characterLevel = character.classes.reduce( + (sum, cls) => sum + cls.level, + 0 + ); + const profBonus = proficiencyBonus(characterLevel); const abilityModifiers = Object.fromEntries( ALL_ABILITIES.map((name) => [ @@ -66,6 +70,7 @@ export function calculateStats(character: Character): DerivedStats { skills, initiative, passivePerception, + characterLevel, ...spellcasting, }; } diff --git a/packages/dnd-character-sheet/src/lib/models/character.ts b/packages/dnd-character-sheet/src/lib/models/character.ts index 17022dfc..321bdbb1 100644 --- a/packages/dnd-character-sheet/src/lib/models/character.ts +++ b/packages/dnd-character-sheet/src/lib/models/character.ts @@ -7,11 +7,15 @@ import type { Spellcasting } from './spellcasting'; export interface Character { // Identity name: string; - class: string; - subclass?: string; - level: number; species: string; background: string; + + classes: { + class: string; + subclass?: string; + level: number; + }[]; + alignment?: string; experiencePoints: number; diff --git a/packages/dnd-character-sheet/src/lib/models/derived-stats.ts b/packages/dnd-character-sheet/src/lib/models/derived-stats.ts index f56708c8..d9814ab3 100644 --- a/packages/dnd-character-sheet/src/lib/models/derived-stats.ts +++ b/packages/dnd-character-sheet/src/lib/models/derived-stats.ts @@ -8,6 +8,7 @@ export interface DerivedStats { skills: Record; passivePerception: number; initiative: number; + characterLevel: number; spellSaveDC?: number; spellAttackBonus?: number; } diff --git a/packages/dnd-character-sheet/src/lib/models/feature.ts b/packages/dnd-character-sheet/src/lib/models/feature.ts index 62b80a4a..56f57fc8 100644 --- a/packages/dnd-character-sheet/src/lib/models/feature.ts +++ b/packages/dnd-character-sheet/src/lib/models/feature.ts @@ -1,5 +1,51 @@ +import type { Ability } from './abilities'; + export interface Feature { name: string; source: string; description: string; + + // Either it uses a resource + cost?: Cost; + + // or introduces a new resource + resource?: { + name: string; + count: ResourceCount; + refresh: Refresh; + }; } + +type ResourceCount = { + display?: 'dots' | 'numeric'; +} & ( + | { + kind: 'character-level'; + multiplier?: number; + } + | { + kind: 'class-level'; + class: string; + multiplier?: number; + } + | { + kind: 'ability'; + ability: Ability; + multiplier?: number; + } + | { + kind: 'fixed'; + count: number; + } +); + +type Cost = string; + +type Refresh = + | { + kind: 'short-rest' | 'long-rest'; + } + | { + kind: 'short-and-long-rest'; + numberOfRefreshesOnShortRest: number; + }; From 52436c6af94435751e61b842b6f50f6d85463898 Mon Sep 17 00:00:00 2001 From: Anish George Date: Wed, 17 Jun 2026 21:31:51 +1000 Subject: [PATCH 03/29] chore: wip --- .../src/characters/example-wizard.data.ts | 21 ++++++- .../src/components/Resources/Resources.tsx | 61 +++++++++++++++++++ .../src/lib/models/feature.ts | 1 + 3 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 packages/dnd-character-sheet/src/components/Resources/Resources.tsx diff --git a/packages/dnd-character-sheet/src/characters/example-wizard.data.ts b/packages/dnd-character-sheet/src/characters/example-wizard.data.ts index 4cc1f8de..88a484ac 100644 --- a/packages/dnd-character-sheet/src/characters/example-wizard.data.ts +++ b/packages/dnd-character-sheet/src/characters/example-wizard.data.ts @@ -6,8 +6,8 @@ export const exampleWizard: Character = { name: 'Seraphina Ashveil', classes: [ { - class: 'Wizard', - subclass: 'School of Evocation', + class: 'Sorcerer', + subclass: 'Wild Magic', level: 4, }, { @@ -93,6 +93,23 @@ export const exampleWizard: Character = { }, features: [ + { + name: 'Font of Magic', + source: 'Sorcerer 2', + description: + 'You can use a bonus action to convert sorcery points into spell slots, or vice versa.', + resource: { + name: 'Sorcery Points', + count: { + kind: 'class-level', + class: 'Sorcerer', + multiplier: 1, + }, + refresh: { + kind: 'long-rest', + }, + }, + }, { name: 'Arcane Recovery', source: 'Wizard 1', diff --git a/packages/dnd-character-sheet/src/components/Resources/Resources.tsx b/packages/dnd-character-sheet/src/components/Resources/Resources.tsx new file mode 100644 index 00000000..1a1250e3 --- /dev/null +++ b/packages/dnd-character-sheet/src/components/Resources/Resources.tsx @@ -0,0 +1,61 @@ +import type { Character } from '../../lib/models/character'; +import type { DerivedStats } from '../../lib/models/derived-stats'; +import type { Feature } from '../../lib/models/feature'; +import { useCharacter } from '../CharacterSheet'; + +type Resource = NonNullable; + +export function Resources() { + const { character } = useCharacter(); +} + +function pullAllResourcesFromFeatures(character: Character) { + return [ + ...character.features, + ...(character.speciesTraits ?? []), + ...(character.feats ?? []), + ] + .map((feature) => feature.resource) + .filter(Boolean) as Resource[]; +} + +function resourceDigester(character: Character, stats: DerivedStats) { + return function digest(resource: Resource) { + const base = { + name: resource.name, + display: resource.count.display ?? 'dots', + refresh: resource.refresh, + }; + + const config = resource.count; + + switch (config.kind) { + case 'character-level': { + return { + ...base, + value: stats.characterLevel * (config.multiplier ?? 1), + }; + } + case 'class-level': { + const cls = character.classes.find((c) => c.class === config.class); + if (!cls) { + console.warn( + `Resource ${resource.name} depends on class ${config.class} which the character doesn't have.` + ); + return null; + } + return { + ...base, + value: cls.level * (config.multiplier ?? 1), + }; + } + case 'ability': { + const mod = stats.abilityModifiers[config.ability]; + return { + ...base, + value: Math.max(mod * (config.multiplier ?? 1), config.min ?? 0), + }; + } + } + }; +} diff --git a/packages/dnd-character-sheet/src/lib/models/feature.ts b/packages/dnd-character-sheet/src/lib/models/feature.ts index 56f57fc8..03243000 100644 --- a/packages/dnd-character-sheet/src/lib/models/feature.ts +++ b/packages/dnd-character-sheet/src/lib/models/feature.ts @@ -31,6 +31,7 @@ type ResourceCount = { | { kind: 'ability'; ability: Ability; + min?: number; multiplier?: number; } | { From a730eb280c51e311fab3fe7cf36db45936b41720 Mon Sep 17 00:00:00 2001 From: Anish George Date: Wed, 17 Jun 2026 22:42:19 +1000 Subject: [PATCH 04/29] feat: Introduce basic resources --- apps/game-tools/src/routeTree.gen.ts | 32 ++- .../routes/_public/dnd/characters/index.tsx | 7 + .../_public/dnd/characters/omarin-kenate.tsx | 201 ++++++++++++++++++ .../ClassFeatures/ClassFeatures.tsx | 8 +- .../src/components/Resources/Resources.tsx | 50 ++++- .../StandardCharacterSheet.tsx | 2 + packages/dnd-character-sheet/src/index.ts | 6 + .../src/lib/models/feature.ts | 8 +- 8 files changed, 304 insertions(+), 10 deletions(-) create mode 100644 apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx diff --git a/apps/game-tools/src/routeTree.gen.ts b/apps/game-tools/src/routeTree.gen.ts index 6bea8bb8..6560f199 100644 --- a/apps/game-tools/src/routeTree.gen.ts +++ b/apps/game-tools/src/routeTree.gen.ts @@ -12,6 +12,7 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as PublicRouteImport } from './routes/_public' import { Route as PublicIndexRouteImport } from './routes/_public/index' import { Route as PublicDndCharactersIndexRouteImport } from './routes/_public/dnd/characters/index' +import { Route as PublicDndCharactersOmarinKenateRouteImport } from './routes/_public/dnd/characters/omarin-kenate' import { Route as PublicDndCharactersExampleRouteImport } from './routes/_public/dnd/characters/example' const PublicRoute = PublicRouteImport.update({ @@ -29,6 +30,12 @@ const PublicDndCharactersIndexRoute = path: '/dnd/characters/', getParentRoute: () => PublicRoute, } as any) +const PublicDndCharactersOmarinKenateRoute = + PublicDndCharactersOmarinKenateRouteImport.update({ + id: '/dnd/characters/omarin-kenate', + path: '/dnd/characters/omarin-kenate', + getParentRoute: () => PublicRoute, + } as any) const PublicDndCharactersExampleRoute = PublicDndCharactersExampleRouteImport.update({ id: '/dnd/characters/example', @@ -39,11 +46,13 @@ const PublicDndCharactersExampleRoute = export interface FileRoutesByFullPath { '/': typeof PublicIndexRoute '/dnd/characters/example': typeof PublicDndCharactersExampleRoute + '/dnd/characters/omarin-kenate': typeof PublicDndCharactersOmarinKenateRoute '/dnd/characters/': typeof PublicDndCharactersIndexRoute } export interface FileRoutesByTo { '/': typeof PublicIndexRoute '/dnd/characters/example': typeof PublicDndCharactersExampleRoute + '/dnd/characters/omarin-kenate': typeof PublicDndCharactersOmarinKenateRoute '/dnd/characters': typeof PublicDndCharactersIndexRoute } export interface FileRoutesById { @@ -51,18 +60,28 @@ export interface FileRoutesById { '/_public': typeof PublicRouteWithChildren '/_public/': typeof PublicIndexRoute '/_public/dnd/characters/example': typeof PublicDndCharactersExampleRoute + '/_public/dnd/characters/omarin-kenate': typeof PublicDndCharactersOmarinKenateRoute '/_public/dnd/characters/': typeof PublicDndCharactersIndexRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/dnd/characters/example' | '/dnd/characters/' + fullPaths: + | '/' + | '/dnd/characters/example' + | '/dnd/characters/omarin-kenate' + | '/dnd/characters/' fileRoutesByTo: FileRoutesByTo - to: '/' | '/dnd/characters/example' | '/dnd/characters' + to: + | '/' + | '/dnd/characters/example' + | '/dnd/characters/omarin-kenate' + | '/dnd/characters' id: | '__root__' | '/_public' | '/_public/' | '/_public/dnd/characters/example' + | '/_public/dnd/characters/omarin-kenate' | '/_public/dnd/characters/' fileRoutesById: FileRoutesById } @@ -93,6 +112,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PublicDndCharactersIndexRouteImport parentRoute: typeof PublicRoute } + '/_public/dnd/characters/omarin-kenate': { + id: '/_public/dnd/characters/omarin-kenate' + path: '/dnd/characters/omarin-kenate' + fullPath: '/dnd/characters/omarin-kenate' + preLoaderRoute: typeof PublicDndCharactersOmarinKenateRouteImport + parentRoute: typeof PublicRoute + } '/_public/dnd/characters/example': { id: '/_public/dnd/characters/example' path: '/dnd/characters/example' @@ -106,12 +132,14 @@ declare module '@tanstack/react-router' { interface PublicRouteChildren { PublicIndexRoute: typeof PublicIndexRoute PublicDndCharactersExampleRoute: typeof PublicDndCharactersExampleRoute + PublicDndCharactersOmarinKenateRoute: typeof PublicDndCharactersOmarinKenateRoute PublicDndCharactersIndexRoute: typeof PublicDndCharactersIndexRoute } const PublicRouteChildren: PublicRouteChildren = { PublicIndexRoute: PublicIndexRoute, PublicDndCharactersExampleRoute: PublicDndCharactersExampleRoute, + PublicDndCharactersOmarinKenateRoute: PublicDndCharactersOmarinKenateRoute, PublicDndCharactersIndexRoute: PublicDndCharactersIndexRoute, } diff --git a/apps/game-tools/src/routes/_public/dnd/characters/index.tsx b/apps/game-tools/src/routes/_public/dnd/characters/index.tsx index 0c057540..ce509617 100644 --- a/apps/game-tools/src/routes/_public/dnd/characters/index.tsx +++ b/apps/game-tools/src/routes/_public/dnd/characters/index.tsx @@ -17,6 +17,13 @@ function RouteComponent() { Wizard / Warlock · Level 7

+ +

Omarin Kenate

+

Fighter 3 / Monk 2

+ ); } diff --git a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx new file mode 100644 index 00000000..95502448 --- /dev/null +++ b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx @@ -0,0 +1,201 @@ +import { + Ability, + type Character, + Skill, + StandardCharacterSheet, +} from '@ageorgedev/dnd-character-sheet'; +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/_public/dnd/characters/omarin-kenate')({ + component: RouteComponent, +}); + +function RouteComponent() { + return ; +} + +const OmarinData: Character = { + name: 'Omarin Kenate', + species: 'Drow Elf', + background: 'Farmer', + size: 'Medium', + classes: [ + { + class: 'Monk', + level: 2, + }, + { + class: 'Fighter', + level: 3, + subclass: 'Battle master', + }, + ], + experiencePoints: 0, + abilities: { + [Ability.Strength]: 8, + [Ability.Dexterity]: 17, + [Ability.Constitution]: 14, + [Ability.Intelligence]: 12, + [Ability.Wisdom]: 15, + [Ability.Charisma]: 8, + }, + savingThrowProficiencies: [Ability.Strength, Ability.Dexterity], + skillProficiencies: [ + Skill.Stealth, + Skill.Nature, + Skill.AnimalHandling, + Skill.Insight, + Skill.Perception, + ], + skillExpertise: [], + armorClass: 15, + speed: 40, + hitPoints: { + maximum: 52, + }, + hitDice: { + dieType: 'd8', + total: 5, + }, + attacks: [ + { + name: 'Shortsword', + kind: 'weapon', + ability: Ability.Dexterity, + damage: [{ dice: '1d6', type: 'Piercing' }], + masteryProperty: 'Vex', + notes: 'Light, Finesse', + }, + { + name: 'Scimitar', + kind: 'weapon', + ability: Ability.Dexterity, + damage: [{ dice: '1d6', type: 'Slashing' }], + masteryProperty: 'Nick', + notes: 'Light, Finesse', + }, + { + name: 'Unarmed Stike', + kind: 'weapon', + ability: Ability.Dexterity, + damage: [{ dice: '1d6', type: 'Bludgeoning' }], + }, + { + name: 'Daggers (x5)', + kind: 'weapon', + ability: Ability.Dexterity, + damage: [{ dice: '1d4', type: 'Piercing' }], + masteryProperty: 'Nick', + notes: 'Light, Finesse, Thrown (range 20/60)', + }, + { + name: 'Light Crossbow', + kind: 'weapon', + ability: Ability.Dexterity, + damage: [{ dice: '1d8', type: 'Piercing' }], + notes: 'Ammo (80/20, Bolt), Loading, 2-handed', + }, + ], + equipment: [], + currency: { + cp: 0, + sp: 0, + ep: 0, + gp: 0, + pp: 0, + }, + armorProficiencies: ['Light armor', 'Medium armor', 'Shields'], + weaponProficiencies: [ + 'Simple', + 'Martial', + 'Monk Weapons (Simple Melee, or Martial weapons with Light property)', + ], + toolProficiencies: ["Tinker's Tools", "Carpenter's Tools"], + languages: [], + personality: { + traits: '', + ideals: '', + bonds: '', + flaws: '', + }, + features: [ + { + name: 'Martial Arts', + description: + 'On using only monk weapons, wearing no armor, and holding no shield, you gain the following benefits. 1) Unarmed strikes as Bonus Actions 2) Can use Dex. modifier for attacks and damage rolls; 1d6 instead of normal damage -- for unarmed strikes and Monk weapons', + }, + { + name: 'Unarmored Defence + Movement', + description: + 'When not wearing armor and not holding Shield: AC = 10 + Dex. + Wis. modifiers, Speed + 10', + }, + { + name: 'Weapon Mastery', + description: 'You have mastery over 3 different weapons', + }, + { + name: "Monk's Focus", + description: 'You have 2 Focus Points to fuel monk features', + resource: { + name: 'Focus Points', + count: { + kind: 'fixed', + value: 2, + }, + refresh: { + kind: 'any-rest', + }, + }, + }, + { + name: 'Second Wind', + duration: 'Bonus Action', + // TODO: Fighter level + description: 'Regain Hit Points equal to 1d10+{{level:Fighter}}', + cost: '1 Second Wind charge', + resource: { + name: 'Second Wind', + count: { kind: 'fixed', value: 2 }, + refresh: { + kind: 'short-and-long-rest', + numberOfRefreshesOnShortRest: 1, + }, + }, + }, + { + name: 'Tactical Mind', + cost: '1 Second Wind charge', + description: + 'When you fail an Ability check, you can roll 1d10 and add to the roll. Second Wind charge not spent if check still unsuccessful', + }, + { + name: 'Combat Superiority', + description: + 'You learn manuevers that are fueled by Superiority Dice. Your Superiority Dice is a D8', + resource: { + name: 'Superiority Dice', + count: { kind: 'fixed', value: 4 }, + refresh: { kind: 'any-rest' }, + }, + }, + { + name: 'Action Surge', + description: 'Take 1 extra Action (except magic)', + resource: { + name: 'Action Surge', + count: { kind: 'fixed', value: 1 }, + refresh: { kind: 'any-rest' }, + }, + }, + { + name: 'Uncanny Metabolism', + description: + 'On rolling Initiative, regain all expended Focus Points, and regain 1d6+{{level:Monk}} HP', + resource: { + name: 'Uncanny Metabolism', + count: { kind: 'fixed', value: 1 }, + refresh: { kind: 'long-rest' }, + }, + }, + ], +}; diff --git a/packages/dnd-character-sheet/src/components/ClassFeatures/ClassFeatures.tsx b/packages/dnd-character-sheet/src/components/ClassFeatures/ClassFeatures.tsx index 67e7a912..ff14c51f 100644 --- a/packages/dnd-character-sheet/src/components/ClassFeatures/ClassFeatures.tsx +++ b/packages/dnd-character-sheet/src/components/ClassFeatures/ClassFeatures.tsx @@ -17,9 +17,11 @@ export function ClassFeatures() {

{feature.name}

-

- {feature.source} -

+ {feature.source && ( +

+ {feature.source} +

+ )}

{feature.description}

diff --git a/packages/dnd-character-sheet/src/components/Resources/Resources.tsx b/packages/dnd-character-sheet/src/components/Resources/Resources.tsx index 1a1250e3..3d1fcf60 100644 --- a/packages/dnd-character-sheet/src/components/Resources/Resources.tsx +++ b/packages/dnd-character-sheet/src/components/Resources/Resources.tsx @@ -1,12 +1,43 @@ +// biome-ignore-all lint/suspicious/noArrayIndexKey: Not important +import { useMemo } from 'react'; import type { Character } from '../../lib/models/character'; import type { DerivedStats } from '../../lib/models/derived-stats'; import type { Feature } from '../../lib/models/feature'; import { useCharacter } from '../CharacterSheet'; +import { CircleCheck } from '../layout/checkables'; +import { Panel } from '../layout/Panel'; +import { PanelTitle } from '../layout/PanelTitle'; type Resource = NonNullable; export function Resources() { - const { character } = useCharacter(); + const { character, derived } = useCharacter(); + const resources = useMemo(() => { + const allResources = pullAllResourcesFromFeatures(character); + return allResources + .map(resourceDigester(character, derived)) + .filter(Boolean) as ResourceDisplay[]; + }, [character, derived]); + + if (!resources.length) { + return null; + } + + return ( + + Resources +
+ {resources.map((r) => ( +
+ {r.name}  + {Array.from({ length: r.value }, (_, i) => ( + + ))} +
+ ))} +
+
+ ); } function pullAllResourcesFromFeatures(character: Character) { @@ -19,8 +50,15 @@ function pullAllResourcesFromFeatures(character: Character) { .filter(Boolean) as Resource[]; } +type ResourceDisplay = { + value: number; + name: string; + display: 'dots' | 'numeric'; + refresh: Resource['refresh']; +}; + function resourceDigester(character: Character, stats: DerivedStats) { - return function digest(resource: Resource) { + return function digest(resource: Resource): ResourceDisplay | null { const base = { name: resource.name, display: resource.count.display ?? 'dots', @@ -30,12 +68,19 @@ function resourceDigester(character: Character, stats: DerivedStats) { const config = resource.count; switch (config.kind) { + case 'fixed': + return { + ...base, + value: config.value, + }; + case 'character-level': { return { ...base, value: stats.characterLevel * (config.multiplier ?? 1), }; } + case 'class-level': { const cls = character.classes.find((c) => c.class === config.class); if (!cls) { @@ -49,6 +94,7 @@ function resourceDigester(character: Character, stats: DerivedStats) { value: cls.level * (config.multiplier ?? 1), }; } + case 'ability': { const mod = stats.abilityModifiers[config.ability]; return { diff --git a/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx b/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx index e684d66a..35510087 100644 --- a/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx +++ b/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx @@ -12,6 +12,7 @@ import { Feats } from '../Feats/Feats'; import { HeroicInspiration } from '../HeroicInspiration/HeroicInspiration'; import { Page } from '../layout/Page'; import { ProficiencyBlock } from '../ProficiencyBlock/ProficiencyBlock'; +import { Resources } from '../Resources/Resources'; import { SheetHeader } from '../SheetHeader/SheetHeader'; import { SpeciesTraits } from '../SpeciesTraits/SpeciesTraits'; import { SpellcastingBlock } from '../SpellcastingBlock'; @@ -47,6 +48,7 @@ export function StandardCharacterSheet({ data }: Props) {
+
diff --git a/packages/dnd-character-sheet/src/index.ts b/packages/dnd-character-sheet/src/index.ts index 821b38aa..f49f87d1 100644 --- a/packages/dnd-character-sheet/src/index.ts +++ b/packages/dnd-character-sheet/src/index.ts @@ -3,3 +3,9 @@ export { ExampleSheet } from './characters/example'; // Standard assembled sheet export { StandardCharacterSheet } from './components/StandardCharacterSheet/StandardCharacterSheet'; + +// Other models +export { Ability } from './lib/models/abilities'; +export type { Character } from './lib/models/character'; +export { Skill } from './lib/models/skills'; +export type { Spell } from './lib/models/spellcasting'; diff --git a/packages/dnd-character-sheet/src/lib/models/feature.ts b/packages/dnd-character-sheet/src/lib/models/feature.ts index 03243000..ca1da9af 100644 --- a/packages/dnd-character-sheet/src/lib/models/feature.ts +++ b/packages/dnd-character-sheet/src/lib/models/feature.ts @@ -2,9 +2,11 @@ import type { Ability } from './abilities'; export interface Feature { name: string; - source: string; + source?: string; description: string; + duration?: 'Action' | 'Bonus Action'; + // Either it uses a resource cost?: Cost; @@ -36,7 +38,7 @@ type ResourceCount = { } | { kind: 'fixed'; - count: number; + value: number; } ); @@ -44,7 +46,7 @@ type Cost = string; type Refresh = | { - kind: 'short-rest' | 'long-rest'; + kind: 'short-rest' | 'long-rest' | 'any-rest'; } | { kind: 'short-and-long-rest'; From 39728326af0438a211d65c2a63231d2598ffbbd9 Mon Sep 17 00:00:00 2001 From: Anish George Date: Wed, 17 Jun 2026 23:31:40 +1000 Subject: [PATCH 05/29] chore: Basic design for resources --- biome.json | 6 +++ .../src/components/AttackList/AttackList.tsx | 9 +++-- .../src/components/Resources/Resources.tsx | 39 +++++++++++++++---- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/biome.json b/biome.json index 31a0776c..8ca73da0 100644 --- a/biome.json +++ b/biome.json @@ -23,11 +23,17 @@ }, "linter": { "enabled": true, + "domains": { + "types": "recommended" + }, "rules": { "recommended": true, "suspicious": { "noDuplicateProperties": "off", "noDuplicateCustomProperties": "off" + }, + "nursery": { + "useExhaustiveSwitchCases": "on" } } }, diff --git a/packages/dnd-character-sheet/src/components/AttackList/AttackList.tsx b/packages/dnd-character-sheet/src/components/AttackList/AttackList.tsx index 36c4a14f..d227dfb6 100644 --- a/packages/dnd-character-sheet/src/components/AttackList/AttackList.tsx +++ b/packages/dnd-character-sheet/src/components/AttackList/AttackList.tsx @@ -1,3 +1,4 @@ +import { cn } from '@ageorgedev/toolbelt/cn'; import { ABILITY_DETAILS } from '../../lib/models/abilities'; import type { Attack, AttackDamage } from '../../lib/models/attacks'; import type { DerivedStats } from '../../lib/models/derived-stats'; @@ -19,7 +20,7 @@ export function AttackList() { Name - Attack Bonus / DC + Atk Bonus / DC Damage & Type Mastery Notes @@ -49,7 +50,7 @@ export function AttackList() { ) : ( — )} - {attack.notes ?? '—'} + {attack.notes ?? '—'} ); })} @@ -62,7 +63,7 @@ export function AttackList() { function Th({ children }: { children: React.ReactNode }) { return ( - + {children} ); @@ -75,7 +76,7 @@ function Td({ children: React.ReactNode; className?: string; }) { - return {children}; + return {children}; } function calcAttackBonus( diff --git a/packages/dnd-character-sheet/src/components/Resources/Resources.tsx b/packages/dnd-character-sheet/src/components/Resources/Resources.tsx index 3d1fcf60..6c6d2e01 100644 --- a/packages/dnd-character-sheet/src/components/Resources/Resources.tsx +++ b/packages/dnd-character-sheet/src/components/Resources/Resources.tsx @@ -26,13 +26,20 @@ export function Resources() { return ( Resources -
+
{resources.map((r) => ( -
- {r.name}  - {Array.from({ length: r.value }, (_, i) => ( - - ))} +
+ {r.name} + {r.refresh} +
+ {r.display === 'dots' ? ( + Array.from({ length: r.value }, (_, i) => ) + ) : ( + <> +
+ / {r.value} + + )}
))}
@@ -54,7 +61,7 @@ type ResourceDisplay = { value: number; name: string; display: 'dots' | 'numeric'; - refresh: Resource['refresh']; + refresh: string; }; function resourceDigester(character: Character, stats: DerivedStats) { @@ -62,7 +69,7 @@ function resourceDigester(character: Character, stats: DerivedStats) { const base = { name: resource.name, display: resource.count.display ?? 'dots', - refresh: resource.refresh, + refresh: `[${getRefreshText(resource.refresh)}]`, }; const config = resource.count; @@ -105,3 +112,19 @@ function resourceDigester(character: Character, stats: DerivedStats) { } }; } + +function getRefreshText(refresh: Resource['refresh']) { + switch (refresh.kind) { + case 'any-rest': + return 'Any Rest'; + + case 'long-rest': + return 'Long Rest'; + + // case 'short-rest': + // return 'Short Rest'; + + case 'short-and-long-rest': + return `${refresh.numberOfRefreshesOnShortRest} / Short Rest, all on Long Rest`; + } +} From eb213338b103cbae6cbc1fca01f25b2fd7a3f88c Mon Sep 17 00:00:00 2001 From: Anish George Date: Wed, 17 Jun 2026 23:32:00 +1000 Subject: [PATCH 06/29] chore: fix from debuggin --- .../src/components/Resources/Resources.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dnd-character-sheet/src/components/Resources/Resources.tsx b/packages/dnd-character-sheet/src/components/Resources/Resources.tsx index 6c6d2e01..42a97456 100644 --- a/packages/dnd-character-sheet/src/components/Resources/Resources.tsx +++ b/packages/dnd-character-sheet/src/components/Resources/Resources.tsx @@ -121,8 +121,8 @@ function getRefreshText(refresh: Resource['refresh']) { case 'long-rest': return 'Long Rest'; - // case 'short-rest': - // return 'Short Rest'; + case 'short-rest': + return 'Short Rest'; case 'short-and-long-rest': return `${refresh.numberOfRefreshesOnShortRest} / Short Rest, all on Long Rest`; From c896093b66b8a6db4e997b8b254b617bc97a93d1 Mon Sep 17 00:00:00 2001 From: Anish George Date: Thu, 18 Jun 2026 11:22:42 +1000 Subject: [PATCH 07/29] chore: set a peacock color for wrkspace --- .vscode/settings.json | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index cb336edb..c7d4cdbf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,5 +9,20 @@ "editor.defaultFormatter": "biomejs.biome", "nxConsole.generateAiAgentRules": true, "js/ts.preferences.preferTypeOnlyAutoImports": true, - "js/ts.tsdk.path": "node_modules/typescript/lib" + "js/ts.tsdk.path": "node_modules/typescript/lib", + "workbench.colorCustomizations": { + "commandCenter.border": "#e7e7e799", + "sash.hoverBorder": "#8f491a", + "statusBar.background": "#643312", + "statusBar.foreground": "#e7e7e7", + "statusBarItem.hoverBackground": "#8f491a", + "statusBarItem.remoteBackground": "#643312", + "statusBarItem.remoteForeground": "#e7e7e7", + "tab.activeBorder": "#8f491a", + "titleBar.activeBackground": "#643312", + "titleBar.activeForeground": "#e7e7e7", + "titleBar.inactiveBackground": "#64331299", + "titleBar.inactiveForeground": "#e7e7e799" + }, + "peacock.color": "#643312" } From 96d501a25feb19b758af13c382bde699663d469a Mon Sep 17 00:00:00 2001 From: Anish George Date: Thu, 18 Jun 2026 11:50:40 +1000 Subject: [PATCH 08/29] chore: happier with the styling of attacklist --- .../src/components/AttackList/AttackList.tsx | 37 +++++++++++-------- .../src/components/Resources/Resources.tsx | 2 +- .../src/components/layout/checkables.tsx | 4 +- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/packages/dnd-character-sheet/src/components/AttackList/AttackList.tsx b/packages/dnd-character-sheet/src/components/AttackList/AttackList.tsx index d227dfb6..e8ce9c5f 100644 --- a/packages/dnd-character-sheet/src/components/AttackList/AttackList.tsx +++ b/packages/dnd-character-sheet/src/components/AttackList/AttackList.tsx @@ -13,14 +13,14 @@ export function AttackList() { if (character.attacks.length === 0) return null; return ( - + Weapons & Damage Cantrips
- + @@ -45,12 +45,13 @@ export function AttackList() { {attack.masteryProperty ? ( ) : ( - + )} - + ); })} @@ -61,22 +62,26 @@ export function AttackList() { ); } -function Th({ children }: { children: React.ReactNode }) { +function Th({ + className, + ...props +}: React.ThHTMLAttributes) { return ( - + ; + className, + ...props +}: React.TdHTMLAttributes) { + return + {/* Adds the text height required */} + + + ); +} + +function SpellTableRow({ spell }: { spell: Spell }) { + return ( + + + + + + + + + {/* TODO: components */} + {/* TODO: Alternative ability */} + + + ); +} + +function Th({ + className, + ...props +}: React.ThHTMLAttributes) { + return ( + + + + + + + + + + + ); +} + +function EmptyRow() { + return ( + + {/* Adds the text height required */} + + ); +} + +function CRMcell({ c, r, m }: { c?: boolean; r?: boolean; m?: boolean }) { + return ( + + ); +} + +/** Utils */ + +function Th({ + className, + ...props +}: React.ThHTMLAttributes) { + return ( + - {/* Adds the text height required */} - - - ); -} - -function SpellTableRow({ spell }: { spell: Spell }) { - return ( - - - - - - - - - {/* TODO: components */} - {/* TODO: Alternative ability */} - - - ); -} - -function Th({ - className, - ...props -}: React.ThHTMLAttributes) { - return ( -
NameAtk Bonus / DCAtk / DC Damage & Type Mastery Notes{formatDamage(attack.damage, damageBonus)} -   {attack.masteryProperty} +  {' '} + {attack.masteryProperty} {attack.notes ?? '—'}{attack.notes}
- {children} - ); } function Td({ - children, - className = '', -}: { - children: React.ReactNode; - className?: string; -}) { - return {children}; } function calcAttackBonus( diff --git a/packages/dnd-character-sheet/src/components/Resources/Resources.tsx b/packages/dnd-character-sheet/src/components/Resources/Resources.tsx index 42a97456..42bc29c8 100644 --- a/packages/dnd-character-sheet/src/components/Resources/Resources.tsx +++ b/packages/dnd-character-sheet/src/components/Resources/Resources.tsx @@ -24,7 +24,7 @@ export function Resources() { } return ( - + Resources
{resources.map((r) => ( diff --git a/packages/dnd-character-sheet/src/components/layout/checkables.tsx b/packages/dnd-character-sheet/src/components/layout/checkables.tsx index 7a340ed3..201e9dd1 100644 --- a/packages/dnd-character-sheet/src/components/layout/checkables.tsx +++ b/packages/dnd-character-sheet/src/components/layout/checkables.tsx @@ -9,7 +9,7 @@ export function CircleCheck({ className, ...props }: CheckableProps) { } export function DiamondCheck({ className, ...props }: CheckableProps) { - return ; + return ; } function BaseCheck({ checked = false, className, ...props }: CheckableProps) { @@ -18,7 +18,7 @@ function BaseCheck({ checked = false, className, ...props }: CheckableProps) { Date: Thu, 18 Jun 2026 12:06:39 +1000 Subject: [PATCH 09/29] chore: Get print styling sorted --- .../StandardCharacterSheet.tsx | 6 ++++-- .../src/components/layout/Page.module.css | 11 ++++++++++- .../src/components/layout/Page.tsx | 17 ++++++++++++++--- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx b/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx index 35510087..e4b0ca75 100644 --- a/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx +++ b/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx @@ -24,9 +24,11 @@ interface Props { export function StandardCharacterSheet({ data }: Props) { return ( - + + {' '} + {/** Should not be margin here, for print reasons */} -
+
diff --git a/packages/dnd-character-sheet/src/components/layout/Page.module.css b/packages/dnd-character-sheet/src/components/layout/Page.module.css index 1773d9a7..06ffc4e7 100644 --- a/packages/dnd-character-sheet/src/components/layout/Page.module.css +++ b/packages/dnd-character-sheet/src/components/layout/Page.module.css @@ -1,5 +1,14 @@ +@reference "@ageorgedev/foundation-styles/theme.css"; + @media print { - .Page { + .PageOuter { break-inside: avoid; } } + +@media print { + .PageInner { + @apply py-6; + min-height: 100vh; + } +} diff --git a/packages/dnd-character-sheet/src/components/layout/Page.tsx b/packages/dnd-character-sheet/src/components/layout/Page.tsx index bf930d88..3b8a82cc 100644 --- a/packages/dnd-character-sheet/src/components/layout/Page.tsx +++ b/packages/dnd-character-sheet/src/components/layout/Page.tsx @@ -1,10 +1,21 @@ import { cn } from '@ageorgedev/toolbelt/cn'; import styles from './Page.module.css'; -export function Page({ children }: React.PropsWithChildren) { +export function Page({ + children, + className, + ...props +}: React.HTMLAttributes) { return ( -
-
+
+
{children}
From b73aaab26b2d5b362902183be4090ab6bebcf981 Mon Sep 17 00:00:00 2001 From: Anish George Date: Thu, 18 Jun 2026 14:12:32 +1000 Subject: [PATCH 10/29] feat: Add text replacement for features --- .../_public/dnd/characters/omarin-kenate.tsx | 4 +-- packages/dnd-character-sheet/package.json | 4 ++- .../src/components/CharacterSheet.tsx | 12 ++++++-- .../src/lib/text-enrichment.ts | 29 +++++++++++++++++++ yarn.lock | 18 ++++++++++++ 5 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 packages/dnd-character-sheet/src/lib/text-enrichment.ts diff --git a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx index 95502448..c587e4f6 100644 --- a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx +++ b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx @@ -151,7 +151,7 @@ const OmarinData: Character = { name: 'Second Wind', duration: 'Bonus Action', // TODO: Fighter level - description: 'Regain Hit Points equal to 1d10+{{level:Fighter}}', + description: 'Regain Hit Points equal to 1d10+{{level.Fighter}}', cost: '1 Second Wind charge', resource: { name: 'Second Wind', @@ -190,7 +190,7 @@ const OmarinData: Character = { { name: 'Uncanny Metabolism', description: - 'On rolling Initiative, regain all expended Focus Points, and regain 1d6+{{level:Monk}} HP', + 'On rolling Initiative, regain all expended Focus Points, and regain 1d6+{{level.Monk}} HP', resource: { name: 'Uncanny Metabolism', count: { kind: 'fixed', value: 1 }, diff --git a/packages/dnd-character-sheet/package.json b/packages/dnd-character-sheet/package.json index 1f46d310..9f0f2377 100644 --- a/packages/dnd-character-sheet/package.json +++ b/packages/dnd-character-sheet/package.json @@ -23,6 +23,7 @@ }, "devDependencies": { "@ageorgedev/ts-config": "workspace:*", + "@types/mustache": "^4", "@types/react": "19.2.17", "@types/react-dom": "19.2.3", "concurrently": "10.0.3", @@ -30,6 +31,7 @@ "typescript": "5.8.3" }, "dependencies": { - "@ageorgedev/toolbelt": "workspace:*" + "@ageorgedev/toolbelt": "workspace:*", + "mustache": "4.2.0" } } diff --git a/packages/dnd-character-sheet/src/components/CharacterSheet.tsx b/packages/dnd-character-sheet/src/components/CharacterSheet.tsx index 0b098b06..11547c3d 100644 --- a/packages/dnd-character-sheet/src/components/CharacterSheet.tsx +++ b/packages/dnd-character-sheet/src/components/CharacterSheet.tsx @@ -2,6 +2,7 @@ import { createContext, useContext, useMemo } from 'react'; import { calculateStats } from '../lib/calculate-derived-stats'; import type { Character } from '../lib/models/character'; import type { DerivedStats } from '../lib/models/derived-stats'; +import { enrichCharacterData } from '../lib/text-enrichment'; interface CharacterContextValue { character: Character; @@ -26,9 +27,16 @@ interface CharacterSheetProps { } export function CharacterSheet({ data, children }: CharacterSheetProps) { + const stats = useMemo(() => calculateStats(data), [data]); + + const character = useMemo( + () => enrichCharacterData(data, stats), + [data, stats] + ); + const value = useMemo( - () => ({ character: data, derived: calculateStats(data) }), - [data] + () => ({ character, derived: stats }), + [character, stats] ); return ( diff --git a/packages/dnd-character-sheet/src/lib/text-enrichment.ts b/packages/dnd-character-sheet/src/lib/text-enrichment.ts new file mode 100644 index 00000000..2077af88 --- /dev/null +++ b/packages/dnd-character-sheet/src/lib/text-enrichment.ts @@ -0,0 +1,29 @@ +import Mustache from 'mustache'; +import type { Character } from './models'; +import type { DerivedStats } from './models/derived-stats'; +import type { Feature } from './models/feature'; + +export function enrichCharacterData( + character: Character, + stats: DerivedStats +): Character { + const dataValues = { + level: { + total: stats.characterLevel, + ...Object.fromEntries(character.classes.map((c) => [c.class, c.level])), + }, + }; + + const enricher = (str: string) => Mustache.render(str, dataValues); + const enrichFeature = (f: Feature): Feature => ({ + ...f, + description: enricher(f.description), + }); + + return { + ...character, + feats: character.feats?.map(enrichFeature), + features: character.features.map(enrichFeature), + speciesTraits: character.speciesTraits?.map(enrichFeature), + }; +} diff --git a/yarn.lock b/yarn.lock index 8372dc27..996caf21 100644 --- a/yarn.lock +++ b/yarn.lock @@ -156,10 +156,12 @@ __metadata: dependencies: "@ageorgedev/toolbelt": "workspace:*" "@ageorgedev/ts-config": "workspace:*" + "@types/mustache": "npm:^4" "@types/react": "npm:19.2.17" "@types/react-dom": "npm:19.2.3" concurrently: "npm:10.0.3" cpx2: "npm:9.0.0" + mustache: "npm:4.2.0" typescript: "npm:5.8.3" peerDependencies: react: 19.2.7 @@ -3837,6 +3839,13 @@ __metadata: languageName: node linkType: hard +"@types/mustache@npm:^4": + version: 4.2.6 + resolution: "@types/mustache@npm:4.2.6" + checksum: 10c0/f49a83b189e92c962e9b61094c80c979f115ea876bd746bdb9f725c38ab8981a6691e3d1d5cd008ade24b6e5040602970bc0bc60ade71772501d1df8adc7adad + languageName: node + linkType: hard + "@types/node@npm:18.16.9": version: 18.16.9 resolution: "@types/node@npm:18.16.9" @@ -7742,6 +7751,15 @@ __metadata: languageName: node linkType: hard +"mustache@npm:4.2.0": + version: 4.2.0 + resolution: "mustache@npm:4.2.0" + bin: + mustache: bin/mustache + checksum: 10c0/1f8197e8a19e63645a786581d58c41df7853da26702dbc005193e2437c98ca49b255345c173d50c08fe4b4dbb363e53cb655ecc570791f8deb09887248dd34a2 + languageName: node + linkType: hard + "nanoid@npm:^3.3.12": version: 3.3.12 resolution: "nanoid@npm:3.3.12" From 6875bd369100bea12d7264387ef0d92a8794da0a Mon Sep 17 00:00:00 2001 From: Anish George Date: Thu, 18 Jun 2026 20:33:27 +1000 Subject: [PATCH 11/29] feat: hit dice calculation --- apps/ageorgedev/package.json | 2 +- .../_public/dnd/characters/omarin-kenate.tsx | 9 +++----- packages/design-system/package.json | 2 +- packages/dnd-character-sheet/package.json | 3 ++- .../src/characters/example-wizard.data.ts | 9 ++------ .../src/components/Resources/Resources.tsx | 2 +- .../components/SheetHeader/SheetHeader.tsx | 7 +++--- packages/dnd-character-sheet/src/index.ts | 1 + .../src/lib/calculate-derived-stats.ts | 23 +++++++++++++++++++ .../src/lib/character-class-constants.ts | 17 ++++++++++++++ .../src/lib/models/character-classes.ts | 18 +++++++++++++++ .../src/lib/models/character.ts | 9 ++------ .../src/lib/models/derived-stats.ts | 1 + .../src/lib/models/index.ts | 1 + .../src/lib/text-enrichment.ts | 2 +- packages/toolbelt/package.json | 2 +- yarn.lock | 15 ++++++------ 17 files changed, 86 insertions(+), 37 deletions(-) create mode 100644 packages/dnd-character-sheet/src/lib/character-class-constants.ts create mode 100644 packages/dnd-character-sheet/src/lib/models/character-classes.ts diff --git a/apps/ageorgedev/package.json b/apps/ageorgedev/package.json index 8fcce42d..c112b506 100644 --- a/apps/ageorgedev/package.json +++ b/apps/ageorgedev/package.json @@ -22,7 +22,7 @@ "@phosphor-icons/react": "2.1.10", "@tanstack/react-router": "1.170.11", "@tanstack/react-start": "1.168.19", - "ramda": "0.28.0", + "ramda": "0.32.0", "react": "19.2.7", "react-dom": "19.2.7", "remark-frontmatter": "5.0.0", diff --git a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx index c587e4f6..8c8ebc8e 100644 --- a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx +++ b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx @@ -1,6 +1,7 @@ import { Ability, type Character, + CharacterClass, Skill, StandardCharacterSheet, } from '@ageorgedev/dnd-character-sheet'; @@ -21,11 +22,11 @@ const OmarinData: Character = { size: 'Medium', classes: [ { - class: 'Monk', + name: CharacterClass.Monk, level: 2, }, { - class: 'Fighter', + name: CharacterClass.Fighter, level: 3, subclass: 'Battle master', }, @@ -53,10 +54,6 @@ const OmarinData: Character = { hitPoints: { maximum: 52, }, - hitDice: { - dieType: 'd8', - total: 5, - }, attacks: [ { name: 'Shortsword', diff --git a/packages/design-system/package.json b/packages/design-system/package.json index 5222e83b..b61e7454 100644 --- a/packages/design-system/package.json +++ b/packages/design-system/package.json @@ -19,7 +19,7 @@ "@phosphor-icons/react": "2.1.10", "@radix-ui/react-slot": "1.2.3", "class-variance-authority": "0.7.1", - "ramda": "0.28.0", + "ramda": "0.32.0", "react-syntax-highlighter": "16.1.1" }, "devDependencies": { diff --git a/packages/dnd-character-sheet/package.json b/packages/dnd-character-sheet/package.json index 9f0f2377..8c9ac82e 100644 --- a/packages/dnd-character-sheet/package.json +++ b/packages/dnd-character-sheet/package.json @@ -32,6 +32,7 @@ }, "dependencies": { "@ageorgedev/toolbelt": "workspace:*", - "mustache": "4.2.0" + "mustache": "4.2.0", + "ramda": "0.32.0" } } diff --git a/packages/dnd-character-sheet/src/characters/example-wizard.data.ts b/packages/dnd-character-sheet/src/characters/example-wizard.data.ts index 88a484ac..0998f7a5 100644 --- a/packages/dnd-character-sheet/src/characters/example-wizard.data.ts +++ b/packages/dnd-character-sheet/src/characters/example-wizard.data.ts @@ -6,12 +6,12 @@ export const exampleWizard: Character = { name: 'Seraphina Ashveil', classes: [ { - class: 'Sorcerer', + name: 'Sorcerer', subclass: 'Wild Magic', level: 4, }, { - class: 'Warlock', + name: 'Warlock', subclass: 'The Archfey', level: 3, }, @@ -47,11 +47,6 @@ export const exampleWizard: Character = { current: 45, }, - hitDice: { - dieType: 'd6', - total: 7, - }, - attacks: [ { name: 'Quarterstaff', diff --git a/packages/dnd-character-sheet/src/components/Resources/Resources.tsx b/packages/dnd-character-sheet/src/components/Resources/Resources.tsx index 42bc29c8..8e0e3da7 100644 --- a/packages/dnd-character-sheet/src/components/Resources/Resources.tsx +++ b/packages/dnd-character-sheet/src/components/Resources/Resources.tsx @@ -89,7 +89,7 @@ function resourceDigester(character: Character, stats: DerivedStats) { } case 'class-level': { - const cls = character.classes.find((c) => c.class === config.class); + const cls = character.classes.find((c) => c.name === config.class); if (!cls) { console.warn( `Resource ${resource.name} depends on class ${config.class} which the character doesn't have.` diff --git a/packages/dnd-character-sheet/src/components/SheetHeader/SheetHeader.tsx b/packages/dnd-character-sheet/src/components/SheetHeader/SheetHeader.tsx index 0049e1a6..5d0eaad4 100644 --- a/packages/dnd-character-sheet/src/components/SheetHeader/SheetHeader.tsx +++ b/packages/dnd-character-sheet/src/components/SheetHeader/SheetHeader.tsx @@ -29,7 +29,7 @@ function NameBlock() {

{character.name}

{character.background} - {joinItems(character.classes, 'class')} + {joinItems(character.classes, 'name')} {character.species} @@ -77,7 +77,7 @@ function ArmorBlock() { } function HealthAndDeathBlock() { - const { character } = useCharacter(); + const { character, derived } = useCharacter(); return ( @@ -95,8 +95,7 @@ function HealthAndDeathBlock() {
Spent - {character.hitDice.total} - {character.hitDice.dieType} + {derived.hitDice.map((d) => `${d.count}${d.dice}`).join(' + ')} Max
diff --git a/packages/dnd-character-sheet/src/index.ts b/packages/dnd-character-sheet/src/index.ts index f49f87d1..591c11da 100644 --- a/packages/dnd-character-sheet/src/index.ts +++ b/packages/dnd-character-sheet/src/index.ts @@ -7,5 +7,6 @@ export { StandardCharacterSheet } from './components/StandardCharacterSheet/Stan // Other models export { Ability } from './lib/models/abilities'; export type { Character } from './lib/models/character'; +export { CharacterClass } from './lib/models/character-classes'; export { Skill } from './lib/models/skills'; export type { Spell } from './lib/models/spellcasting'; diff --git a/packages/dnd-character-sheet/src/lib/calculate-derived-stats.ts b/packages/dnd-character-sheet/src/lib/calculate-derived-stats.ts index 22404b9d..93809696 100644 --- a/packages/dnd-character-sheet/src/lib/calculate-derived-stats.ts +++ b/packages/dnd-character-sheet/src/lib/calculate-derived-stats.ts @@ -1,3 +1,5 @@ +import { groupBy, map, mapObjIndexed, pipe, reduce, toPairs } from 'ramda'; +import { HIT_DICE_TABLE } from './character-class-constants'; import { Ability, ALL_ABILITIES } from './models/abilities'; import type { Character } from './models/character'; import type { DerivedStats } from './models/derived-stats'; @@ -63,6 +65,8 @@ export function calculateStats(character: Character): DerivedStats { } : {}; + const hitDice = computeHitDice(character.classes); + return { abilityModifiers, proficiencyBonus: profBonus, @@ -71,6 +75,25 @@ export function calculateStats(character: Character): DerivedStats { initiative, passivePerception, characterLevel, + hitDice, ...spellcasting, }; } + +// function hitDice(charClasses: Character['classes']) { +// return charClasses.map(({ name, level }) => ({ +// count: level, +// dice: HIT_DICE_TABLE[name] +// })) +// } + +const computeHitDice = pipe( + map((config: Character['classes'][number]) => ({ + count: config.level, + dice: HIT_DICE_TABLE[config.name], + })), + groupBy(({ dice }) => dice), + mapObjIndexed(reduce((acc, { count }) => count + acc, 0)), + toPairs, + map(([dice, count]) => ({ dice, count })) +); diff --git a/packages/dnd-character-sheet/src/lib/character-class-constants.ts b/packages/dnd-character-sheet/src/lib/character-class-constants.ts new file mode 100644 index 00000000..41f57a06 --- /dev/null +++ b/packages/dnd-character-sheet/src/lib/character-class-constants.ts @@ -0,0 +1,17 @@ +import { CharacterClass } from './models/character-classes'; + +export const HIT_DICE_TABLE: Record = { + [CharacterClass.Artificer]: 'd8', + [CharacterClass.Barbarian]: 'd12', + [CharacterClass.Bard]: 'd8', + [CharacterClass.Cleric]: 'd8', + [CharacterClass.Druid]: 'd8', + [CharacterClass.Fighter]: 'd10', + [CharacterClass.Monk]: 'd8', + [CharacterClass.Paladin]: 'd10', + [CharacterClass.Ranger]: 'd10', + [CharacterClass.Rogue]: 'd8', + [CharacterClass.Sorcerer]: 'd6', + [CharacterClass.Warlock]: 'd8', + [CharacterClass.Wizard]: 'd6', +}; diff --git a/packages/dnd-character-sheet/src/lib/models/character-classes.ts b/packages/dnd-character-sheet/src/lib/models/character-classes.ts new file mode 100644 index 00000000..00f9502b --- /dev/null +++ b/packages/dnd-character-sheet/src/lib/models/character-classes.ts @@ -0,0 +1,18 @@ +export const CharacterClass = { + Artificer: 'Artificer', + Barbarian: 'Barbarian', + Bard: 'Bard', + Cleric: 'Cleric', + Druid: 'Druid', + Fighter: 'Fighter', + Monk: 'Monk', + Paladin: 'Paladin', + Ranger: 'Ranger', + Rogue: 'Rogue', + Sorcerer: 'Sorcerer', + Warlock: 'Warlock', + Wizard: 'Wizard', +} as const; + +export type CharacterClass = + (typeof CharacterClass)[keyof typeof CharacterClass]; diff --git a/packages/dnd-character-sheet/src/lib/models/character.ts b/packages/dnd-character-sheet/src/lib/models/character.ts index 321bdbb1..00369f9b 100644 --- a/packages/dnd-character-sheet/src/lib/models/character.ts +++ b/packages/dnd-character-sheet/src/lib/models/character.ts @@ -1,5 +1,6 @@ import type { Ability } from './abilities'; import type { Attack } from './attacks'; +import type { CharacterClass } from './character-classes'; import type { Feature } from './feature'; import type { Skill } from './skills'; import type { Spellcasting } from './spellcasting'; @@ -11,7 +12,7 @@ export interface Character { background: string; classes: { - class: string; + name: CharacterClass; subclass?: string; level: number; }[]; @@ -36,12 +37,6 @@ export interface Character { temporary?: number; }; - // Hit dice - hitDice: { - dieType: 'd6' | 'd8' | 'd10' | 'd12'; - total: number; - }; - // Attacks attacks: Attack[]; diff --git a/packages/dnd-character-sheet/src/lib/models/derived-stats.ts b/packages/dnd-character-sheet/src/lib/models/derived-stats.ts index d9814ab3..79e1c27b 100644 --- a/packages/dnd-character-sheet/src/lib/models/derived-stats.ts +++ b/packages/dnd-character-sheet/src/lib/models/derived-stats.ts @@ -9,6 +9,7 @@ export interface DerivedStats { passivePerception: number; initiative: number; characterLevel: number; + hitDice: { dice: string; count: number }[]; spellSaveDC?: number; spellAttackBonus?: number; } diff --git a/packages/dnd-character-sheet/src/lib/models/index.ts b/packages/dnd-character-sheet/src/lib/models/index.ts index 1196e277..1acfe9f1 100644 --- a/packages/dnd-character-sheet/src/lib/models/index.ts +++ b/packages/dnd-character-sheet/src/lib/models/index.ts @@ -1,4 +1,5 @@ export { ABILITY_DETAILS, Ability, ALL_ABILITIES } from './abilities'; export type { Character } from './character'; +export type { CharacterClass } from './character-classes'; export { AbilitySkillGrouping, Skill } from './skills'; export type { Spell } from './spellcasting'; diff --git a/packages/dnd-character-sheet/src/lib/text-enrichment.ts b/packages/dnd-character-sheet/src/lib/text-enrichment.ts index 2077af88..7fafd365 100644 --- a/packages/dnd-character-sheet/src/lib/text-enrichment.ts +++ b/packages/dnd-character-sheet/src/lib/text-enrichment.ts @@ -10,7 +10,7 @@ export function enrichCharacterData( const dataValues = { level: { total: stats.characterLevel, - ...Object.fromEntries(character.classes.map((c) => [c.class, c.level])), + ...Object.fromEntries(character.classes.map((c) => [c.name, c.level])), }, }; diff --git a/packages/toolbelt/package.json b/packages/toolbelt/package.json index 9aa648fa..a5787624 100644 --- a/packages/toolbelt/package.json +++ b/packages/toolbelt/package.json @@ -18,7 +18,7 @@ }, "dependencies": { "clsx": "2.1.1", - "ramda": "0.28.0", + "ramda": "0.32.0", "tailwind-merge": "3.3.1" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 996caf21..06fc49c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -60,7 +60,7 @@ __metadata: concurrently: "npm:10.0.3" postcss: "npm:8.5.15" postcss-nesting: "npm:12.1.5" - ramda: "npm:0.28.0" + ramda: "npm:0.32.0" react: "npm:19.2.7" react-dom: "npm:19.2.7" remark-frontmatter: "npm:5.0.0" @@ -138,7 +138,7 @@ __metadata: concurrently: "npm:10.0.3" cpx2: "npm:9.0.0" jsdom: "npm:29.1.1" - ramda: "npm:0.28.0" + ramda: "npm:0.32.0" react-syntax-highlighter: "npm:16.1.1" typescript: "npm:5.8.3" typescript-plugin-css-modules: "npm:5.2.0" @@ -162,6 +162,7 @@ __metadata: concurrently: "npm:10.0.3" cpx2: "npm:9.0.0" mustache: "npm:4.2.0" + ramda: "npm:0.32.0" typescript: "npm:5.8.3" peerDependencies: react: 19.2.7 @@ -299,7 +300,7 @@ __metadata: "@types/ramda": "npm:0.28.23" "@vitest/ui": "npm:4.1.8" clsx: "npm:2.1.1" - ramda: "npm:0.28.0" + ramda: "npm:0.32.0" tailwind-merge: "npm:3.3.1" typescript: "npm:5.8.3" vitest: "npm:4.1.8" @@ -8461,10 +8462,10 @@ __metadata: languageName: node linkType: hard -"ramda@npm:0.28.0": - version: 0.28.0 - resolution: "ramda@npm:0.28.0" - checksum: 10c0/0f9dc0cc3b0432ff047f1e2a5e58860c531a84574674c0f52fef535efc6e1e07fa3851102fff3da7dd551a592c743f6f6fa521379a6aa5fe50266f8af8f0b570 +"ramda@npm:0.32.0": + version: 0.32.0 + resolution: "ramda@npm:0.32.0" + checksum: 10c0/6ad24338813cbf91932f7f160e33af9159a540580a43386b974365a8c98b8aa63e2a9d98affa9b2ac4b6628ff34705ddf9ced8f2987c04e3562f58f202c41cf9 languageName: node linkType: hard From afab64291c11b00d697e9c1b76f9e185f6028cba Mon Sep 17 00:00:00 2001 From: Anish George Date: Thu, 18 Jun 2026 21:08:11 +1000 Subject: [PATCH 12/29] chore: Update data for omarin --- .../_public/dnd/characters/omarin-kenate.tsx | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx index 8c8ebc8e..4d808617 100644 --- a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx +++ b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx @@ -195,4 +195,41 @@ const OmarinData: Character = { }, }, ], + speciesTraits: [ + { + name: 'Darkvision', + description: '120ft', + }, + { + name: 'Fey Ancestry', + description: + 'You have Advantage on saving throws you make to avoid or end the Charmed condition', + }, + { + name: 'Keen Sense', + description: 'Proficiency in Perception skill', + }, + { + name: 'Trance', + description: + "You don't need sleep and magic can't put you to sleep. You can finish a Long Rest in 4 hourse if you spend that time in a trance like state", + }, + { + name: 'Elven Lineage', + description: + 'You know the Faerie Fire, and Darkness spells. You can cast them once without a spell slot. Refresh on Long Rest. Wisdom is your spell-casting ability for this', + }, + ], + feats: [ + { + name: 'Tough', + description: + 'Whenever you gain character levels, your Hit Point maximum increases by an additional 2 points', + }, + { + name: 'Two-Weapon Fighting', + description: + 'When making the extra attack of the Light property, you can add the modifier to the damage', + }, + ], }; From 789e8a4ab26149b998a2976646cbd3ecf3248ba0 Mon Sep 17 00:00:00 2001 From: Anish George Date: Thu, 18 Jun 2026 15:44:10 +1000 Subject: [PATCH 13/29] feat: simplify feature lists --- .../_public/dnd/characters/omarin-kenate.tsx | 2 +- .../ClassFeatures/ClassFeatures.tsx | 33 ---------------- .../src/components/Feats/Feats.tsx | 38 ------------------- .../SpeciesTraits/SpeciesTraits.tsx | 38 ------------------- .../StandardCharacterSheet.tsx | 6 +-- .../feature-blocks/ClassFeatures.tsx | 18 +++++++++ .../src/components/feature-blocks/Feats.tsx | 15 ++++++++ .../feature-blocks/FeatureEntry.tsx | 13 +++++++ .../components/feature-blocks/FeatureList.tsx | 28 ++++++++++++++ .../feature-blocks/SpeciesTraits.tsx | 15 ++++++++ 10 files changed, 93 insertions(+), 113 deletions(-) delete mode 100644 packages/dnd-character-sheet/src/components/ClassFeatures/ClassFeatures.tsx delete mode 100644 packages/dnd-character-sheet/src/components/Feats/Feats.tsx delete mode 100644 packages/dnd-character-sheet/src/components/SpeciesTraits/SpeciesTraits.tsx create mode 100644 packages/dnd-character-sheet/src/components/feature-blocks/ClassFeatures.tsx create mode 100644 packages/dnd-character-sheet/src/components/feature-blocks/Feats.tsx create mode 100644 packages/dnd-character-sheet/src/components/feature-blocks/FeatureEntry.tsx create mode 100644 packages/dnd-character-sheet/src/components/feature-blocks/FeatureList.tsx create mode 100644 packages/dnd-character-sheet/src/components/feature-blocks/SpeciesTraits.tsx diff --git a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx index 4d808617..bea8525f 100644 --- a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx +++ b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx @@ -170,7 +170,7 @@ const OmarinData: Character = { description: 'You learn manuevers that are fueled by Superiority Dice. Your Superiority Dice is a D8', resource: { - name: 'Superiority Dice', + name: 'Superiority Dice (d8)', count: { kind: 'fixed', value: 4 }, refresh: { kind: 'any-rest' }, }, diff --git a/packages/dnd-character-sheet/src/components/ClassFeatures/ClassFeatures.tsx b/packages/dnd-character-sheet/src/components/ClassFeatures/ClassFeatures.tsx deleted file mode 100644 index ff14c51f..00000000 --- a/packages/dnd-character-sheet/src/components/ClassFeatures/ClassFeatures.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { useCharacter } from '../CharacterSheet'; -import { Panel } from '../layout/Panel'; -import { PanelTitle } from '../layout/PanelTitle'; - -export function ClassFeatures() { - const { character } = useCharacter(); - - return ( - - Class Features -
- {character.features.map((feature) => ( -
-

- {feature.name} -

- {feature.source && ( -

- {feature.source} -

- )} -

- {feature.description} -

-
- ))} -
-
- ); -} diff --git a/packages/dnd-character-sheet/src/components/Feats/Feats.tsx b/packages/dnd-character-sheet/src/components/Feats/Feats.tsx deleted file mode 100644 index b210963b..00000000 --- a/packages/dnd-character-sheet/src/components/Feats/Feats.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useCharacter } from '../CharacterSheet'; -import { Panel } from '../layout/Panel'; -import { PanelTitle } from '../layout/PanelTitle'; - -export function Feats() { - const { character } = useCharacter(); - const feats = character.feats ?? []; - - return ( - - Feats -
- {feats.length === 0 ? ( -

- No feats -

- ) : ( - feats.map((feat) => ( -
-

- {feat.name} -

-

- {feat.source} -

-

- {feat.description} -

-
- )) - )} -
-
- ); -} diff --git a/packages/dnd-character-sheet/src/components/SpeciesTraits/SpeciesTraits.tsx b/packages/dnd-character-sheet/src/components/SpeciesTraits/SpeciesTraits.tsx deleted file mode 100644 index c0906be2..00000000 --- a/packages/dnd-character-sheet/src/components/SpeciesTraits/SpeciesTraits.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useCharacter } from '../CharacterSheet'; -import { Panel } from '../layout/Panel'; -import { PanelTitle } from '../layout/PanelTitle'; - -export function SpeciesTraits() { - const { character } = useCharacter(); - const traits = character.speciesTraits ?? []; - - return ( - - Species Traits -
- {traits.length === 0 ? ( -

- No species traits -

- ) : ( - traits.map((trait) => ( -
-

- {trait.name} -

-

- {trait.source} -

-

- {trait.description} -

-
- )) - )} -
-
- ); -} diff --git a/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx b/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx index e4b0ca75..a8bee080 100644 --- a/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx +++ b/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx @@ -4,17 +4,17 @@ import { AbilityBox } from '../AbilityBox/AbilityBox'; import { AttackList } from '../AttackList/AttackList'; import { BackstoryBlock } from '../BackstoryBlock/BackstoryBlock'; import { CharacterSheet } from '../CharacterSheet'; -import { ClassFeatures } from '../ClassFeatures/ClassFeatures'; import { CombatRow } from '../CombatRow/CombatRow'; import { EquipmentBlock } from '../EquipmentBlock/EquipmentBlock'; import { EquipmentTraining } from '../EquipmentTraining/EquipmentTraining'; -import { Feats } from '../Feats/Feats'; +import { ClassFeatures } from '../feature-blocks/ClassFeatures'; +import { Feats } from '../feature-blocks/Feats'; +import { SpeciesTraits } from '../feature-blocks/SpeciesTraits'; import { HeroicInspiration } from '../HeroicInspiration/HeroicInspiration'; import { Page } from '../layout/Page'; import { ProficiencyBlock } from '../ProficiencyBlock/ProficiencyBlock'; import { Resources } from '../Resources/Resources'; import { SheetHeader } from '../SheetHeader/SheetHeader'; -import { SpeciesTraits } from '../SpeciesTraits/SpeciesTraits'; import { SpellcastingBlock } from '../SpellcastingBlock'; interface Props { diff --git a/packages/dnd-character-sheet/src/components/feature-blocks/ClassFeatures.tsx b/packages/dnd-character-sheet/src/components/feature-blocks/ClassFeatures.tsx new file mode 100644 index 00000000..ddc85d39 --- /dev/null +++ b/packages/dnd-character-sheet/src/components/feature-blocks/ClassFeatures.tsx @@ -0,0 +1,18 @@ +import { useCharacter } from '../CharacterSheet'; +import { FeatureList } from '../feature-blocks/FeatureList'; +import { Panel } from '../layout/Panel'; +import { PanelTitle } from '../layout/PanelTitle'; + +export function ClassFeatures() { + const { character } = useCharacter(); + + return ( + + Class Features + + + ); +} diff --git a/packages/dnd-character-sheet/src/components/feature-blocks/Feats.tsx b/packages/dnd-character-sheet/src/components/feature-blocks/Feats.tsx new file mode 100644 index 00000000..05984d30 --- /dev/null +++ b/packages/dnd-character-sheet/src/components/feature-blocks/Feats.tsx @@ -0,0 +1,15 @@ +import { useCharacter } from '../CharacterSheet'; +import { Panel } from '../layout/Panel'; +import { PanelTitle } from '../layout/PanelTitle'; +import { FeatureList } from './FeatureList'; + +export function Feats() { + const { character } = useCharacter(); + + return ( + + Feats + + + ); +} diff --git a/packages/dnd-character-sheet/src/components/feature-blocks/FeatureEntry.tsx b/packages/dnd-character-sheet/src/components/feature-blocks/FeatureEntry.tsx new file mode 100644 index 00000000..55ddbb97 --- /dev/null +++ b/packages/dnd-character-sheet/src/components/feature-blocks/FeatureEntry.tsx @@ -0,0 +1,13 @@ +import type { Feature } from '../../lib/models/feature'; + +export function FeatureEntry({ data: feat }: { data: Feature }) { + return ( +
+ {feat.name}. + {feat.cost && ( + (Cost: {feat.cost}) + )} +  {feat.description} +
+ ); +} diff --git a/packages/dnd-character-sheet/src/components/feature-blocks/FeatureList.tsx b/packages/dnd-character-sheet/src/components/feature-blocks/FeatureList.tsx new file mode 100644 index 00000000..6cf00a49 --- /dev/null +++ b/packages/dnd-character-sheet/src/components/feature-blocks/FeatureList.tsx @@ -0,0 +1,28 @@ +import { cn } from '@ageorgedev/toolbelt/cn'; +import type { Feature } from '../../lib/models/feature'; +import { FeatureEntry } from './FeatureEntry'; + +type FeatureListProps = React.HTMLAttributes & { + features: Feature[]; +}; + +export function FeatureList({ + className, + features, + ...props +}: FeatureListProps) { + if (!features.length) { + return ( +
+ No features +
+ ); + } + return ( +
+ {features.map((feature) => ( + + ))} +
+ ); +} diff --git a/packages/dnd-character-sheet/src/components/feature-blocks/SpeciesTraits.tsx b/packages/dnd-character-sheet/src/components/feature-blocks/SpeciesTraits.tsx new file mode 100644 index 00000000..0989e5a8 --- /dev/null +++ b/packages/dnd-character-sheet/src/components/feature-blocks/SpeciesTraits.tsx @@ -0,0 +1,15 @@ +import { useCharacter } from '../CharacterSheet'; +import { Panel } from '../layout/Panel'; +import { PanelTitle } from '../layout/PanelTitle'; +import { FeatureList } from './FeatureList'; + +export function SpeciesTraits() { + const { character } = useCharacter(); + + return ( + + Species Traits + + + ); +} From dd23bcea61f6f303eb19f61d867d54b43501319b Mon Sep 17 00:00:00 2001 From: Anish George Date: Thu, 18 Jun 2026 15:50:12 +1000 Subject: [PATCH 14/29] feat: Get overall layout a bit better --- .../src/components/EquipmentTraining/EquipmentTraining.tsx | 6 +++--- .../src/components/ProficiencyBlock/ProficiencyBlock.tsx | 2 +- .../StandardCharacterSheet/StandardCharacterSheet.tsx | 4 ++-- packages/dnd-character-sheet/src/components/layout/Page.tsx | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/dnd-character-sheet/src/components/EquipmentTraining/EquipmentTraining.tsx b/packages/dnd-character-sheet/src/components/EquipmentTraining/EquipmentTraining.tsx index 2fd03797..0568b505 100644 --- a/packages/dnd-character-sheet/src/components/EquipmentTraining/EquipmentTraining.tsx +++ b/packages/dnd-character-sheet/src/components/EquipmentTraining/EquipmentTraining.tsx @@ -15,7 +15,7 @@ export function EquipmentTraining() { const { character } = useCharacter(); return ( - + Equipment Training & Proficiencies @@ -34,12 +34,12 @@ export function EquipmentTraining() {
Weapons -

{character.weaponProficiencies.join(', ')}

+

{character.weaponProficiencies.join(', ')}

Tools -

{character.toolProficiencies.join(', ')}

+

{character.toolProficiencies.join(', ')}

); diff --git a/packages/dnd-character-sheet/src/components/ProficiencyBlock/ProficiencyBlock.tsx b/packages/dnd-character-sheet/src/components/ProficiencyBlock/ProficiencyBlock.tsx index ed9d20a2..390693aa 100644 --- a/packages/dnd-character-sheet/src/components/ProficiencyBlock/ProficiencyBlock.tsx +++ b/packages/dnd-character-sheet/src/components/ProficiencyBlock/ProficiencyBlock.tsx @@ -13,7 +13,7 @@ export function ProficiencyBlock() { bottomLeftCorner="scooped" > Proficiency Bonus -
+
+{derived.proficiencyBonus}
diff --git a/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx b/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx index a8bee080..d0f2b970 100644 --- a/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx +++ b/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx @@ -31,14 +31,14 @@ export function StandardCharacterSheet({ data }: Props) {
-
+
-
+
diff --git a/packages/dnd-character-sheet/src/components/layout/Page.tsx b/packages/dnd-character-sheet/src/components/layout/Page.tsx index 3b8a82cc..ad295065 100644 --- a/packages/dnd-character-sheet/src/components/layout/Page.tsx +++ b/packages/dnd-character-sheet/src/components/layout/Page.tsx @@ -11,7 +11,7 @@ export function Page({
Date: Thu, 18 Jun 2026 21:15:34 +1000 Subject: [PATCH 15/29] chore: Add the spells for omarin --- .../_public/dnd/characters/omarin-kenate.tsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx index bea8525f..bef93a19 100644 --- a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx +++ b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx @@ -232,4 +232,35 @@ const OmarinData: Character = { 'When making the extra attack of the Light property, you can add the modifier to the damage', }, ], + + spellcasting: { + ability: Ability.Wisdom, + spells: [ + { + name: 'Faerie Fire', + level: 1, + castingTime: 'Action', + range: '60ft', + duration: '1min', + concentration: true, + alwaysPrepared: true, + freeUses: 1, + }, + { + name: 'Darkness', + level: 2, + castingTime: 'Action', + range: '60ft', + duration: '10min', + concentration: true, + alwaysPrepared: true, + freeUses: 1, + notes: '15ft Sphere', + }, + ], + slots: { + 1: 0, + 2: 0, + }, + }, }; From fe0ceadec0245a86f1c25c34b910c4238c448595 Mon Sep 17 00:00:00 2001 From: Anish George Date: Thu, 18 Jun 2026 22:42:15 +1000 Subject: [PATCH 16/29] feat: Start splitting up spellcasting section --- .../_public/dnd/characters/omarin-kenate.tsx | 15 +- .../src/components/AbilityBox/AbilityBox.tsx | 5 +- .../src/components/AttackList/AttackList.tsx | 11 +- .../src/components/CombatRow/CombatRow.tsx | 5 +- .../src/components/Resources/Resources.tsx | 5 +- .../components/SheetHeader/SheetHeader.tsx | 35 +--- .../SpellcastingBlock/SpellcastingBlock.tsx | 149 ++++++++++++++---- .../src/components/layout/checkables.tsx | 22 +++ .../src/components/layout/labels.tsx | 20 +++ packages/dnd-character-sheet/src/lib/utils.ts | 7 + 10 files changed, 191 insertions(+), 83 deletions(-) create mode 100644 packages/dnd-character-sheet/src/components/layout/labels.tsx create mode 100644 packages/dnd-character-sheet/src/lib/utils.ts diff --git a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx index bef93a19..53f9d5f9 100644 --- a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx +++ b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx @@ -235,6 +235,10 @@ const OmarinData: Character = { spellcasting: { ability: Ability.Wisdom, + pactMagic: { + level: 3, + slots: 2, + }, spells: [ { name: 'Faerie Fire', @@ -259,8 +263,15 @@ const OmarinData: Character = { }, ], slots: { - 1: 0, - 2: 0, + 1: 4, + 2: 3, + 3: 3, + 4: 3, + 5: 3, + 6: 2, + 7: 2, + 8: 1, + 9: 1, }, }, }; diff --git a/packages/dnd-character-sheet/src/components/AbilityBox/AbilityBox.tsx b/packages/dnd-character-sheet/src/components/AbilityBox/AbilityBox.tsx index a001911f..6076a10b 100644 --- a/packages/dnd-character-sheet/src/components/AbilityBox/AbilityBox.tsx +++ b/packages/dnd-character-sheet/src/components/AbilityBox/AbilityBox.tsx @@ -1,5 +1,6 @@ import { ABILITY_DETAILS, type Ability } from '../../lib/models/abilities'; import { AbilitySkillGrouping } from '../../lib/models/skills'; +import { formatMod } from '../../lib/utils'; import { useCharacter } from '../CharacterSheet'; import { BigNumber } from '../layout/BigNumber'; import type { CheckedState } from '../layout/checkables'; @@ -7,10 +8,6 @@ import { CircleCheck } from '../layout/checkables'; import { Panel } from '../layout/Panel'; import { PanelTitle } from '../layout/PanelTitle'; -function formatMod(mod: number): string { - return mod >= 0 ? `+${mod}` : `${mod}`; -} - function proficiencyState( isProficient: boolean, hasExpertise: boolean diff --git a/packages/dnd-character-sheet/src/components/AttackList/AttackList.tsx b/packages/dnd-character-sheet/src/components/AttackList/AttackList.tsx index e8ce9c5f..cbe33726 100644 --- a/packages/dnd-character-sheet/src/components/AttackList/AttackList.tsx +++ b/packages/dnd-character-sheet/src/components/AttackList/AttackList.tsx @@ -2,6 +2,7 @@ import { cn } from '@ageorgedev/toolbelt/cn'; import { ABILITY_DETAILS } from '../../lib/models/abilities'; import type { Attack, AttackDamage } from '../../lib/models/attacks'; import type { DerivedStats } from '../../lib/models/derived-stats'; +import { formatModIgnoreZero } from '../../lib/utils'; import { useCharacter } from '../CharacterSheet'; import { DiamondCheck } from '../layout/checkables'; import { Panel } from '../layout/Panel'; @@ -94,7 +95,7 @@ function calcAttackBonus( const profBonus = attack.notProficient ? 0 : derived.proficiencyBonus; const base = abilityMod + profBonus + (attack.attackBonusMod ?? 0); return { - bonusText: formatBonus(base), + bonusText: formatModIgnoreZero(base), damageBonus: abilityMod + (attack.attackBonusMod ?? 0), }; } @@ -108,7 +109,7 @@ function calcAttackBonus( } const totalAttackBonus = spellAttackBonus + (attack.attackBonusMod ?? 0); return { - bonusText: formatBonus(totalAttackBonus), + bonusText: formatModIgnoreZero(totalAttackBonus), damageBonus: 0 + (attack.attackBonusMod ?? 0), }; } @@ -131,12 +132,8 @@ function calcAttackBonus( function formatDamage(entries: AttackDamage[], mod: number): string { return entries .map((entry) => { - const suffix = entry.disableModifier ? '' : formatBonus(mod); + const suffix = entry.disableModifier ? '' : formatModIgnoreZero(mod); return `${entry.dice}${suffix} ${entry.type}`; }) .join(' + '); } - -function formatBonus(n: number): string { - return n === 0 ? '' : n > 0 ? `+${n}` : `${n}`; -} diff --git a/packages/dnd-character-sheet/src/components/CombatRow/CombatRow.tsx b/packages/dnd-character-sheet/src/components/CombatRow/CombatRow.tsx index 23303f4b..0b1e6609 100644 --- a/packages/dnd-character-sheet/src/components/CombatRow/CombatRow.tsx +++ b/packages/dnd-character-sheet/src/components/CombatRow/CombatRow.tsx @@ -1,11 +1,8 @@ +import { formatMod } from '../../lib/utils'; import { useCharacter } from '../CharacterSheet'; import { Panel } from '../layout/Panel'; import { PanelTitle } from '../layout/PanelTitle'; -function formatMod(mod: number): string { - return mod >= 0 ? `+${mod}` : `${mod}`; -} - function StatCell({ label, children, diff --git a/packages/dnd-character-sheet/src/components/Resources/Resources.tsx b/packages/dnd-character-sheet/src/components/Resources/Resources.tsx index 8e0e3da7..6c94bd97 100644 --- a/packages/dnd-character-sheet/src/components/Resources/Resources.tsx +++ b/packages/dnd-character-sheet/src/components/Resources/Resources.tsx @@ -4,7 +4,7 @@ import type { Character } from '../../lib/models/character'; import type { DerivedStats } from '../../lib/models/derived-stats'; import type { Feature } from '../../lib/models/feature'; import { useCharacter } from '../CharacterSheet'; -import { CircleCheck } from '../layout/checkables'; +import { EmptyCheckList } from '../layout/checkables'; import { Panel } from '../layout/Panel'; import { PanelTitle } from '../layout/PanelTitle'; @@ -33,7 +33,8 @@ export function Resources() { {r.refresh}
{r.display === 'dots' ? ( - Array.from({ length: r.value }, (_, i) => ) + // Array.from({ length: r.value }, (_, i) => ) + ) : ( <>
diff --git a/packages/dnd-character-sheet/src/components/SheetHeader/SheetHeader.tsx b/packages/dnd-character-sheet/src/components/SheetHeader/SheetHeader.tsx index 5d0eaad4..b403854e 100644 --- a/packages/dnd-character-sheet/src/components/SheetHeader/SheetHeader.tsx +++ b/packages/dnd-character-sheet/src/components/SheetHeader/SheetHeader.tsx @@ -1,8 +1,8 @@ -import { cn } from '@ageorgedev/toolbelt/cn'; import { useCharacter } from '../CharacterSheet'; import { BigNumber } from '../layout/BigNumber'; -import { DiamondCheck } from '../layout/checkables'; +import { DiamondCheck, EmptyCheckList } from '../layout/checkables'; import { VerticalDivider } from '../layout/dividers'; +import { BasicLabel, LabelUnder } from '../layout/labels'; import { Panel } from '../layout/Panel'; import { PanelTitle } from '../layout/PanelTitle'; import { VerticalSubPanel } from '../layout/SubPanel'; @@ -107,19 +107,11 @@ function HealthAndDeathBlock() { Saves
-
- - - -
+ Successes
-
- - - -
+ Failures
@@ -144,25 +136,6 @@ function NameField({ ); } -function LabelUnder({ - className, - ...props -}: React.HTMLAttributes) { - return ; -} - -function BasicLabel({ - children, - className, - ...props -}: React.HTMLAttributes) { - return ( -
- {children} -
- ); -} - function joinItems< T extends Record, K extends keyof T, diff --git a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingBlock.tsx b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingBlock.tsx index 3c2a8997..97000ce3 100644 --- a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingBlock.tsx +++ b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingBlock.tsx @@ -1,49 +1,132 @@ -import type { Spell } from '../../lib/models/spellcasting'; +import { ABILITY_DETAILS } from '../../lib/models'; +import { formatMod } from '../../lib/utils'; import { useCharacter } from '../CharacterSheet'; -import { Panel } from '../layout/Panel'; +import { BigNumber } from '../layout/BigNumber'; +import { EmptyCheckList } from '../layout/checkables'; +import { LabelUnder } from '../layout/labels'; +import { Panel, type PanelProps } from '../layout/Panel'; import { PanelTitle } from '../layout/PanelTitle'; -import { SpellcastingHeader } from './SpellcastingHeader'; -import { SpellLevelSection } from './SpellLevelSection'; export function SpellcastingBlock() { - const { character } = useCharacter(); - const { spellcasting } = character; - if (!spellcasting) return null; + return ( +
+ + +
+ ); +} - // Collect all levels that have spells or defined slots - const spellsByLevel = new Map(); +function SpellAbilityPanel(props: PanelProps) { + const { character, derived } = useCharacter(); - for (const spell of spellcasting.spells) { - const bucket = spellsByLevel.get(spell.level) ?? []; - bucket.push(spell); - spellsByLevel.set(spell.level, bucket); + if (!character.spellcasting) { + return null; } - // Ensure levels with slots but no spells still appear - if (spellcasting.slots) { - for (const levelStr of Object.keys(spellcasting.slots)) { - const level = Number(levelStr); - if (!spellsByLevel.has(level)) { - spellsByLevel.set(level, []); - } - } - } + return ( + +

+ {ABILITY_DETAILS[character.spellcasting.ability].label} +

+ Spellcasting Ability + +
+ Spellcasting Modifier + + {formatMod(derived.abilityModifiers[character.spellcasting.ability])} + - const sortedLevels = [...spellsByLevel.keys()].sort((a, b) => a - b); + Spellcasting Save DC + + {derived.spellSaveDC} + + + Spellcasting Attack Bonus + + {formatMod(derived.spellAttackBonus ?? 0)} + +
+
+ ); +} + +function SpellSlotsPanel(props: PanelProps) { + const { character } = useCharacter(); + const { spellcasting } = character; + if (!spellcasting) return null; + + const slotLevels = spellcasting.slots + ? (Object.entries(spellcasting.slots) as [string, number][]).sort( + ([a], [b]) => Number(a) - Number(b) + ) + : []; return ( - - Spellcasting - -
- {sortedLevels.map((level) => ( - + + Spell Slots + +
+ {spellcasting.pactMagic && ( +
+ + Pact Magic level {spellcasting.pactMagic.level} + +
+ +
+ )} + + {slotLevels.map(([level, count]) => ( +
+ Level {level} +
+ +
))}
); } + +// export function SpellcastingBlock() { +// const { character } = useCharacter(); +// const { spellcasting } = character; +// if (!spellcasting) return null; + +// // Collect all levels that have spells or defined slots +// const spellsByLevel = new Map(); + +// for (const spell of spellcasting.spells) { +// const bucket = spellsByLevel.get(spell.level) ?? []; +// bucket.push(spell); +// spellsByLevel.set(spell.level, bucket); +// } + +// // Ensure levels with slots but no spells still appear +// if (spellcasting.slots) { +// for (const levelStr of Object.keys(spellcasting.slots)) { +// const level = Number(levelStr); +// if (!spellsByLevel.has(level)) { +// spellsByLevel.set(level, []); +// } +// } +// } + +// const sortedLevels = [...spellsByLevel.keys()].sort((a, b) => a - b); + +// return ( +// +// Spellcasting +// +//
+// {sortedLevels.map((level) => ( +// +// ))} +//
+//
+// ); +// } diff --git a/packages/dnd-character-sheet/src/components/layout/checkables.tsx b/packages/dnd-character-sheet/src/components/layout/checkables.tsx index 201e9dd1..1c6a7c0b 100644 --- a/packages/dnd-character-sheet/src/components/layout/checkables.tsx +++ b/packages/dnd-character-sheet/src/components/layout/checkables.tsx @@ -33,3 +33,25 @@ const CHECK_FILL: Record<'true' | 'false' | 'special', string> = { special: 'bg-secondary-surface-2 border-secondary-foreground', false: '', }; + +export function EmptyCheckList({ + className, + count, + kind = 'circle', + ...props +}: React.HtmlHTMLAttributes & { + count: number; + kind?: 'circle' | 'diamond'; +}) { + const Comp = kind === 'circle' ? CircleCheck : DiamondCheck; + const gapClass = kind === 'circle' ? 'gap-1' : 'gap-2'; // Diamonds need a bit more space for visually same space + + return ( +
+ {Array.from({ length: count }, (_, i) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: Empty checkboxes, will be fine + + ))} +
+ ); +} diff --git a/packages/dnd-character-sheet/src/components/layout/labels.tsx b/packages/dnd-character-sheet/src/components/layout/labels.tsx new file mode 100644 index 00000000..040119be --- /dev/null +++ b/packages/dnd-character-sheet/src/components/layout/labels.tsx @@ -0,0 +1,20 @@ +import { cn } from '@ageorgedev/toolbelt/cn'; + +export function BasicLabel({ + children, + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ {children} +
+ ); +} + +export function LabelUnder({ + className, + ...props +}: React.HTMLAttributes) { + return ; +} diff --git a/packages/dnd-character-sheet/src/lib/utils.ts b/packages/dnd-character-sheet/src/lib/utils.ts new file mode 100644 index 00000000..db37ea1c --- /dev/null +++ b/packages/dnd-character-sheet/src/lib/utils.ts @@ -0,0 +1,7 @@ +export function formatMod(mod: number): string { + return mod >= 0 ? `+${mod}` : `${mod}`; +} + +export function formatModIgnoreZero(n: number): string { + return n === 0 ? '' : n > 0 ? `+${n}` : `${n}`; +} From 794c4c987210314e921f6f14a066ed65ebd98c4e Mon Sep 17 00:00:00 2001 From: Anish George Date: Fri, 19 Jun 2026 14:57:21 +1000 Subject: [PATCH 17/29] feat: finish spell slots section --- .../_public/dnd/characters/omarin-kenate.tsx | 18 +--- .../SpellcastingBlock/SpellcastingBlock.tsx | 86 ++++++++++++++----- .../src/components/layout/dividers.tsx | 22 ++++- .../src/lib/models/spellcasting.ts | 2 + 4 files changed, 88 insertions(+), 40 deletions(-) diff --git a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx index 53f9d5f9..34072c8d 100644 --- a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx +++ b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx @@ -235,10 +235,6 @@ const OmarinData: Character = { spellcasting: { ability: Ability.Wisdom, - pactMagic: { - level: 3, - slots: 2, - }, spells: [ { name: 'Faerie Fire', @@ -262,16 +258,8 @@ const OmarinData: Character = { notes: '15ft Sphere', }, ], - slots: { - 1: 4, - 2: 3, - 3: 3, - 4: 3, - 5: 3, - 6: 2, - 7: 2, - 8: 1, - 9: 1, - }, + slots: {}, + numberOfCantrips: 0, + numberOfPreparedSpells: 0, }, }; diff --git a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingBlock.tsx b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingBlock.tsx index 97000ce3..727fa5fa 100644 --- a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingBlock.tsx +++ b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingBlock.tsx @@ -1,8 +1,11 @@ +import { cn } from '@ageorgedev/toolbelt/cn'; +import { useMemo } from 'react'; import { ABILITY_DETAILS } from '../../lib/models'; import { formatMod } from '../../lib/utils'; import { useCharacter } from '../CharacterSheet'; import { BigNumber } from '../layout/BigNumber'; import { EmptyCheckList } from '../layout/checkables'; +import { HorizontalDivider } from '../layout/dividers'; import { LabelUnder } from '../layout/labels'; import { Panel, type PanelProps } from '../layout/Panel'; import { PanelTitle } from '../layout/PanelTitle'; @@ -36,11 +39,15 @@ function SpellAbilityPanel(props: PanelProps) { {formatMod(derived.abilityModifiers[character.spellcasting.ability])} + + Spellcasting Save DC {derived.spellSaveDC} + + Spellcasting Attack Bonus {formatMod(derived.spellAttackBonus ?? 0)} @@ -53,36 +60,73 @@ function SpellAbilityPanel(props: PanelProps) { function SpellSlotsPanel(props: PanelProps) { const { character } = useCharacter(); const { spellcasting } = character; - if (!spellcasting) return null; - const slotLevels = spellcasting.slots - ? (Object.entries(spellcasting.slots) as [string, number][]).sort( - ([a], [b]) => Number(a) - Number(b) - ) - : []; + const allSlots = useMemo(() => { + const spellSlots = spellcasting?.slots + ? (Object.entries(spellcasting.slots) as [string, number][]) + .sort(([a], [b]) => Number(a) - Number(b)) + .map(([level, slots]) => [`Level ${level}`, slots] as const) + : []; + + const pactMagicSlots = spellcasting?.pactMagic + ? [ + [ + `Pact Magic level ${spellcasting.pactMagic.level}`, + spellcasting.pactMagic.slots, + ] as const, + ] + : []; + + return [...pactMagicSlots, ...spellSlots]; + }, [spellcasting]); + + const columnStyle = useMemo(() => { + switch (allSlots.length) { + case 0: + case 1: + return ''; + case 2: + case 4: + return 'columns-2'; + default: + return 'columns-3'; + } + }, [allSlots]); + + if (!spellcasting) return null; return ( - + Spell Slots +
+ Cantrips:{' '} + + {spellcasting.numberOfCantrips ?? 0} + + , Prepared Spells:{' '} + + {spellcasting.numberOfPreparedSpells ?? 0} + +
-
- {spellcasting.pactMagic && ( -
- - Pact Magic level {spellcasting.pactMagic.level} - -
- -
- )} - - {slotLevels.map(([level, count]) => ( -
- Level {level} +
+ {allSlots.map(([name, count]) => ( +
+ {name}
))} + {!allSlots.length && ( +
+ No Slots +
+ )}
); diff --git a/packages/dnd-character-sheet/src/components/layout/dividers.tsx b/packages/dnd-character-sheet/src/components/layout/dividers.tsx index 15010ce2..260cb705 100644 --- a/packages/dnd-character-sheet/src/components/layout/dividers.tsx +++ b/packages/dnd-character-sheet/src/components/layout/dividers.tsx @@ -1,9 +1,23 @@ -export function VerticalDivider() { +import { cn } from '@ageorgedev/toolbelt/cn'; + +export function VerticalDivider({ + className, + ...props +}: React.HTMLAttributes) { return ( -
+
); } -export function HorizontalDivider() { - return
; +export function HorizontalDivider({ + className, + ...props +}: React.HTMLAttributes) { + return
; } diff --git a/packages/dnd-character-sheet/src/lib/models/spellcasting.ts b/packages/dnd-character-sheet/src/lib/models/spellcasting.ts index 2607e1f3..3bef6997 100644 --- a/packages/dnd-character-sheet/src/lib/models/spellcasting.ts +++ b/packages/dnd-character-sheet/src/lib/models/spellcasting.ts @@ -37,4 +37,6 @@ export interface Spellcasting { slots?: Partial>; pactMagic?: { level: number; slots: number }; spells: Spell[]; + numberOfCantrips?: number; + numberOfPreparedSpells?: number; } From ee05a3be20e79dd0580adaef2d9d273db5e21df1 Mon Sep 17 00:00:00 2001 From: Anish George Date: Sat, 20 Jun 2026 00:36:23 +1000 Subject: [PATCH 18/29] feat: Finish spell list basic --- .../SpellcastingBlock/SpellLevelSection.tsx | 38 ---- .../SpellcastingBlock/SpellcastingBlock.tsx | 205 +++++++++++++----- .../SpellcastingBlock/SpellcastingHeader.tsx | 101 --------- .../StandardCharacterSheet.tsx | 2 +- .../src/components/layout/checkables.tsx | 10 +- 5 files changed, 164 insertions(+), 192 deletions(-) delete mode 100644 packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellLevelSection.tsx delete mode 100644 packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingHeader.tsx diff --git a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellLevelSection.tsx b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellLevelSection.tsx deleted file mode 100644 index ead36292..00000000 --- a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellLevelSection.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import type { Spell } from '../../lib/models/spellcasting'; -import { SpellRow } from './SpellRow'; - -interface SpellLevelSectionProps { - level: number; - spells: Spell[]; -} - -export function SpellLevelSection({ level, spells }: SpellLevelSectionProps) { - const label = level === 0 ? 'Cantrips' : `Level ${level}`; - - return ( -
-
- - {label} - -
- {spells.length > 0 && ( -
- {spells.map((spell) => ( - - ))} -
- )} -
- ); -} diff --git a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingBlock.tsx b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingBlock.tsx index 727fa5fa..029c1a6c 100644 --- a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingBlock.tsx +++ b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingBlock.tsx @@ -1,10 +1,14 @@ import { cn } from '@ageorgedev/toolbelt/cn'; import { useMemo } from 'react'; -import { ABILITY_DETAILS } from '../../lib/models'; +import { ABILITY_DETAILS, type Spell } from '../../lib/models'; import { formatMod } from '../../lib/utils'; import { useCharacter } from '../CharacterSheet'; import { BigNumber } from '../layout/BigNumber'; -import { EmptyCheckList } from '../layout/checkables'; +import { + CircleCheck, + DiamondCheck, + EmptyCheckList, +} from '../layout/checkables'; import { HorizontalDivider } from '../layout/dividers'; import { LabelUnder } from '../layout/labels'; import { Panel, type PanelProps } from '../layout/Panel'; @@ -12,9 +16,12 @@ import { PanelTitle } from '../layout/PanelTitle'; export function SpellcastingBlock() { return ( -
- - +
+
+ + +
+
); } @@ -58,8 +65,9 @@ function SpellAbilityPanel(props: PanelProps) { } function SpellSlotsPanel(props: PanelProps) { - const { character } = useCharacter(); - const { spellcasting } = character; + const { + character: { spellcasting }, + } = useCharacter(); const allSlots = useMemo(() => { const spellSlots = spellcasting?.slots @@ -132,45 +140,144 @@ function SpellSlotsPanel(props: PanelProps) { ); } -// export function SpellcastingBlock() { -// const { character } = useCharacter(); -// const { spellcasting } = character; -// if (!spellcasting) return null; - -// // Collect all levels that have spells or defined slots -// const spellsByLevel = new Map(); - -// for (const spell of spellcasting.spells) { -// const bucket = spellsByLevel.get(spell.level) ?? []; -// bucket.push(spell); -// spellsByLevel.set(spell.level, bucket); -// } - -// // Ensure levels with slots but no spells still appear -// if (spellcasting.slots) { -// for (const levelStr of Object.keys(spellcasting.slots)) { -// const level = Number(levelStr); -// if (!spellsByLevel.has(level)) { -// spellsByLevel.set(level, []); -// } -// } -// } - -// const sortedLevels = [...spellsByLevel.keys()].sort((a, b) => a - b); - -// return ( -// -// Spellcasting -// -//
-// {sortedLevels.map((level) => ( -// -// ))} -//
-//
-// ); -// } +function SpellListAsTable() { + const { + character: { spellcasting }, + } = useCharacter(); + + const totalRows = 38; // Eyeballed for typical a4 size + const emptyRows = totalRows - (spellcasting?.spells.length ?? 0); + + return ( + + Spells + + + + + + + + + + + + + + + + {spellcasting?.spells.map((spell) => ( + + ))} + {Array.from({ length: emptyRows }, (_, i) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: not required for this + + ))} + +
PrepLevel  NameCasting TimeRangeDurationConcentration, Ritual & Material consumedNotes
+
+ ); +} + +function EmptyRow() { + return ( +
+   + + + + + + C + + + R + + + M + + +
+ {spell.alwaysPrepared ? ( + AP + ) : ( + + )} + {spell.level} +
+ {spell.name} + + {!!spell.freeUses && ( + + free x{spell.freeUses}{' '} + {' '} +   + + )} +
+
{spell.castingTime}{spell.range}{spell.duration} + + C + + + R + + + M + + {spell.notes}
+ ); +} + +function Td({ + className, + ...props +}: React.ThHTMLAttributes) { + return ( + + ); +} diff --git a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingHeader.tsx b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingHeader.tsx deleted file mode 100644 index acca919a..00000000 --- a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingHeader.tsx +++ /dev/null @@ -1,101 +0,0 @@ -// biome-ignore-all lint/suspicious/noArrayIndexKey: Not important -import { useCharacter } from '../CharacterSheet'; -import { CircleCheck } from '../layout/checkables'; - -const ABILITY_LABELS: Record = { - strength: 'Strength', - dexterity: 'Dexterity', - constitution: 'Constitution', - intelligence: 'Intelligence', - wisdom: 'Wisdom', - charisma: 'Charisma', -}; - -function formatBonus(n: number): string { - return n >= 0 ? `+${n}` : `${n}`; -} - -export function SpellcastingHeader() { - const { character, derived } = useCharacter(); - const { spellcasting } = character; - if (!spellcasting) return null; - - const abilityLabel = - ABILITY_LABELS[spellcasting.ability] ?? spellcasting.ability; - const slotLevels = spellcasting.slots - ? (Object.entries(spellcasting.slots) as [string, number][]).sort( - ([a], [b]) => Number(a) - Number(b) - ) - : []; - - return ( -
-
-
- - Ability - - - {abilityLabel} - -
-
- - Save DC - - - {derived.spellSaveDC ?? '—'} - -
-
- - Spell Attack - - - {derived.spellAttackBonus != null - ? formatBonus(derived.spellAttackBonus) - : '—'} - -
-
- - {slotLevels.length > 0 && ( -
- - Slots - - {slotLevels.map(([level, count]) => ( -
- - {level}: - -
- {Array.from({ length: count }, (_, i) => ( - - ))} -
-
- ))} -
- )} - - {spellcasting.pactMagic && ( -
- - Pact - -
- - {spellcasting.pactMagic.level}: - -
- {Array.from({ length: spellcasting.pactMagic.slots }, (_, i) => ( - - ))} -
-
-
- )} -
- ); -} diff --git a/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx b/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx index d0f2b970..af7b37e7 100644 --- a/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx +++ b/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx @@ -61,7 +61,7 @@ export function StandardCharacterSheet({ data }: Props) { -
+
{data.spellcasting && }
diff --git a/packages/dnd-character-sheet/src/components/layout/checkables.tsx b/packages/dnd-character-sheet/src/components/layout/checkables.tsx index 1c6a7c0b..35404e9d 100644 --- a/packages/dnd-character-sheet/src/components/layout/checkables.tsx +++ b/packages/dnd-character-sheet/src/components/layout/checkables.tsx @@ -9,7 +9,12 @@ export function CircleCheck({ className, ...props }: CheckableProps) { } export function DiamondCheck({ className, ...props }: CheckableProps) { - return ; + return ( + + ); } function BaseCheck({ checked = false, className, ...props }: CheckableProps) { @@ -44,10 +49,9 @@ export function EmptyCheckList({ kind?: 'circle' | 'diamond'; }) { const Comp = kind === 'circle' ? CircleCheck : DiamondCheck; - const gapClass = kind === 'circle' ? 'gap-1' : 'gap-2'; // Diamonds need a bit more space for visually same space return ( -
+
{Array.from({ length: count }, (_, i) => ( // biome-ignore lint/suspicious/noArrayIndexKey: Empty checkboxes, will be fine From 407fd4c0619057a0f94af5ceb1f401d1022ebb1d Mon Sep 17 00:00:00 2001 From: Anish George Date: Sat, 20 Jun 2026 12:01:51 +1000 Subject: [PATCH 19/29] feat: Finish spell list --- .../src/characters/example-wizard.data.ts | 28 +- .../SpellcastingBlock/SpellAbilityPanel.tsx | 45 +++ .../SpellcastingBlock/SpellList.tsx | 196 +++++++++++++ .../components/SpellcastingBlock/SpellRow.tsx | 72 ----- .../SpellcastingBlock/SpellSlotsPanel.tsx | 82 ++++++ .../SpellcastingBlock/SpellcastingBlock.tsx | 276 +----------------- .../src/components/SpellcastingBlock/index.ts | 1 - .../StandardCharacterSheet.tsx | 4 +- .../src/components/layout/Page.module.css | 3 +- .../src/components/layout/Page.tsx | 2 +- 10 files changed, 346 insertions(+), 363 deletions(-) create mode 100644 packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellAbilityPanel.tsx create mode 100644 packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellList.tsx delete mode 100644 packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellRow.tsx create mode 100644 packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellSlotsPanel.tsx delete mode 100644 packages/dnd-character-sheet/src/components/SpellcastingBlock/index.ts diff --git a/packages/dnd-character-sheet/src/characters/example-wizard.data.ts b/packages/dnd-character-sheet/src/characters/example-wizard.data.ts index 0998f7a5..f15f8763 100644 --- a/packages/dnd-character-sheet/src/characters/example-wizard.data.ts +++ b/packages/dnd-character-sheet/src/characters/example-wizard.data.ts @@ -164,7 +164,7 @@ export const exampleWizard: Character = { name: 'Fire Bolt', level: 0, school: 'Evocation', - castingTime: '1 action', + castingTime: 'Action', range: '120 ft', duration: 'Instantaneous', components: { verbal: true, somatic: true }, @@ -173,7 +173,7 @@ export const exampleWizard: Character = { name: 'Mage Hand', level: 0, school: 'Conjuration', - castingTime: '1 action', + castingTime: 'Action', range: '30 ft', duration: '1 minute', components: { verbal: true, somatic: true }, @@ -182,7 +182,7 @@ export const exampleWizard: Character = { name: 'Prestidigitation', level: 0, school: 'Transmutation', - castingTime: '1 action', + castingTime: 'Action', range: '10 ft', duration: 'Up to 1 hour', components: { verbal: true, somatic: true }, @@ -192,9 +192,9 @@ export const exampleWizard: Character = { name: 'Detect Magic', level: 1, school: 'Divination', - castingTime: '1 action', + castingTime: 'Action', range: 'Self', - duration: 'Concentration, up to 10 minutes', + duration: '10 minutes', concentration: true, ritual: true, components: { verbal: true, somatic: true }, @@ -204,7 +204,7 @@ export const exampleWizard: Character = { name: 'Magic Missile', level: 1, school: 'Evocation', - castingTime: '1 action', + castingTime: 'Action', range: '120 ft', duration: 'Instantaneous', components: { verbal: true, somatic: true }, @@ -213,7 +213,7 @@ export const exampleWizard: Character = { name: 'Shield', level: 1, school: 'Abjuration', - castingTime: '1 reaction', + castingTime: 'Reaction', range: 'Self', duration: '1 round', components: { verbal: true, somatic: true }, @@ -222,7 +222,7 @@ export const exampleWizard: Character = { name: 'Misty Step', level: 2, school: 'Conjuration', - castingTime: '1 bonus action', + castingTime: 'Bonus Action', range: 'Self', duration: 'Instantaneous', components: { verbal: true }, @@ -231,7 +231,7 @@ export const exampleWizard: Character = { name: 'Scorching Ray', level: 2, school: 'Evocation', - castingTime: '1 action', + castingTime: 'Action', range: '120 ft', duration: 'Instantaneous', components: { verbal: true, somatic: true }, @@ -240,9 +240,9 @@ export const exampleWizard: Character = { name: 'Hypnotic Pattern', level: 3, school: 'Illusion', - castingTime: '1 action', + castingTime: 'Action', range: '120 ft', - duration: 'Concentration, up to 1 minute', + duration: '1 minute', concentration: true, components: { verbal: false, somatic: true, materialConsumed: false }, notes: @@ -252,7 +252,7 @@ export const exampleWizard: Character = { name: 'Fireball', level: 3, school: 'Evocation', - castingTime: '1 action', + castingTime: 'Action', range: '150 ft', duration: 'Instantaneous', components: { verbal: true, somatic: true, materialConsumed: true }, @@ -262,9 +262,9 @@ export const exampleWizard: Character = { name: 'Banishment', level: 4, school: 'Abjuration', - castingTime: '1 action', + castingTime: 'Action', range: '60 ft', - duration: 'Concentration, up to 1 minute', + duration: '1 minute', concentration: true, components: { verbal: true, somatic: true, materialConsumed: false }, alternativeAbility: Ability.Charisma, diff --git a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellAbilityPanel.tsx b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellAbilityPanel.tsx new file mode 100644 index 00000000..f6f4c75f --- /dev/null +++ b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellAbilityPanel.tsx @@ -0,0 +1,45 @@ +import { ABILITY_DETAILS } from '../../lib/models'; +import { formatMod } from '../../lib/utils'; +import { useCharacter } from '../CharacterSheet'; +import { BigNumber } from '../layout/BigNumber'; +import { HorizontalDivider } from '../layout/dividers'; +import { LabelUnder } from '../layout/labels'; +import { Panel, type PanelProps } from '../layout/Panel'; + +export function SpellAbilityPanel(props: PanelProps) { + const { character, derived } = useCharacter(); + + if (!character.spellcasting) { + return null; + } + + return ( + +

+ {ABILITY_DETAILS[character.spellcasting.ability].label} +

+ Spellcasting Ability + +
+ Spellcasting Modifier + + {formatMod(derived.abilityModifiers[character.spellcasting.ability])} + + + + + Spellcasting Save DC + + {derived.spellSaveDC} + + + + + Spellcasting Attack Bonus + + {formatMod(derived.spellAttackBonus ?? 0)} + +
+
+ ); +} diff --git a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellList.tsx b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellList.tsx new file mode 100644 index 00000000..a578dbd6 --- /dev/null +++ b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellList.tsx @@ -0,0 +1,196 @@ +import { cn } from '@ageorgedev/toolbelt/cn'; +import { ABILITY_DETAILS, type Spell } from '../../lib/models'; +import { formatMod } from '../../lib/utils'; +import { useCharacter } from '../CharacterSheet'; +import { + CircleCheck, + DiamondCheck, + EmptyCheckList, +} from '../layout/checkables'; +import { HorizontalDivider } from '../layout/dividers'; +import { Panel } from '../layout/Panel'; +import { PanelTitle } from '../layout/PanelTitle'; + +export function SpellList() { + const { + character: { spellcasting }, + } = useCharacter(); + + const totalRows = 30; // problematic, does not work all the time. if we don't find a css solution, might have to prop drill from StandardCharacter sheet + const emptyRows = totalRows - (spellcasting?.spells.length ?? 0); + + return ( + + Spells + + + + + + + + + + + + + + + + {spellcasting?.spells.map((spell) => ( + + ))} + {Array.from({ length: emptyRows }, (_, i) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: not required for this + + ))} + +
PrepLevel  NameCasting TimeRangeDurationConcentration, Ritual & Material consumedNotes
+
+ ); +} + +export function SpellRow({ spell }: { spell: Spell }) { + const { derived } = useCharacter(); + const vs = [spell.components?.verbal && 'V', spell.components?.somatic && 'S'] + .filter(Boolean) + .join(','); + + const freeUsesNode = !spell.freeUses ? null : ( + + free x{spell.freeUses}{' '} + + + ); + + const notesSection = [ + vs && {vs}, + spell.notes && {spell.notes}, + ].filter(Boolean); + + const castingTimeText = (() => { + switch (spell.castingTime.toLowerCase()) { + case 'action': + return 'Action'; + case 'bonus action': + return 'Bonus'; + default: + return spell.duration; + } + })(); + + const altAbilityNode = !spell.alternativeAbility + ? null + : (() => { + const name = ABILITY_DETAILS[spell.alternativeAbility].shortName; + const abilityMod = derived.abilityModifiers[spell.alternativeAbility]; + const toHit = formatMod(abilityMod + derived.proficiencyBonus); + const dc = abilityMod + derived.proficiencyBonus + 8; + return ( + + ({name} spell, {toHit} to Hit, DC {dc}) + + ); + })(); + + return ( +
+ {spell.alwaysPrepared ? ( + AP + ) : ( + + )} + {spell.level} +
+ {spell.name} + {freeUsesNode} + {altAbilityNode} +
+
{castingTimeText}{spell.range}{spell.duration} + {notesSection.map((node, idx) => ( + <> + {node} + {idx === notesSection.length - 1 ? '' : ' | '} + + ))} +
+   + + + + + +
+
+ + C + + + R + + + M + +
+
+ ); +} + +function Td({ + className, + ...props +}: React.ThHTMLAttributes) { + return ( + + ); +} diff --git a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellRow.tsx b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellRow.tsx deleted file mode 100644 index 532c4e46..00000000 --- a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellRow.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { ABILITY_DETAILS } from '../../lib/models/abilities'; -import type { Spell } from '../../lib/models/spellcasting'; -import { CircleCheck } from '../layout/checkables'; - -function ComponentFlags({ spell }: { spell: Spell }) { - const { components } = spell; - if (!components) return null; - const flags: string[] = []; - if (components.verbal) flags.push('V'); - if (components.somatic) flags.push('S'); - if (components.materialConsumed) flags.push('M*'); - else if ( - 'materialConsumed' in components && - components.materialConsumed === false - ) - flags.push('M'); - if (flags.length === 0) return null; - return ( - {flags.join(' ')} - ); -} - -function Badge({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ); -} - -interface SpellRowProps { - spell: Spell; - showPrepared: boolean; -} - -export function SpellRow({ spell, showPrepared }: SpellRowProps) { - return ( - <> - {showPrepared ? ( -
- -
- ) : null} -
- {spell.name} -
-
- -
-
- {spell.castingTime} -
-
- {spell.range} -
-
- {spell.duration} -
-
-
- {spell.concentration && C} - {spell.ritual && R} - {spell.alwaysPrepared && } - {spell.freeUses != null && free×{spell.freeUses}} - {spell.alternativeAbility && ( - {ABILITY_DETAILS[spell.alternativeAbility].shortName} - )} -
-
- - ); -} diff --git a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellSlotsPanel.tsx b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellSlotsPanel.tsx new file mode 100644 index 00000000..5bbd3879 --- /dev/null +++ b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellSlotsPanel.tsx @@ -0,0 +1,82 @@ +import { cn } from '@ageorgedev/toolbelt/cn'; +import { useMemo } from 'react'; +import { useCharacter } from '../CharacterSheet'; +import { EmptyCheckList } from '../layout/checkables'; +import { Panel, type PanelProps } from '../layout/Panel'; +import { PanelTitle } from '../layout/PanelTitle'; + +export function SpellSlotsPanel(props: PanelProps) { + const { + character: { spellcasting }, + } = useCharacter(); + + const allSlots = useMemo(() => { + const spellSlots = spellcasting?.slots + ? (Object.entries(spellcasting.slots) as [string, number][]) + .sort(([a], [b]) => Number(a) - Number(b)) + .map(([level, slots]) => [`Level ${level}`, slots] as const) + : []; + + const pactMagicSlots = spellcasting?.pactMagic + ? [ + [ + `Pact Magic level ${spellcasting.pactMagic.level}`, + spellcasting.pactMagic.slots, + ] as const, + ] + : []; + + return [...pactMagicSlots, ...spellSlots]; + }, [spellcasting]); + + const columnStyle = useMemo(() => { + switch (allSlots.length) { + case 0: + case 1: + return ''; + case 2: + case 4: + return 'columns-2'; + default: + return 'columns-3'; + } + }, [allSlots]); + + if (!spellcasting) return null; + + return ( + + Spell Slots +
+ Cantrips:{' '} + + {spellcasting.numberOfCantrips ?? 0} + + , Prepared Spells:{' '} + + {spellcasting.numberOfPreparedSpells ?? 0} + +
+ +
+ {allSlots.map(([name, count]) => ( +
+ {name} +
+ +
+ ))} + {!allSlots.length && ( +
+ No Slots +
+ )} +
+
+ ); +} diff --git a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingBlock.tsx b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingBlock.tsx index 029c1a6c..4f17e450 100644 --- a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingBlock.tsx +++ b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellcastingBlock.tsx @@ -1,18 +1,6 @@ -import { cn } from '@ageorgedev/toolbelt/cn'; -import { useMemo } from 'react'; -import { ABILITY_DETAILS, type Spell } from '../../lib/models'; -import { formatMod } from '../../lib/utils'; -import { useCharacter } from '../CharacterSheet'; -import { BigNumber } from '../layout/BigNumber'; -import { - CircleCheck, - DiamondCheck, - EmptyCheckList, -} from '../layout/checkables'; -import { HorizontalDivider } from '../layout/dividers'; -import { LabelUnder } from '../layout/labels'; -import { Panel, type PanelProps } from '../layout/Panel'; -import { PanelTitle } from '../layout/PanelTitle'; +import { SpellAbilityPanel } from './SpellAbilityPanel'; +import { SpellList } from './SpellList'; +import { SpellSlotsPanel } from './SpellSlotsPanel'; export function SpellcastingBlock() { return ( @@ -21,263 +9,7 @@ export function SpellcastingBlock() { - + ); } - -function SpellAbilityPanel(props: PanelProps) { - const { character, derived } = useCharacter(); - - if (!character.spellcasting) { - return null; - } - - return ( - -

- {ABILITY_DETAILS[character.spellcasting.ability].label} -

- Spellcasting Ability - -
- Spellcasting Modifier - - {formatMod(derived.abilityModifiers[character.spellcasting.ability])} - - - - - Spellcasting Save DC - - {derived.spellSaveDC} - - - - - Spellcasting Attack Bonus - - {formatMod(derived.spellAttackBonus ?? 0)} - -
-
- ); -} - -function SpellSlotsPanel(props: PanelProps) { - const { - character: { spellcasting }, - } = useCharacter(); - - const allSlots = useMemo(() => { - const spellSlots = spellcasting?.slots - ? (Object.entries(spellcasting.slots) as [string, number][]) - .sort(([a], [b]) => Number(a) - Number(b)) - .map(([level, slots]) => [`Level ${level}`, slots] as const) - : []; - - const pactMagicSlots = spellcasting?.pactMagic - ? [ - [ - `Pact Magic level ${spellcasting.pactMagic.level}`, - spellcasting.pactMagic.slots, - ] as const, - ] - : []; - - return [...pactMagicSlots, ...spellSlots]; - }, [spellcasting]); - - const columnStyle = useMemo(() => { - switch (allSlots.length) { - case 0: - case 1: - return ''; - case 2: - case 4: - return 'columns-2'; - default: - return 'columns-3'; - } - }, [allSlots]); - - if (!spellcasting) return null; - - return ( - - Spell Slots -
- Cantrips:{' '} - - {spellcasting.numberOfCantrips ?? 0} - - , Prepared Spells:{' '} - - {spellcasting.numberOfPreparedSpells ?? 0} - -
- -
- {allSlots.map(([name, count]) => ( -
- {name} -
- -
- ))} - {!allSlots.length && ( -
- No Slots -
- )} -
-
- ); -} - -function SpellListAsTable() { - const { - character: { spellcasting }, - } = useCharacter(); - - const totalRows = 38; // Eyeballed for typical a4 size - const emptyRows = totalRows - (spellcasting?.spells.length ?? 0); - - return ( - - Spells - - - - - - - - - - - - - - - - {spellcasting?.spells.map((spell) => ( - - ))} - {Array.from({ length: emptyRows }, (_, i) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: not required for this - - ))} - -
PrepLevel  NameCasting TimeRangeDurationConcentration, Ritual & Material consumedNotes
-
- ); -} - -function EmptyRow() { - return ( -
-   - - - - - - C - - - R - - - M - - -
- {spell.alwaysPrepared ? ( - AP - ) : ( - - )} - {spell.level} -
- {spell.name} - - {!!spell.freeUses && ( - - free x{spell.freeUses}{' '} - {' '} -   - - )} -
-
{spell.castingTime}{spell.range}{spell.duration} - - C - - - R - - - M - - {spell.notes}
- ); -} - -function Td({ - className, - ...props -}: React.ThHTMLAttributes) { - return ( - - ); -} diff --git a/packages/dnd-character-sheet/src/components/SpellcastingBlock/index.ts b/packages/dnd-character-sheet/src/components/SpellcastingBlock/index.ts deleted file mode 100644 index 50f18988..00000000 --- a/packages/dnd-character-sheet/src/components/SpellcastingBlock/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { SpellcastingBlock } from './SpellcastingBlock'; diff --git a/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx b/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx index af7b37e7..ba2b74a7 100644 --- a/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx +++ b/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx @@ -15,7 +15,7 @@ import { Page } from '../layout/Page'; import { ProficiencyBlock } from '../ProficiencyBlock/ProficiencyBlock'; import { Resources } from '../Resources/Resources'; import { SheetHeader } from '../SheetHeader/SheetHeader'; -import { SpellcastingBlock } from '../SpellcastingBlock'; +import { SpellcastingBlock } from '../SpellcastingBlock/SpellcastingBlock'; interface Props { data: Character; @@ -62,7 +62,7 @@ export function StandardCharacterSheet({ data }: Props) {
-
+
{data.spellcasting && }
diff --git a/packages/dnd-character-sheet/src/components/layout/Page.module.css b/packages/dnd-character-sheet/src/components/layout/Page.module.css index 06ffc4e7..673e264e 100644 --- a/packages/dnd-character-sheet/src/components/layout/Page.module.css +++ b/packages/dnd-character-sheet/src/components/layout/Page.module.css @@ -9,6 +9,7 @@ @media print { .PageInner { @apply py-6; - min-height: 100vh; + height: 100vh; + overflow: hidden; } } diff --git a/packages/dnd-character-sheet/src/components/layout/Page.tsx b/packages/dnd-character-sheet/src/components/layout/Page.tsx index ad295065..b9a310d5 100644 --- a/packages/dnd-character-sheet/src/components/layout/Page.tsx +++ b/packages/dnd-character-sheet/src/components/layout/Page.tsx @@ -11,7 +11,7 @@ export function Page({
Date: Sun, 21 Jun 2026 16:59:46 +1000 Subject: [PATCH 20/29] feat: introduce a basic rich text display for dnd sheets --- .../_public/dnd/characters/omarin-kenate.tsx | 2 +- packages/dnd-character-sheet/package.json | 1 + .../RichTextDisplay.module.css | 5 ++++ .../RichTextDisplay/RichTextDisplay.tsx | 29 +++++++++++++++++++ .../feature-blocks/FeatureEntry.tsx | 5 +++- .../src/components/layout/Page.tsx | 2 +- yarn.lock | 20 +++++++++++++ 7 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 packages/dnd-character-sheet/src/components/RichTextDisplay/RichTextDisplay.module.css create mode 100644 packages/dnd-character-sheet/src/components/RichTextDisplay/RichTextDisplay.tsx diff --git a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx index 34072c8d..fc53d879 100644 --- a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx +++ b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx @@ -119,7 +119,7 @@ const OmarinData: Character = { { name: 'Martial Arts', description: - 'On using only monk weapons, wearing no armor, and holding no shield, you gain the following benefits. 1) Unarmed strikes as Bonus Actions 2) Can use Dex. modifier for attacks and damage rolls; 1d6 instead of normal damage -- for unarmed strikes and Monk weapons', + 'On using only monk weapons, wearing no armor, and holding no shield, you gain the following benefits.
  1. Unarmed strikes as Bonus Actions
  2. Can use Dex. modifier for attacks and damage rolls; 1d6 instead of normal damage -- for unarmed strikes and Monk weapons
', }, { name: 'Unarmored Defence + Movement', diff --git a/packages/dnd-character-sheet/package.json b/packages/dnd-character-sheet/package.json index 8c9ac82e..232de05c 100644 --- a/packages/dnd-character-sheet/package.json +++ b/packages/dnd-character-sheet/package.json @@ -32,6 +32,7 @@ }, "dependencies": { "@ageorgedev/toolbelt": "workspace:*", + "dompurify": "3.4.11", "mustache": "4.2.0", "ramda": "0.32.0" } diff --git a/packages/dnd-character-sheet/src/components/RichTextDisplay/RichTextDisplay.module.css b/packages/dnd-character-sheet/src/components/RichTextDisplay/RichTextDisplay.module.css new file mode 100644 index 00000000..0f89077b --- /dev/null +++ b/packages/dnd-character-sheet/src/components/RichTextDisplay/RichTextDisplay.module.css @@ -0,0 +1,5 @@ +@reference "@ageorgedev/foundation-styles/theme.css"; + +.Container ol { + @apply list-decimal list-inside; +} diff --git a/packages/dnd-character-sheet/src/components/RichTextDisplay/RichTextDisplay.tsx b/packages/dnd-character-sheet/src/components/RichTextDisplay/RichTextDisplay.tsx new file mode 100644 index 00000000..a290ce42 --- /dev/null +++ b/packages/dnd-character-sheet/src/components/RichTextDisplay/RichTextDisplay.tsx @@ -0,0 +1,29 @@ +/** biome-ignore-all lint/security/noDangerouslySetInnerHtml: handled through dompurify */ +import { cn } from '@ageorgedev/toolbelt/cn'; +import dompurify from 'dompurify'; +import { useMemo } from 'react'; +import styles from './RichTextDisplay.module.css'; + +type RichTextDisplayProps = Omit< + React.HTMLAttributes, + 'children' +> & { + content: string; + element?: 'span' | 'div'; +}; + +export function RichTextDisplay({ + className, + element: Element = 'div', + content, + ...props +}: RichTextDisplayProps) { + const cleanHtml = useMemo(() => dompurify.sanitize(content), [content]); + return ( + + ); +} diff --git a/packages/dnd-character-sheet/src/components/feature-blocks/FeatureEntry.tsx b/packages/dnd-character-sheet/src/components/feature-blocks/FeatureEntry.tsx index 55ddbb97..f6705bc7 100644 --- a/packages/dnd-character-sheet/src/components/feature-blocks/FeatureEntry.tsx +++ b/packages/dnd-character-sheet/src/components/feature-blocks/FeatureEntry.tsx @@ -1,4 +1,5 @@ import type { Feature } from '../../lib/models/feature'; +import { RichTextDisplay } from '../RichTextDisplay/RichTextDisplay'; export function FeatureEntry({ data: feat }: { data: Feature }) { return ( @@ -7,7 +8,9 @@ export function FeatureEntry({ data: feat }: { data: Feature }) { {feat.cost && ( (Cost: {feat.cost}) )} -  {feat.description} +   + {/*  {feat.description} */} +
); } diff --git a/packages/dnd-character-sheet/src/components/layout/Page.tsx b/packages/dnd-character-sheet/src/components/layout/Page.tsx index b9a310d5..ad295065 100644 --- a/packages/dnd-character-sheet/src/components/layout/Page.tsx +++ b/packages/dnd-character-sheet/src/components/layout/Page.tsx @@ -11,7 +11,7 @@ export function Page({
Date: Sun, 21 Jun 2026 17:13:23 +1000 Subject: [PATCH 21/29] feat: Simplify appearance and personality --- .../_public/dnd/characters/omarin-kenate.tsx | 11 ++- .../src/characters/example-wizard.data.ts | 10 --- .../BackstoryBlock/BackstoryBlock.tsx | 70 ------------------- .../components/GenericPanel/GenericPanel.tsx | 23 ++++++ .../SpellcastingBlock/SpellList.tsx | 2 +- .../StandardCharacterSheet.tsx | 13 +++- .../src/lib/models/character.ts | 10 +-- 7 files changed, 41 insertions(+), 98 deletions(-) delete mode 100644 packages/dnd-character-sheet/src/components/BackstoryBlock/BackstoryBlock.tsx create mode 100644 packages/dnd-character-sheet/src/components/GenericPanel/GenericPanel.tsx diff --git a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx index fc53d879..8c950117 100644 --- a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx +++ b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx @@ -109,12 +109,6 @@ const OmarinData: Character = { ], toolProficiencies: ["Tinker's Tools", "Carpenter's Tools"], languages: [], - personality: { - traits: '', - ideals: '', - bonds: '', - flaws: '', - }, features: [ { name: 'Martial Arts', @@ -262,4 +256,9 @@ const OmarinData: Character = { numberOfCantrips: 0, numberOfPreparedSpells: 0, }, + + backstory: ` +

This is the story of Omarin

+

Early years Had a lot of fun

+ `, }; diff --git a/packages/dnd-character-sheet/src/characters/example-wizard.data.ts b/packages/dnd-character-sheet/src/characters/example-wizard.data.ts index f15f8763..ad0b8b8d 100644 --- a/packages/dnd-character-sheet/src/characters/example-wizard.data.ts +++ b/packages/dnd-character-sheet/src/characters/example-wizard.data.ts @@ -136,16 +136,6 @@ export const exampleWizard: Character = { toolProficiencies: ["Calligrapher's Supplies"], languages: ['Common', 'Elvish', 'Draconic', 'Sylvan'], - personality: { - traits: - 'I use polysyllabic words that convey the impression of great erudition.', - ideals: - 'Knowledge. The path to power and self-improvement is through knowledge.', - bonds: - "My life's work is a series of tomes related to the history of magic.", - flaws: 'I overlook obvious solutions in favour of complicated ones.', - }, - backstory: "Seraphina studied at the Arcane Academy for fifteen years before striking out on her own to research the ancient Ashveil bloodline's connection to the Weave.", diff --git a/packages/dnd-character-sheet/src/components/BackstoryBlock/BackstoryBlock.tsx b/packages/dnd-character-sheet/src/components/BackstoryBlock/BackstoryBlock.tsx deleted file mode 100644 index 7db2f8ff..00000000 --- a/packages/dnd-character-sheet/src/components/BackstoryBlock/BackstoryBlock.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { useCharacter } from '../CharacterSheet'; -import { Panel } from '../layout/Panel'; -import { PanelTitle } from '../layout/PanelTitle'; - -const PERSONALITY_FIELDS = [ - { key: 'traits', label: 'Personality Traits' }, - { key: 'ideals', label: 'Ideals' }, - { key: 'bonds', label: 'Bonds' }, - { key: 'flaws', label: 'Flaws' }, -] as const; - -export function BackstoryBlock() { - const { character } = useCharacter(); - - return ( - - - Backstory & Personality - -
- {PERSONALITY_FIELDS.map(({ key, label }) => ( -
-

- {label} -

-

- {character.personality[key]} -

-
- ))} - - {character.backstory && ( -
-

- Backstory -

-

- {character.backstory} -

-
- )} - - {character.notes && ( -
-

- Notes -

-

- {character.notes} -

-
- )} - - {character.languages.length > 0 && ( -
-

- Languages -

-

- {character.languages.join(', ')} -

-
- )} -
-
- ); -} diff --git a/packages/dnd-character-sheet/src/components/GenericPanel/GenericPanel.tsx b/packages/dnd-character-sheet/src/components/GenericPanel/GenericPanel.tsx new file mode 100644 index 00000000..8eedb827 --- /dev/null +++ b/packages/dnd-character-sheet/src/components/GenericPanel/GenericPanel.tsx @@ -0,0 +1,23 @@ +import { HorizontalDivider } from '../layout/dividers'; +import { Panel, type PanelProps } from '../layout/Panel'; +import { PanelTitle } from '../layout/PanelTitle'; +import { RichTextDisplay } from '../RichTextDisplay/RichTextDisplay'; + +export type GenericPanelProps = { + heading: string; + htmlContent: string; +} & PanelProps; + +export function GenericPanel({ + heading, + htmlContent, + ...props +}: GenericPanelProps) { + return ( + + {heading} + + + + ); +} diff --git a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellList.tsx b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellList.tsx index a578dbd6..ff7d2b73 100644 --- a/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellList.tsx +++ b/packages/dnd-character-sheet/src/components/SpellcastingBlock/SpellList.tsx @@ -22,7 +22,7 @@ export function SpellList() { return ( Spells - + diff --git a/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx b/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx index ba2b74a7..44d1064a 100644 --- a/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx +++ b/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx @@ -2,7 +2,6 @@ import { Ability } from '../../lib/models/abilities'; import type { Character } from '../../lib/models/character'; import { AbilityBox } from '../AbilityBox/AbilityBox'; import { AttackList } from '../AttackList/AttackList'; -import { BackstoryBlock } from '../BackstoryBlock/BackstoryBlock'; import { CharacterSheet } from '../CharacterSheet'; import { CombatRow } from '../CombatRow/CombatRow'; import { EquipmentBlock } from '../EquipmentBlock/EquipmentBlock'; @@ -10,6 +9,7 @@ import { EquipmentTraining } from '../EquipmentTraining/EquipmentTraining'; import { ClassFeatures } from '../feature-blocks/ClassFeatures'; import { Feats } from '../feature-blocks/Feats'; import { SpeciesTraits } from '../feature-blocks/SpeciesTraits'; +import { GenericPanel } from '../GenericPanel/GenericPanel'; import { HeroicInspiration } from '../HeroicInspiration/HeroicInspiration'; import { Page } from '../layout/Page'; import { ProficiencyBlock } from '../ProficiencyBlock/ProficiencyBlock'; @@ -66,7 +66,16 @@ export function StandardCharacterSheet({ data }: Props) { {data.spellcasting && }
- + +
diff --git a/packages/dnd-character-sheet/src/lib/models/character.ts b/packages/dnd-character-sheet/src/lib/models/character.ts index 00369f9b..9475097f 100644 --- a/packages/dnd-character-sheet/src/lib/models/character.ts +++ b/packages/dnd-character-sheet/src/lib/models/character.ts @@ -64,17 +64,9 @@ export interface Character { toolProficiencies: string[]; languages: string[]; - // Personality - personality: { - traits: string; - ideals: string; - bonds: string; - flaws: string; - }; - // Freeform backstory?: string; - notes?: string; + appearance?: string; // Spellcasting spellcasting?: Spellcasting; From bb604f47828b237909f10fb1b12c06648163f72b Mon Sep 17 00:00:00 2001 From: Anish George Date: Sun, 21 Jun 2026 21:46:47 +1000 Subject: [PATCH 22/29] feat: More second page stuff --- .../_public/dnd/characters/omarin-kenate.tsx | 9 +--- .../src/components/CoinBlock/CoinBlock.tsx | 30 +++++++++++ .../EquipmentBlock/EquipmentBlock.tsx | 51 ------------------- .../EquipmentTraining/EquipmentTraining.tsx | 5 ++ .../HandwrittenNotes/HandwrittenNotes.tsx | 25 +++++++++ .../src/components/Inventory/Inventory.tsx | 22 ++++++++ .../StandardCharacterSheet.tsx | 6 ++- .../src/lib/models/character.ts | 9 +--- 8 files changed, 88 insertions(+), 69 deletions(-) create mode 100644 packages/dnd-character-sheet/src/components/CoinBlock/CoinBlock.tsx delete mode 100644 packages/dnd-character-sheet/src/components/EquipmentBlock/EquipmentBlock.tsx create mode 100644 packages/dnd-character-sheet/src/components/HandwrittenNotes/HandwrittenNotes.tsx create mode 100644 packages/dnd-character-sheet/src/components/Inventory/Inventory.tsx diff --git a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx index 8c950117..72f982ea 100644 --- a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx +++ b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx @@ -93,14 +93,7 @@ const OmarinData: Character = { notes: 'Ammo (80/20, Bolt), Loading, 2-handed', }, ], - equipment: [], - currency: { - cp: 0, - sp: 0, - ep: 0, - gp: 0, - pp: 0, - }, + equipment: ['Bedroll, Backpack, Lantern', 'Flasks of oil (x4)'], armorProficiencies: ['Light armor', 'Medium armor', 'Shields'], weaponProficiencies: [ 'Simple', diff --git a/packages/dnd-character-sheet/src/components/CoinBlock/CoinBlock.tsx b/packages/dnd-character-sheet/src/components/CoinBlock/CoinBlock.tsx new file mode 100644 index 00000000..219bf0ff --- /dev/null +++ b/packages/dnd-character-sheet/src/components/CoinBlock/CoinBlock.tsx @@ -0,0 +1,30 @@ +import { HandWrittenNotes } from '../HandwrittenNotes/HandwrittenNotes'; +import { HorizontalDivider } from '../layout/dividers'; +import { Panel, type PanelProps } from '../layout/Panel'; +import { PanelTitle } from '../layout/PanelTitle'; + +export function CoinBlock(props: PanelProps) { + return ( + + Coins & Treasure + +
+ + + + + +
+ +
+ ); +} + +function CoinSlot({ name }: { name: string }) { + return ( +
+ {name} +
+
+ ); +} diff --git a/packages/dnd-character-sheet/src/components/EquipmentBlock/EquipmentBlock.tsx b/packages/dnd-character-sheet/src/components/EquipmentBlock/EquipmentBlock.tsx deleted file mode 100644 index f7c28dd2..00000000 --- a/packages/dnd-character-sheet/src/components/EquipmentBlock/EquipmentBlock.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { useCharacter } from '../CharacterSheet'; -import { Panel } from '../layout/Panel'; -import { PanelTitle } from '../layout/PanelTitle'; - -const CURRENCY = [ - { key: 'pp', label: 'PP' }, - { key: 'gp', label: 'GP' }, - { key: 'ep', label: 'EP' }, - { key: 'sp', label: 'SP' }, - { key: 'cp', label: 'CP' }, -] as const; - -export function EquipmentBlock() { - const { character } = useCharacter(); - - return ( - - Equipment - -
-

- Currency -

-
- {CURRENCY.map(({ key, label }) => ( -
- - {character.currency[key]} - - - {label} - -
- ))} -
-
- -
    - {character.equipment.map((item) => ( -
  • - - {item} -
  • - ))} -
-
- ); -} diff --git a/packages/dnd-character-sheet/src/components/EquipmentTraining/EquipmentTraining.tsx b/packages/dnd-character-sheet/src/components/EquipmentTraining/EquipmentTraining.tsx index 0568b505..001b294e 100644 --- a/packages/dnd-character-sheet/src/components/EquipmentTraining/EquipmentTraining.tsx +++ b/packages/dnd-character-sheet/src/components/EquipmentTraining/EquipmentTraining.tsx @@ -41,6 +41,11 @@ export function EquipmentTraining() { Tools

{character.toolProficiencies.join(', ')}

+ +
+ Languages +

{character.languages.join(', ') || 'Common'}

+
); } diff --git a/packages/dnd-character-sheet/src/components/HandwrittenNotes/HandwrittenNotes.tsx b/packages/dnd-character-sheet/src/components/HandwrittenNotes/HandwrittenNotes.tsx new file mode 100644 index 00000000..c8fbaa0b --- /dev/null +++ b/packages/dnd-character-sheet/src/components/HandwrittenNotes/HandwrittenNotes.tsx @@ -0,0 +1,25 @@ +import { cn } from '@ageorgedev/toolbelt/cn'; + +type HandWrittenNotesProps = { + lineCount: number; +} & Omit, 'children'>; + +export function HandWrittenNotes({ + lineCount, + className, + ...props +}: HandWrittenNotesProps) { + return ( +
+ {Array.from({ length: lineCount }, (_, i) => ( +
+   +
+ ))} +
+ ); +} diff --git a/packages/dnd-character-sheet/src/components/Inventory/Inventory.tsx b/packages/dnd-character-sheet/src/components/Inventory/Inventory.tsx new file mode 100644 index 00000000..9b8bfcd0 --- /dev/null +++ b/packages/dnd-character-sheet/src/components/Inventory/Inventory.tsx @@ -0,0 +1,22 @@ +import { useCharacter } from '../CharacterSheet'; +import { HandWrittenNotes } from '../HandwrittenNotes/HandwrittenNotes'; +import { HorizontalDivider } from '../layout/dividers'; +import { Panel, type PanelProps } from '../layout/Panel'; +import { PanelTitle } from '../layout/PanelTitle'; + +export function Inventory(props: PanelProps) { + const { character } = useCharacter(); + return ( + + Equipment + + {character.equipment.map((e) => ( +
{e}
+ ))} + +
+

Magic Item Attunement

+
+
+ ); +} diff --git a/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx b/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx index 44d1064a..088a148a 100644 --- a/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx +++ b/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx @@ -3,14 +3,15 @@ import type { Character } from '../../lib/models/character'; import { AbilityBox } from '../AbilityBox/AbilityBox'; import { AttackList } from '../AttackList/AttackList'; import { CharacterSheet } from '../CharacterSheet'; +import { CoinBlock } from '../CoinBlock/CoinBlock'; import { CombatRow } from '../CombatRow/CombatRow'; -import { EquipmentBlock } from '../EquipmentBlock/EquipmentBlock'; import { EquipmentTraining } from '../EquipmentTraining/EquipmentTraining'; import { ClassFeatures } from '../feature-blocks/ClassFeatures'; import { Feats } from '../feature-blocks/Feats'; import { SpeciesTraits } from '../feature-blocks/SpeciesTraits'; import { GenericPanel } from '../GenericPanel/GenericPanel'; import { HeroicInspiration } from '../HeroicInspiration/HeroicInspiration'; +import { Inventory } from '../Inventory/Inventory'; import { Page } from '../layout/Page'; import { ProficiencyBlock } from '../ProficiencyBlock/ProficiencyBlock'; import { Resources } from '../Resources/Resources'; @@ -76,7 +77,8 @@ export function StandardCharacterSheet({ data }: Props) { htmlContent={data.backstory ?? ''} outerClasses="min-h-[15em]" /> - + + diff --git a/packages/dnd-character-sheet/src/lib/models/character.ts b/packages/dnd-character-sheet/src/lib/models/character.ts index 9475097f..934e5642 100644 --- a/packages/dnd-character-sheet/src/lib/models/character.ts +++ b/packages/dnd-character-sheet/src/lib/models/character.ts @@ -18,7 +18,7 @@ export interface Character { }[]; alignment?: string; - experiencePoints: number; + experiencePoints?: number; // Ability scores abilities: Record; @@ -42,13 +42,6 @@ export interface Character { // Equipment equipment: string[]; - currency: { - cp: number; - sp: number; - ep: number; - gp: number; - pp: number; - }; // Features features: Feature[]; From 5b11eed72818554ec92f524072f9db0a02675b66 Mon Sep 17 00:00:00 2001 From: Anish George Date: Sun, 21 Jun 2026 23:35:14 +1000 Subject: [PATCH 23/29] feat: Add magic item attunement --- .../src/components/Inventory/Inventory.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/dnd-character-sheet/src/components/Inventory/Inventory.tsx b/packages/dnd-character-sheet/src/components/Inventory/Inventory.tsx index 9b8bfcd0..a34666f6 100644 --- a/packages/dnd-character-sheet/src/components/Inventory/Inventory.tsx +++ b/packages/dnd-character-sheet/src/components/Inventory/Inventory.tsx @@ -1,5 +1,6 @@ import { useCharacter } from '../CharacterSheet'; import { HandWrittenNotes } from '../HandwrittenNotes/HandwrittenNotes'; +import { DiamondCheck } from '../layout/checkables'; import { HorizontalDivider } from '../layout/dividers'; import { Panel, type PanelProps } from '../layout/Panel'; import { PanelTitle } from '../layout/PanelTitle'; @@ -15,7 +16,21 @@ export function Inventory(props: PanelProps) { ))}
-

Magic Item Attunement

+

+ Magic Item Attunement +

+ +
+ {Array.from({ length: 3 }, (_, i) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: Not important +
+ +
+   +
+
+ ))} +
); From 1dfe44251f5341ec7218474904be071e3b0a7fca Mon Sep 17 00:00:00 2001 From: Anish George Date: Sun, 21 Jun 2026 23:38:44 +1000 Subject: [PATCH 24/29] feat: Minor layout change --- .../StandardCharacterSheet/StandardCharacterSheet.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx b/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx index 088a148a..8f1e0a2e 100644 --- a/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx +++ b/packages/dnd-character-sheet/src/components/StandardCharacterSheet/StandardCharacterSheet.tsx @@ -62,12 +62,13 @@ export function StandardCharacterSheet({ data }: Props) { -
-
+
+
{data.spellcasting && }
-
+
- +
From 37d26d79cfb643f650d2c3bd582e0dc931f35ea8 Mon Sep 17 00:00:00 2001 From: Anish George Date: Mon, 22 Jun 2026 10:37:18 +1000 Subject: [PATCH 25/29] chore: working through character list --- .../routes/_public/dnd/characters/example.tsx | 3 ++ .../routes/_public/dnd/characters/index.tsx | 45 ++++++++++++------- .../_public/dnd/characters/omarin-kenate.tsx | 19 ++++---- 3 files changed, 42 insertions(+), 25 deletions(-) diff --git a/apps/game-tools/src/routes/_public/dnd/characters/example.tsx b/apps/game-tools/src/routes/_public/dnd/characters/example.tsx index c871c9e6..713dc680 100644 --- a/apps/game-tools/src/routes/_public/dnd/characters/example.tsx +++ b/apps/game-tools/src/routes/_public/dnd/characters/example.tsx @@ -3,6 +3,9 @@ import { createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute('/_public/dnd/characters/example')({ component: RouteComponent, + staticData: { + name: 'Example', + }, }); function RouteComponent() { diff --git a/apps/game-tools/src/routes/_public/dnd/characters/index.tsx b/apps/game-tools/src/routes/_public/dnd/characters/index.tsx index ce509617..5342f64d 100644 --- a/apps/game-tools/src/routes/_public/dnd/characters/index.tsx +++ b/apps/game-tools/src/routes/_public/dnd/characters/index.tsx @@ -1,29 +1,40 @@ -import { createFileRoute, Link } from '@tanstack/react-router'; +import { + createFileRoute, + type Route as RouteType, + useRouter, +} from '@tanstack/react-router'; +import { useMemo } from 'react'; export const Route = createFileRoute('/_public/dnd/characters/')({ component: RouteComponent, }); function RouteComponent() { + const router = useRouter(); + const currentRouteMatch = Route.useMatch(); + + const allCharacters = useMemo(() => { + const currentId = currentRouteMatch.routeId; + return Object.values(router.routesById).filter( + (o) => o.id.startsWith(currentId) && o.id !== currentId + ) as RouteType[]; + }, [router.routesById, currentRouteMatch.routeId]); + return (

Characters

- -

Seraphina Ashveil

-

- Wizard / Warlock · Level 7 -

- - -

Omarin Kenate

-

Fighter 3 / Monk 2

- + + {allCharacters.map(({ Link, ...r }) => ( + +

+ {r.options.staticData?.name ?? 'Character NAME'} +

+

TODO

+ + ))}
); } diff --git a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx index 72f982ea..02b7683f 100644 --- a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx +++ b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx @@ -7,14 +7,6 @@ import { } from '@ageorgedev/dnd-character-sheet'; import { createFileRoute } from '@tanstack/react-router'; -export const Route = createFileRoute('/_public/dnd/characters/omarin-kenate')({ - component: RouteComponent, -}); - -function RouteComponent() { - return ; -} - const OmarinData: Character = { name: 'Omarin Kenate', species: 'Drow Elf', @@ -255,3 +247,14 @@ const OmarinData: Character = {

Early years Had a lot of fun

`, }; + +export const Route = createFileRoute('/_public/dnd/characters/omarin-kenate')({ + component: RouteComponent, + staticData: { + name: OmarinData.name, + }, +}); + +function RouteComponent() { + return ; +} From c321131c3bf3fc860b5db358666224426d76b16f Mon Sep 17 00:00:00 2001 From: Anish George Date: Mon, 22 Jun 2026 11:03:23 +1000 Subject: [PATCH 26/29] chore: some more fixes --- .../routes/_public/dnd/characters/example.tsx | 6 ++- .../routes/_public/dnd/characters/index.tsx | 43 ++++++++++++------- .../_public/dnd/characters/omarin-kenate.tsx | 6 ++- apps/game-tools/src/type-enhancements.d.ts | 12 ++++++ apps/game-tools/tsconfig.json | 1 + 5 files changed, 51 insertions(+), 17 deletions(-) create mode 100644 apps/game-tools/src/type-enhancements.d.ts diff --git a/apps/game-tools/src/routes/_public/dnd/characters/example.tsx b/apps/game-tools/src/routes/_public/dnd/characters/example.tsx index 713dc680..21927003 100644 --- a/apps/game-tools/src/routes/_public/dnd/characters/example.tsx +++ b/apps/game-tools/src/routes/_public/dnd/characters/example.tsx @@ -4,7 +4,11 @@ import { createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute('/_public/dnd/characters/example')({ component: RouteComponent, staticData: { - name: 'Example', + character: { + name: 'Seraphina Ashveil', + description: 'Just an example wizard', + level: 7, + }, }, }); diff --git a/apps/game-tools/src/routes/_public/dnd/characters/index.tsx b/apps/game-tools/src/routes/_public/dnd/characters/index.tsx index 5342f64d..0baa5364 100644 --- a/apps/game-tools/src/routes/_public/dnd/characters/index.tsx +++ b/apps/game-tools/src/routes/_public/dnd/characters/index.tsx @@ -15,26 +15,39 @@ function RouteComponent() { const allCharacters = useMemo(() => { const currentId = currentRouteMatch.routeId; - return Object.values(router.routesById).filter( - (o) => o.id.startsWith(currentId) && o.id !== currentId - ) as RouteType[]; + return ( + Object.values(router.routesById).filter( + (o) => o.id.startsWith(currentId) && o.id !== currentId + ) as RouteType[] + ).filter((r) => r.options.staticData?.character); }, [router.routesById, currentRouteMatch.routeId]); return (
-

Characters

+

Characters

- {allCharacters.map(({ Link, ...r }) => ( - -

- {r.options.staticData?.name ?? 'Character NAME'} -

-

TODO

- - ))} +
+ {allCharacters.map(({ Link, ...r }) => { + const characterInfo = r.options.staticData?.character; + if (!characterInfo) { + return null; + } + + return ( + +

+ {characterInfo.name} +

+

+ Level {characterInfo.level} - {characterInfo.description} +

+ + ); + })} +
); } diff --git a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx index 02b7683f..4628d96b 100644 --- a/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx +++ b/apps/game-tools/src/routes/_public/dnd/characters/omarin-kenate.tsx @@ -251,7 +251,11 @@ const OmarinData: Character = { export const Route = createFileRoute('/_public/dnd/characters/omarin-kenate')({ component: RouteComponent, staticData: { - name: OmarinData.name, + character: { + name: OmarinData.name, + level: OmarinData.classes.reduce((acc, { level }) => acc + level, 0), + description: 'Drow battle master with inner peace', + }, }, }); diff --git a/apps/game-tools/src/type-enhancements.d.ts b/apps/game-tools/src/type-enhancements.d.ts new file mode 100644 index 00000000..323d656d --- /dev/null +++ b/apps/game-tools/src/type-enhancements.d.ts @@ -0,0 +1,12 @@ +declare module '@tanstack/react-router' { + interface StaticDataRouteOption { + character?: { + name: string; + description: string; + level: number; + }; + } +} + +// important, makes this file a module for ts, not an ambient script which will replace the above module entirely +export {}; diff --git a/apps/game-tools/tsconfig.json b/apps/game-tools/tsconfig.json index 9edbced5..2274edf9 100644 --- a/apps/game-tools/tsconfig.json +++ b/apps/game-tools/tsconfig.json @@ -3,6 +3,7 @@ "include": ["src"], "exclude": ["node_modules", "dist"], "compilerOptions": { + "rootDir": "./src", "outDir": "dist" } } From 29c2fb2d9eba5fbc67e9fd5175a1dd3f884f25c2 Mon Sep 17 00:00:00 2001 From: Anish George Date: Mon, 22 Jun 2026 11:30:06 +1000 Subject: [PATCH 27/29] chore: minor fixups --- apps/ageorgedev/tsconfig.json | 1 + apps/design-docs/tsconfig.json | 1 + .../src/characters/example-wizard.data.ts | 7 ------- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/apps/ageorgedev/tsconfig.json b/apps/ageorgedev/tsconfig.json index 7c4ba92d..d9e82867 100644 --- a/apps/ageorgedev/tsconfig.json +++ b/apps/ageorgedev/tsconfig.json @@ -4,6 +4,7 @@ "exclude": ["node_modules", "dist"], "compilerOptions": { "outDir": "dist", + "rootDir": "./src", "types": ["@testing-library/jest-dom"] } } diff --git a/apps/design-docs/tsconfig.json b/apps/design-docs/tsconfig.json index 060bc0aa..84c561f2 100644 --- a/apps/design-docs/tsconfig.json +++ b/apps/design-docs/tsconfig.json @@ -3,6 +3,7 @@ "include": ["stories"], "exclude": ["node_modules", "dist", "storybook-static"], "compilerOptions": { + "rootDir": "./stories", "outDir": "dist" } } diff --git a/packages/dnd-character-sheet/src/characters/example-wizard.data.ts b/packages/dnd-character-sheet/src/characters/example-wizard.data.ts index ad0b8b8d..5985b845 100644 --- a/packages/dnd-character-sheet/src/characters/example-wizard.data.ts +++ b/packages/dnd-character-sheet/src/characters/example-wizard.data.ts @@ -79,13 +79,6 @@ export const exampleWizard: Character = { 'Dagger', 'Robes', ], - currency: { - cp: 0, - sp: 0, - ep: 0, - gp: 120, - pp: 0, - }, features: [ { From 33e1994f393d3a04f791a609308fa122b8016ac2 Mon Sep 17 00:00:00 2001 From: Anish George Date: Mon, 22 Jun 2026 14:14:24 +1000 Subject: [PATCH 28/29] feat: Introduce game tools deployment --- .github/workflows/info.yml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/info.yml b/.github/workflows/info.yml index cf63ea7f..25a4c1e9 100644 --- a/.github/workflows/info.yml +++ b/.github/workflows/info.yml @@ -5,15 +5,6 @@ on: affected_projects: description: 'JSON array of affected projects' value: ${{ jobs.Info.outputs.affected_projects }} - should_run_e2e: - description: 'Whether to run the e2e because of e2e change' - value: ${{ contains(fromJSON(jobs.Info.outputs.affected_projects), '@ageorgedev/ageorgedev-e2e') }} - should_deploy_site: - description: 'Whether to deploy the site' - value: ${{ contains(fromJSON(jobs.Info.outputs.affected_projects), '@ageorgedev/ageorgedev') }} - should_deploy_design_system: - description: 'Whether to deploy the design-system storybook' - value: ${{ contains(fromJSON(jobs.Info.outputs.affected_projects), '@ageorgedev/design-docs')}} production_deploy_matrix: description: 'JSON array of projects to deploy to production' value: ${{ jobs.Info.outputs.production_deploy_matrix }} @@ -42,6 +33,7 @@ jobs: AFFECTED: ${{ steps.affected_projects.outputs.affected_projects }} SITE_PROJECT: ${{ vars.VERCEL_PROJECT_AGEORGEDEV }} DESIGN_PROJECT: ${{ vars.VERCEL_PROJECT_DESIGN_SYSTEM }} + GAME_TOOLS_RPOJECT: ${{ vars.VERCEL_PROJECT_GAME_TOOLS }} run: | matrix=$(echo "$AFFECTED" | jq -c ' . as $p | @@ -51,6 +43,9 @@ jobs: else . end | if ($p | contains(["@ageorgedev/design-docs"])) then . + [{"vercel_project_name": env.DESIGN_PROJECT}] + else . end | + if ($p | contains(["@ageorgedev/game-tools"])) then + . + [{"vercel_project_name": env.GAME_TOOLS_PROJECT}] else . end ') echo "production_deploy_matrix=$matrix" >> "$GITHUB_OUTPUT" @@ -60,6 +55,7 @@ jobs: AFFECTED: ${{ steps.affected_projects.outputs.affected_projects }} SITE_PROJECT: ${{ vars.VERCEL_PROJECT_AGEORGEDEV }} DESIGN_PROJECT: ${{ vars.VERCEL_PROJECT_DESIGN_SYSTEM }} + GAME_TOOLS_RPOJECT: ${{ vars.VERCEL_PROJECT_GAME_TOOLS }} run: | matrix=$(echo "$AFFECTED" | jq -c ' . as $p | @@ -69,6 +65,9 @@ jobs: else . end | if ($p | contains(["@ageorgedev/design-docs"])) then . + [{"vercel_project_name": env.DESIGN_PROJECT, "label": "Design System"}] + else . end | + if ($p | contains(["@ageorgedev/game-tools"])) then + . + [{"vercel_project_name": env.GAME_TOOLS_PROJECT, "label": "Game Tools"}] else . end ') echo "pr_deploy_matrix=$matrix" >> "$GITHUB_OUTPUT" From 4b6258859c9647d5aa0b6847c4a33f4e704bda5d Mon Sep 17 00:00:00 2001 From: Anish George Date: Mon, 22 Jun 2026 14:23:13 +1000 Subject: [PATCH 29/29] chore: fix silly errors --- .github/workflows/info.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/info.yml b/.github/workflows/info.yml index 25a4c1e9..16929bdf 100644 --- a/.github/workflows/info.yml +++ b/.github/workflows/info.yml @@ -33,7 +33,7 @@ jobs: AFFECTED: ${{ steps.affected_projects.outputs.affected_projects }} SITE_PROJECT: ${{ vars.VERCEL_PROJECT_AGEORGEDEV }} DESIGN_PROJECT: ${{ vars.VERCEL_PROJECT_DESIGN_SYSTEM }} - GAME_TOOLS_RPOJECT: ${{ vars.VERCEL_PROJECT_GAME_TOOLS }} + GAME_TOOLS_PROJECT: ${{ vars.VERCEL_PROJECT_GAME_TOOLS }} run: | matrix=$(echo "$AFFECTED" | jq -c ' . as $p | @@ -55,7 +55,7 @@ jobs: AFFECTED: ${{ steps.affected_projects.outputs.affected_projects }} SITE_PROJECT: ${{ vars.VERCEL_PROJECT_AGEORGEDEV }} DESIGN_PROJECT: ${{ vars.VERCEL_PROJECT_DESIGN_SYSTEM }} - GAME_TOOLS_RPOJECT: ${{ vars.VERCEL_PROJECT_GAME_TOOLS }} + GAME_TOOLS_PROJECT: ${{ vars.VERCEL_PROJECT_GAME_TOOLS }} run: | matrix=$(echo "$AFFECTED" | jq -c ' . as $p |