@arbor-education/design-system.components 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent-memory/blanche-designspert/MEMORY.md +189 -0
- package/.agent-memory/dorothy-fact-checker/MEMORY.md +228 -0
- package/.agent-memory/dorothy-fact-checker/numberinput_component.md +53 -0
- package/.agent-memory/dorothy-fact-checker/progress_component.md +36 -0
- package/.agent-memory/rose-storybookspert/MEMORY.md +105 -0
- package/.agent-memory/sophia-componentspert/MEMORY.md +34 -0
- package/{.claude/agent-memory → .agent-memory}/sophia-componentspert/components.md +170 -17
- package/{.claude → .gather}/agents/blanche-designspert.md +7 -2
- package/{.claude → .gather}/agents/dorothy-fact-checker.md +7 -2
- package/{.claude → .gather}/agents/rose-storybookspert.md +80 -11
- package/{.claude → .gather}/agents/sophia-componentspert.md +9 -4
- package/.gather/gather.yaml +9 -0
- package/{CLAUDE.md → .gather/instructions/project-overview.md} +42 -9
- package/{.claude → .gather}/skills/analyze-design/README.md +5 -0
- package/{.claude → .gather}/skills/analyze-design/SKILL.md +1 -1
- package/.gather/skills/analyze-design/meta.md +4 -0
- package/{.claude → .gather}/skills/create-page/README.md +5 -0
- package/{.claude → .gather}/skills/create-page/design-analysis-template.md +5 -0
- package/.gather/skills/create-page/meta.md +4 -0
- package/{.claude → .gather}/skills/create-page/page-template.scss +5 -0
- package/{.claude → .gather}/skills/create-page/page-template.tsx +5 -0
- package/{.claude → .gather}/skills/map-legacy/README.md +5 -0
- package/.gather/skills/map-legacy/meta.md +4 -0
- package/{.claude → .gather}/skills/migrate-page/README.md +5 -0
- package/.gather/skills/migrate-page/meta.md +4 -0
- package/.gather/skills/write-stories/README.md +157 -0
- package/.gather/skills/write-stories/SKILL.md +841 -0
- package/.gather/skills/write-stories/meta.md +4 -0
- package/.ralph/storybook-upgrade/knowledge.md +308 -0
- package/.ralph/storybook-upgrade/prd.json +777 -0
- package/.ralph/storybook-upgrade/progress.md +342 -0
- package/.storybook/DocsTemplate.tsx +122 -0
- package/.storybook/preview.ts +40 -0
- package/.stylelintignore +2 -0
- package/CHANGELOG.md +14 -0
- package/{.claude/component-library.md → component-library.md} +27 -10
- package/dist/components/articleCard/ArticleCard.d.ts +30 -0
- package/dist/components/articleCard/ArticleCard.d.ts.map +1 -0
- package/dist/components/articleCard/ArticleCard.js +24 -0
- package/dist/components/articleCard/ArticleCard.js.map +1 -0
- package/dist/components/articleCard/ArticleCard.stories.d.ts +18 -0
- package/dist/components/articleCard/ArticleCard.stories.d.ts.map +1 -0
- package/dist/components/articleCard/ArticleCard.stories.js +112 -0
- package/dist/components/articleCard/ArticleCard.stories.js.map +1 -0
- package/dist/components/articleCard/ArticleCard.test.d.ts +2 -0
- package/dist/components/articleCard/ArticleCard.test.d.ts.map +1 -0
- package/dist/components/articleCard/ArticleCard.test.js +49 -0
- package/dist/components/articleCard/ArticleCard.test.js.map +1 -0
- package/dist/components/badge/Badge.stories.d.ts +85 -6
- package/dist/components/badge/Badge.stories.d.ts.map +1 -1
- package/dist/components/badge/Badge.stories.js +626 -27
- package/dist/components/badge/Badge.stories.js.map +1 -1
- package/dist/components/banner/Banner.stories.d.ts +129 -63
- package/dist/components/banner/Banner.stories.d.ts.map +1 -1
- package/dist/components/banner/Banner.stories.js +855 -39
- package/dist/components/banner/Banner.stories.js.map +1 -1
- package/dist/components/button/Button.stories.d.ts +148 -8
- package/dist/components/button/Button.stories.d.ts.map +1 -1
- package/dist/components/button/Button.stories.js +1089 -80
- package/dist/components/button/Button.stories.js.map +1 -1
- package/dist/components/card/Card.d.ts +41 -12
- package/dist/components/card/Card.d.ts.map +1 -1
- package/dist/components/card/Card.js +46 -17
- package/dist/components/card/Card.js.map +1 -1
- package/dist/components/card/Card.stories.d.ts +9 -84
- package/dist/components/card/Card.stories.d.ts.map +1 -1
- package/dist/components/card/Card.stories.js +15 -73
- package/dist/components/card/Card.stories.js.map +1 -1
- package/dist/components/card/Card.test.js +50 -152
- package/dist/components/card/Card.test.js.map +1 -1
- package/dist/components/dot/Dot.stories.d.ts +46 -11
- package/dist/components/dot/Dot.stories.d.ts.map +1 -1
- package/dist/components/dot/Dot.stories.js +504 -15
- package/dist/components/dot/Dot.stories.js.map +1 -1
- package/dist/components/dropdown/Dropdown.stories.d.ts +89 -14
- package/dist/components/dropdown/Dropdown.stories.d.ts.map +1 -1
- package/dist/components/dropdown/Dropdown.stories.js +769 -17
- package/dist/components/dropdown/Dropdown.stories.js.map +1 -1
- package/dist/components/formField/FormField.stories.d.ts +95 -35
- package/dist/components/formField/FormField.stories.d.ts.map +1 -1
- package/dist/components/formField/FormField.stories.js +1174 -69
- package/dist/components/formField/FormField.stories.js.map +1 -1
- package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts +96 -9
- package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js +717 -10
- package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js.map +1 -1
- package/dist/components/formField/inputs/number/NumberInput.stories.d.ts +149 -11
- package/dist/components/formField/inputs/number/NumberInput.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/number/NumberInput.stories.js +624 -10
- package/dist/components/formField/inputs/number/NumberInput.stories.js.map +1 -1
- package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts +74 -1
- package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js +673 -44
- package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js.map +1 -1
- package/dist/components/formField/inputs/text/TextInput.stories.d.ts +119 -1
- package/dist/components/formField/inputs/text/TextInput.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/text/TextInput.stories.js +549 -10
- package/dist/components/formField/inputs/text/TextInput.stories.js.map +1 -1
- package/dist/components/formField/inputs/textArea/TextArea.stories.d.ts +129 -4
- package/dist/components/formField/inputs/textArea/TextArea.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/textArea/TextArea.stories.js +577 -3
- package/dist/components/formField/inputs/textArea/TextArea.stories.js.map +1 -1
- package/dist/components/formField/inputs/time/TimeInput.d.ts +1 -1
- package/dist/components/formField/inputs/time/TimeInput.stories.d.ts +1 -1
- package/dist/components/heading/Heading.stories.d.ts +449 -50
- package/dist/components/heading/Heading.stories.d.ts.map +1 -1
- package/dist/components/heading/Heading.stories.js +536 -60
- package/dist/components/heading/Heading.stories.js.map +1 -1
- package/dist/components/icoText/IcoText.d.ts +37 -0
- package/dist/components/icoText/IcoText.d.ts.map +1 -0
- package/dist/components/icoText/IcoText.js +29 -0
- package/dist/components/icoText/IcoText.js.map +1 -0
- package/dist/components/icoText/IcoText.stories.d.ts +34 -0
- package/dist/components/icoText/IcoText.stories.d.ts.map +1 -0
- package/dist/components/icoText/IcoText.stories.js +24 -0
- package/dist/components/icoText/IcoText.stories.js.map +1 -0
- package/dist/components/icoText/IcoText.test.d.ts +2 -0
- package/dist/components/icoText/IcoText.test.d.ts.map +1 -0
- package/dist/components/icoText/IcoText.test.js +27 -0
- package/dist/components/icoText/IcoText.test.js.map +1 -0
- package/dist/components/icon/Icon.stories.d.ts +81 -10
- package/dist/components/icon/Icon.stories.d.ts.map +1 -1
- package/dist/components/icon/Icon.stories.js +979 -8
- package/dist/components/icon/Icon.stories.js.map +1 -1
- package/dist/components/kpiCard/KPICard.d.ts +13 -0
- package/dist/components/kpiCard/KPICard.d.ts.map +1 -0
- package/dist/components/kpiCard/KPICard.js +8 -0
- package/dist/components/kpiCard/KPICard.js.map +1 -0
- package/dist/components/kpiCard/KPICard.stories.d.ts +9 -0
- package/dist/components/kpiCard/KPICard.stories.d.ts.map +1 -0
- package/dist/components/kpiCard/KPICard.stories.js +18 -0
- package/dist/components/kpiCard/KPICard.stories.js.map +1 -0
- package/dist/components/kpiCard/KPICard.test.d.ts +2 -0
- package/dist/components/kpiCard/KPICard.test.d.ts.map +1 -0
- package/dist/components/kpiCard/KPICard.test.js +37 -0
- package/dist/components/kpiCard/KPICard.test.js.map +1 -0
- package/dist/components/kvpList/KVPList.d.ts +34 -0
- package/dist/components/kvpList/KVPList.d.ts.map +1 -0
- package/dist/components/kvpList/KVPList.js +20 -0
- package/dist/components/kvpList/KVPList.js.map +1 -0
- package/dist/components/kvpList/KVPList.stories.d.ts +27 -0
- package/dist/components/kvpList/KVPList.stories.d.ts.map +1 -0
- package/dist/components/kvpList/KVPList.stories.js +18 -0
- package/dist/components/kvpList/KVPList.stories.js.map +1 -0
- package/dist/components/kvpList/KVPList.test.d.ts +2 -0
- package/dist/components/kvpList/KVPList.test.d.ts.map +1 -0
- package/dist/components/kvpList/KVPList.test.js +29 -0
- package/dist/components/kvpList/KVPList.test.js.map +1 -0
- package/dist/components/pill/Pill.stories.d.ts +71 -19
- package/dist/components/pill/Pill.stories.d.ts.map +1 -1
- package/dist/components/pill/Pill.stories.js +573 -14
- package/dist/components/pill/Pill.stories.js.map +1 -1
- package/dist/components/progress/Progress.stories.d.ts +75 -298
- package/dist/components/progress/Progress.stories.d.ts.map +1 -1
- package/dist/components/progress/Progress.stories.js +449 -52
- package/dist/components/progress/Progress.stories.js.map +1 -1
- package/dist/components/separator/Separator.stories.d.ts +58 -5
- package/dist/components/separator/Separator.stories.d.ts.map +1 -1
- package/dist/components/separator/Separator.stories.js +443 -4
- package/dist/components/separator/Separator.stories.js.map +1 -1
- package/dist/components/singleUser/SingleUser.d.ts +1 -1
- package/dist/components/tabs/TabsItem.stories.d.ts +2 -2
- package/dist/components/tag/Tag.stories.d.ts +116 -5
- package/dist/components/tag/Tag.stories.d.ts.map +1 -1
- package/dist/components/tag/Tag.stories.js +581 -28
- package/dist/components/tag/Tag.stories.js.map +1 -1
- package/dist/index.css +194 -23
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +13 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/eslint.config.mts +5 -1
- package/package.json +3 -3
- package/src/components/articleCard/ArticleCard.stories.tsx +132 -0
- package/src/components/articleCard/ArticleCard.test.tsx +121 -0
- package/src/components/articleCard/ArticleCard.tsx +100 -0
- package/src/components/articleCard/articleCard.scss +39 -0
- package/src/components/badge/Badge.stories.tsx +869 -42
- package/src/components/banner/Banner.stories.tsx +1081 -63
- package/src/components/button/Button.stories.tsx +1394 -99
- package/src/components/card/Card.stories.tsx +35 -79
- package/src/components/card/Card.test.tsx +72 -190
- package/src/components/card/Card.tsx +117 -58
- package/src/components/card/card.scss +18 -31
- package/src/components/dot/Dot.stories.tsx +723 -32
- package/src/components/dropdown/Dropdown.stories.tsx +1174 -35
- package/src/components/formField/FormField.stories.tsx +1522 -105
- package/src/components/formField/inputs/checkbox/CheckboxInput.stories.tsx +1020 -15
- package/src/components/formField/inputs/number/NumberInput.stories.tsx +908 -15
- package/src/components/formField/inputs/radio/RadioButtonInput.stories.tsx +932 -51
- package/src/components/formField/inputs/text/TextInput.stories.tsx +773 -13
- package/src/components/formField/inputs/textArea/TextArea.stories.tsx +756 -8
- package/src/components/heading/Heading.stories.tsx +752 -120
- package/src/components/icoText/IcoText.stories.tsx +47 -0
- package/src/components/icoText/IcoText.test.tsx +41 -0
- package/src/components/icoText/IcoText.tsx +93 -0
- package/src/components/icoText/icoText.scss +34 -0
- package/src/components/icon/Icon.stories.tsx +1446 -12
- package/src/components/kpiCard/KPICard.stories.tsx +47 -0
- package/src/components/kpiCard/KPICard.test.tsx +60 -0
- package/src/components/kpiCard/KPICard.tsx +45 -0
- package/src/components/kpiCard/kpiCard.scss +35 -0
- package/src/components/kvpList/KVPList.stories.tsx +51 -0
- package/src/components/kvpList/KVPList.test.tsx +66 -0
- package/src/components/kvpList/KVPList.tsx +109 -0
- package/src/components/kvpList/kvpList.scss +64 -0
- package/src/components/pill/Pill.stories.tsx +867 -21
- package/src/components/progress/Progress.stories.tsx +625 -58
- package/src/components/separator/Separator.stories.tsx +730 -8
- package/src/components/separator/separator.scss +12 -3
- package/src/components/tag/Tag.stories.tsx +755 -53
- package/src/index.scss +4 -0
- package/src/index.ts +13 -4
- package/src/tokens.scss +6 -0
- package/tokens/json/Arbor.json +30 -0
- package/.claude/agent-memory/blanche-designspert/MEMORY.md +0 -64
- package/.claude/agent-memory/dorothy-fact-checker/MEMORY.md +0 -129
- package/.claude/agent-memory/rose-storybookspert/MEMORY.md +0 -29
- package/.claude/agent-memory/sophia-componentspert/MEMORY.md +0 -14
- package/.claude/design-assessment-daily-attendance-2026-04-10.md +0 -566
- package/.claude/figma-assessment-7154-58899.md +0 -404
- package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-11086-97537.md +0 -392
- package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-551-41974.md +0 -474
- package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-551-43094.md +0 -462
- package/.claude/figma-assessment-fcFK4CGzkz2fVyY3koX8ZE-7154-59061.md +0 -440
- package/.claude/migration-report-custom-report-writer-2026-02-19.md +0 -591
- /package/{.claude/agent-memory → .agent-memory}/blanche-designspert/token-review-patterns.md +0 -0
- /package/{.claude/agent-memory → .agent-memory}/rose-storybookspert/patterns.md +0 -0
- /package/{.claude → .gather}/skills/create-page/SKILL.md +0 -0
- /package/{.claude → .gather}/skills/map-legacy/SKILL.md +0 -0
- /package/{.claude → .gather}/skills/migrate-page/SKILL.md +0 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Blanche Designspert - Persistent Memory
|
|
2
|
+
|
|
3
|
+
## Key File Locations
|
|
4
|
+
- Design tokens: `src/tokens.scss` (CSS custom properties, NOT Sass vars - globally available, never import)
|
|
5
|
+
- Component public API: `src/index.ts`
|
|
6
|
+
- Styles entry: `src/index.scss`
|
|
7
|
+
- `src/pages/` does NOT exist on the current branch (confirmed 2026-04-17) — was on a prior branch
|
|
8
|
+
|
|
9
|
+
## Token Conventions (confirmed from src/tokens.scss)
|
|
10
|
+
- Spacing tokens: `--spacing-xsmall` (0.25rem), `--spacing-small` (0.5rem), `--spacing-medium` (0.75rem), `--spacing-large` (1rem), `--spacing-xlarge` (1.5rem), `--spacing-xxlarge` (2rem), `--spacing-xxxlarge` (4rem)
|
|
11
|
+
- Grey scale: `--color-grey-050` through `--color-grey-900` (050, 100, 200, 300, 400, 500, 600, 700, 800, 900)
|
|
12
|
+
- Mono: `--color-mono-black` (#202020), `--color-mono-white` (#ffffff)
|
|
13
|
+
- Font sizes: `--font-size-1-11` through `--font-size-8-40` (no `--font-size-5-18` listed on line 9-16 but IS defined at line 16)
|
|
14
|
+
- Font weights: `--font-weight-regular`, `--font-weight-medium`, `--font-weight-semi-bold`, `--font-weight-bold`
|
|
15
|
+
- Border radius: `--border-radius-xsmall` (4px), `--border-radius-small` (8px), `--border-radius-large` (16px), `--border-radius-round` (99px)
|
|
16
|
+
- Line heights: `--line-height-tight` (1.25), `--line-height-default` (1.5)
|
|
17
|
+
|
|
18
|
+
## Semantic / Component Tokens (use over base tokens)
|
|
19
|
+
- Hyperlink color: `--type-body-hyperlink-color` (maps to `--color-brand-700`) - use for link text
|
|
20
|
+
- Hyperlink weight: `--type-body-hyperlink-weight` (maps to `--font-weight-medium`)
|
|
21
|
+
- Body text size: `--type-body-p-size` (maps to `--font-size-2-13`)
|
|
22
|
+
- Page background: `--page-color-background` (maps to `--color-grey-050`)
|
|
23
|
+
- Page border: `--page-base-color-border` (maps to `--color-grey-050`)
|
|
24
|
+
- Page heading text: `--page-heading-color-text` (maps to `--color-mono-black`)
|
|
25
|
+
- Page heading gap: `--page-heading-spacing-gap` (maps to `--spacing-small`)
|
|
26
|
+
- Default icon color: `--icons-icon-default` (maps to `--color-grey-900`)
|
|
27
|
+
- Focus color: `--focus-color-focus` (maps to `--color-brand-500`)
|
|
28
|
+
|
|
29
|
+
## CSS Naming Conventions
|
|
30
|
+
- Prefix: `ds-` (all design system classes)
|
|
31
|
+
- BEM: `ds-{block}`, `ds-{block}__{element}`, `ds-{block}--{modifier}`
|
|
32
|
+
- SCSS file: camelCase matching directory name (e.g. `assessmentsPage.scss`)
|
|
33
|
+
- Use `classnames` library for conditional classes
|
|
34
|
+
|
|
35
|
+
## Typography Color Patterns (for body text in pages)
|
|
36
|
+
- Primary / heading text: `--color-grey-900` or `--color-mono-black`
|
|
37
|
+
- Secondary / supporting text: `--color-grey-600` or `--color-grey-500`
|
|
38
|
+
- Muted / disabled: `--color-grey-400`
|
|
39
|
+
- Links: use breadcrumb component tokens when in breadcrumbs (NOT raw `--type-body-hyperlink-color` or `--color-grey-600`)
|
|
40
|
+
- Current breadcrumb text: `--breadcrumbs-breadcrumb-link-active-color-text` (maps to `--color-grey-900`)
|
|
41
|
+
|
|
42
|
+
## Breadcrumb Component Tokens (tokens exist — line numbers may drift as tokens.scss grows)
|
|
43
|
+
- List gap: `--breadcrumbs-spacing-gap-horizontal` (maps to `--spacing-small`)
|
|
44
|
+
- Divider color: `--breadcrumbs-divider-default-color` (maps to `--color-grey-600`)
|
|
45
|
+
- Link default color: `--breadcrumbs-breadcrumb-link-default-color-text` (maps to `--color-grey-600`)
|
|
46
|
+
- Link hover color: `--breadcrumbs-breadcrumb-link-hover-color-text` (maps to `--color-brand-600`)
|
|
47
|
+
- Link active/current color: `--breadcrumbs-breadcrumb-link-active-color-text` (maps to `--color-grey-900`)
|
|
48
|
+
- Link hover icon color: `--breadcrumbs-breadcrumb-link-hover-color-icon` (maps to `--color-brand-600`)
|
|
49
|
+
- Link default icon color: `--breadcrumbs-breadcrumb-link-default-color-icon` (maps to `--color-grey-600`)
|
|
50
|
+
- No breadcrumb-specific font-weight token exists; use `--type-body-hyperlink-weight` for link weight
|
|
51
|
+
|
|
52
|
+
## Design Token Hierarchy to Enforce
|
|
53
|
+
1. Component tokens (e.g. `--breadcrumbs-breadcrumb-link-default-color-text`) - most specific, prefer always
|
|
54
|
+
2. Semantic tokens (e.g. `--type-body-hyperlink-color`, `--page-color-background`)
|
|
55
|
+
3. Base tokens (e.g. `--color-grey-600`) - only if no semantic equivalent
|
|
56
|
+
|
|
57
|
+
## Button Component Tokens (confirmed from src/tokens.scss + src/components/button/button.scss)
|
|
58
|
+
- Spacing pattern: `--button-{size}-spacing-vertical`, `--button-{size}-spacing-horizontal`, `--button-{size}-spacing-gap-vertical` where size = `medium` or `small`
|
|
59
|
+
- Height: `--size-medium` (2.25rem), `--size-small` (2rem) — these are the actual height tokens used
|
|
60
|
+
- Radius: `--button-medium-radius` / `--button-small-radius` (both map to `--border-radius-round`)
|
|
61
|
+
- Color pattern: `--button-{size}-{variant}-{state}-color-{role}` — e.g. `--button-medium-primary-default-color-background`
|
|
62
|
+
- Focus ring: `--focus-border` (3px) + `--button-{size}-{variant}-focus-color-focus` (most map to `--focus-color-focus` = `--color-brand-500`)
|
|
63
|
+
- Error border: `--color-semantic-destructive-500` (applied as `ds-button--error` modifier class)
|
|
64
|
+
- Disabled: CSS `opacity: 0.5` + `cursor: not-allowed` + `pointer-events: none` — no dedicated token
|
|
65
|
+
- Dropdown variant: uses raw base tokens (`--color-grey-200`, `--color-grey-500`, `--color-grey-900`, `--color-brand-500`) NOT component tokens — this is a known inconsistency in the SCSS
|
|
66
|
+
- `ds-text` class: NOT a formal design system class. It is used in story wrappers (e.g. Dropdown.stories.tsx) to apply standard `ds-` font-family/size via the global selector `*[class^='ds-']`. Use `className="ds-text"` on `<p>` or `<span>` wrapper elements in stories only.
|
|
67
|
+
- Story wrapper convention: `padding: 'var(--spacing-xlarge)'`, `gap: 'var(--spacing-large)'`, label text color `'var(--color-grey-600)'`, `className="ds-text"` for text labels
|
|
68
|
+
- Tertiary variant bug: `button.scss` uses `--button-small-tertiary-*` tokens for the tertiary variant regardless of size (not `--button-medium-tertiary-*`). No medium tertiary tokens in tokens.scss for tertiary — small tokens are used for both sizes.
|
|
69
|
+
|
|
70
|
+
## Banner Component Tokens (confirmed from src/tokens.scss lines 452–470 + banner.scss)
|
|
71
|
+
- Spacing: `--banner-spacing-gap`, `--banner-spacing-vertical`, `--banner-spacing-horizontal` (all resolve to `--spacing-large` / 1rem)
|
|
72
|
+
- Radius: `--banner-radius` (resolves to `--border-radius-small` / 8px)
|
|
73
|
+
- Color pattern: `--banner-{level}-color-{role}` where level = `info|neutral|warning|destructive` and role = `icon|text|border|background`
|
|
74
|
+
- Neutral level has NO `--banner-neutral-color-background` token — transparent bg is intentional
|
|
75
|
+
- Each level wraps semantic tokens: info/warning/destructive use `--color-semantic-{level}-050/500/800` pattern
|
|
76
|
+
- Neutral uses `--color-grey-400` (border) and `--color-grey-900` (icon + text)
|
|
77
|
+
- Inner `__central-container` gap uses `--spacing-small` — internal implementation detail, do not expose to consumers
|
|
78
|
+
|
|
79
|
+
## Banner Story Conventions
|
|
80
|
+
- AllVariants gap between stacked banners: `--spacing-xxlarge` (2rem) — gives enough breathing room over internal `--spacing-large` padding
|
|
81
|
+
- Neutral variant needs a visible backdrop in stories: `background: 'var(--color-grey-050)'` with `borderRadius: 'var(--border-radius-small)'` and `padding: 'var(--spacing-medium)'`
|
|
82
|
+
- AllVariants disclaimer label wording: "Storybook overview only — the design spec prohibits more than one banner per surface. Never render multiple banners simultaneously in a real page."
|
|
83
|
+
- Banner anti-patterns: hardcoded colours (use level prop), success messages (use Toast), stacking (one per surface), full-sentence titles (use short noun phrases)
|
|
84
|
+
|
|
85
|
+
## Icon Component Conventions (confirmed from src/components/icon/ + src/tokens.scss)
|
|
86
|
+
- Size tokens: `--icon-size-xsmall` (12px), `--icon-size-small` (16px), `--icon-size-medium` (24px) — exist but NOT used by Icon component itself. Used in SCSS of consuming components (e.g. checkboxInput.scss, numberInput.scss) to size containers that wrap icons.
|
|
87
|
+
- Icon component takes numeric prop `size?: 12 | 16 | 24` (type `IconSize`) — TypeScript enforces it, no runtime token lookup
|
|
88
|
+
- Default color is `'currentColor'` — always inherits from parent CSS `color` property
|
|
89
|
+
- Only 3 allowed sizes: 12 (dense UI only), 16 (default), 24 (high-significance contexts)
|
|
90
|
+
- Icon gallery cell convention: `minmax(7rem, 1fr)` for auto-fill grid, `gap: var(--spacing-small)`, cell `padding: var(--spacing-medium)`, label `font-size: var(--font-size-1-11)` + `color: var(--color-grey-600)`
|
|
91
|
+
- Icon+label tight gap: `--spacing-xsmall` (0.25rem) when icon and label are a fused unit; `--spacing-small` (0.5rem) when adjacent but distinct
|
|
92
|
+
- Semantic icon colours to use at -600 shade: `--color-semantic-info-600`, `--color-semantic-success-600`, `--color-semantic-warning-600`, `--color-semantic-destructive-600` — all confirmed present
|
|
93
|
+
- CurrentColor inheritance swatches for stories: `--color-grey-900` (default/`--icons-icon-default`), `--color-brand-700` (interactive/link), `--color-semantic-destructive-600` (severity), `--color-grey-400` (muted/disabled)
|
|
94
|
+
- Solid icons (`check-solid`, `x-solid`, `favourite-filled`) must not be mixed with outline Lucide icons in flowing content — reserved for specific state indicators
|
|
95
|
+
- `aria-hidden` always applied by component; `screenReaderText` prop renders `sr-only` span — icon-only interactive elements MUST pass `screenReaderText`
|
|
96
|
+
- `--icons-icon-default` token resolves to `--color-grey-900` (line 355, tokens.scss)
|
|
97
|
+
|
|
98
|
+
## Pill Component Tokens (confirmed from src/tokens.scss lines 268–301 + 1346–1349)
|
|
99
|
+
- Spacing: `--pill-spacing-vertical` (xsmall), `--pill-spacing-horizontal` (small), `--pill-spacing-gap-horizontal` (small), radius: `--pill-radius` (round)
|
|
100
|
+
- Toggle mode: `--pill-single-filter-{default|hover|active|selected}-color-{background|border|text}`
|
|
101
|
+
- default: white bg, grey-100 border; active: grey-100 bg, grey-500 border; all text grey-900
|
|
102
|
+
- Checkbox mode: `--pill-checkbox-{default|hover|selected}-color-{background|border|text|icon}`
|
|
103
|
+
- default: white bg, grey-100 border; selected: brand-050 bg, brand-600 border, brand-800 text
|
|
104
|
+
- Focus: `--pill-{checkbox|single-filter}-{default|selected}-focus` all resolve to `--focus-color-focus`
|
|
105
|
+
- CSS classes: `ds-pill__inactive`/`ds-pill__active` (toggle), `ds-pill__unchecked`/`ds-pill__checked` (checkbox)
|
|
106
|
+
- Known a11y limitation: renders `<span onClick>` — not keyboard accessible (no tabIndex, role, or onKeyDown)
|
|
107
|
+
- "All" reset: normal `<Pill text="All">` at position 0, no special props — just stateful management in parent
|
|
108
|
+
- Filter group layout: `flexWrap: wrap`, `gap: var(--spacing-small)`, NEVER horizontal scroll (Confluence rule)
|
|
109
|
+
|
|
110
|
+
## FormField Component Tokens (confirmed from src/tokens.scss lines 1034–1151)
|
|
111
|
+
- Spacing: `--form-field-spacing-vertical` (small/0.5rem), `--form-field-spacing-horizontal` (small/0.5rem), `--form-field-spacing-vertical-gap` (xsmall/0.25rem), `--form-field-spacing-padding-x/left/right`
|
|
112
|
+
- Sizing: `--form-field-text-small-height` (2rem), `--form-field-text-medium-height` (2.25rem), `--form-field-radius` (border-radius-small/8px)
|
|
113
|
+
- Label: `--form-field-label-color-text` (grey-900), `--form-field-label-color-icon` (grey-900)
|
|
114
|
+
- Description: `--form-field-color-label-description-text` (grey-600) — also aliased as `--form-field-help-text-color-label-description-text`
|
|
115
|
+
- Input text state pattern: `--form-field-text-{state}-color-{role}` (state: default/hover/focus/active/error/disabled/uneditable/placeholder; role: text/border/background)
|
|
116
|
+
- Error message: `--form-field-text-error-color-error-text` (grey-600), `--form-field-text-error-color-error-icon` (semantic-destructive-500)
|
|
117
|
+
- Help text link default: `--form-field-help-text-default-color-text` (brand-600), `--form-field-help-text-default-color-icon` (brand-600)
|
|
118
|
+
- Help text link hover: `--form-field-help-text-hover-color-text` (brand-800), `--form-field-help-text-hover-color-icon` (brand-800)
|
|
119
|
+
- Combobox has a full parallel set: `--form-field-combobox-*` — same state pattern
|
|
120
|
+
- Textarea has a full parallel set: `--form-field-textarea-*` — same state pattern
|
|
121
|
+
- Story wrapper: `maxWidth: '28rem'`, `padding: 'var(--spacing-xlarge)'`; stacked field gap: `--spacing-large`; section group gap: `--spacing-xxlarge`
|
|
122
|
+
|
|
123
|
+
## TextArea-Specific Story Conventions
|
|
124
|
+
- `--form-field-textarea-*` component tokens exist (lines 1116–1139) and mirror the `--form-field-text-*` pattern exactly — always prefer these over base tokens in TextArea stories
|
|
125
|
+
- There is NO dedicated textarea character-count token. Use `--color-grey-600` (normal count), `--color-semantic-caution-600` (approaching limit), `--color-semantic-destructive-600` (at/over limit)
|
|
126
|
+
- There is NO `--form-field-textarea-min-height` token — component uses `scrollHeight` math in JS. No CSS min-height convention is codified.
|
|
127
|
+
- Default `rows` for stories: 3 (matches Figma spec density for multi-line fields)
|
|
128
|
+
- Story wrapper: same as FormField — `maxWidth: '28rem'`, `padding: 'var(--spacing-xlarge)'`; stacked story gap `--spacing-large`; character count label/count row gap `--spacing-xsmall`
|
|
129
|
+
- `autoSize` resize uses `scrollHeight + 4px` vertical padding hardcoded in component (not a token — implementation detail)
|
|
130
|
+
- Focus outline in input.scss line 22 uses `--button-medium-primary-focus-color-focus` — this is a known cross-token leak (not a textarea-specific token). Do NOT surface this as intended to story readers; just use `--focus-border` + `--focus-color-focus` when describing focus behaviour in prose.
|
|
131
|
+
|
|
132
|
+
## CheckboxInput Component Tokens (confirmed from src/tokens.scss lines 965–995 + 1378–1382 + checkboxInput.scss)
|
|
133
|
+
- Control size: `--icon-size-small` (16px) — fixed, one size only, no size variants
|
|
134
|
+
- Internal horizontal gap (control to label): `--checkbox-spacing-gap-horizontal` → `--spacing-small` (8px)
|
|
135
|
+
- Focus outline (row with label): `--checkbox-radius` → `--border-radius-small` (8px)
|
|
136
|
+
- Focus outline (control only, no label): `--checkbox-input-control-radius` → `--border-radius-xsmall` (4px)
|
|
137
|
+
- Control radius: `--checkbox-input-control-radius` → `--border-radius-xsmall` (4px)
|
|
138
|
+
- Border is a box-shadow (`inset 0 0 0 [weight] [color]`), NOT a CSS border — overriding `border` CSS has no effect
|
|
139
|
+
- Border weight: `--checkbox-input-control-unchecked-weight-border` → `--border-weight` (global token)
|
|
140
|
+
- Unchecked background: `--checkbox-input-control-unchecked-default-color-background` → `--color-mono-white` (NOT transparent)
|
|
141
|
+
- Checked bg: `--checkbox-input-control-checked-default-color-background` → `--color-brand-600`
|
|
142
|
+
- Indeterminate bg: `--checkbox-input-control-indeterminate-default-color-background` → `--color-brand-600`
|
|
143
|
+
- Disabled (any state) bg: all resolve to `--color-grey-400`
|
|
144
|
+
- Icon color applied inline via JSX `color` prop (not CSS) — cannot override with CSS targeting `.ds-checkbox-input__icon`
|
|
145
|
+
- Label text: `--checkbox-default-color-text` → `--color-grey-900`; hover: `--checkbox-hover-color-text` → `--color-grey-900`
|
|
146
|
+
- Disabled label tokens: `--checkbox-default-disabled-color-text` + `--checkbox-selected-disabled-color-text` → `--color-grey-600` — exist in tokens.scss but NOT applied in checkboxInput.scss (known implementation gap)
|
|
147
|
+
- Focus tokens: `--checkbox-default-focus` + `--checkbox-selected-focus` → `--focus-color-focus`; NOTE: SCSS uses `var(--color-brand-500)` directly (cross-token inconsistency — do not surface as intentional)
|
|
148
|
+
- `--checkbox-group-spacing-gap-horizontal` exists in tokens.scss but is NOT used in any SCSS — reserved token, do not reference as active
|
|
149
|
+
- `--checkbox-spacing-padding` → `--spacing-small` — exists but not applied in component SCSS
|
|
150
|
+
|
|
151
|
+
## CheckboxGroup / Fieldset Tokens
|
|
152
|
+
- Fieldset uses `--checkbox-or-radio-button-group-spacing-gap-vertical` → `--spacing-medium` (12px) for gap between items
|
|
153
|
+
- Fieldset legend: `--type-body-bold-weight` + `--form-field-label-color-text` (grey-900) + `margin-bottom: --form-field-spacing-vertical`
|
|
154
|
+
- CheckboxGroup passes `options` array of `CheckboxInputProps` — `id` field is required per `key` usage
|
|
155
|
+
- `legend` prop is technically optional in TypeScript but required for accessibility (a11y rule: always pass it)
|
|
156
|
+
|
|
157
|
+
## CheckboxInput Story Wrapper Conventions
|
|
158
|
+
- Outer container: `padding: 'var(--spacing-xlarge)'`, `maxWidth: '28rem'` (matches FormField convention)
|
|
159
|
+
- AllStates vertical list gap: `--spacing-medium` (12px) — NOT `--spacing-small` (too tight vs focus outline bleed)
|
|
160
|
+
- Section heading (quiet): `font-size: var(--font-size-2-13)`, `color: var(--color-grey-600)`, `font-weight: var(--font-weight-regular)`
|
|
161
|
+
- Section heading (prominent): `font-size: var(--font-size-3-14)`, `color: var(--color-grey-900)`, `font-weight: var(--font-weight-semi-bold)`
|
|
162
|
+
|
|
163
|
+
## RadioButtonInput Component Tokens (confirmed from src/tokens.scss lines 1324–1344 + radioButtonInput.scss)
|
|
164
|
+
- Shared token namespace: `--checkbox-or-radio-button-group-*` — tokens are shared between CheckboxInput and RadioButtonInput
|
|
165
|
+
- Horizontal gap (indicator to label): `--checkbox-or-radio-button-group-spacing-gap-horizontal` → `--spacing-small` (8px)
|
|
166
|
+
- Vertical gap between items in Fieldset: `--checkbox-or-radio-button-group-spacing-gap-vertical` → `--spacing-medium` (12px) — built into Fieldset SCSS, do NOT add a wrapper gap
|
|
167
|
+
- Focus ring radius: `--checkbox-radius` → `--border-radius-small` (8px) — same token as CheckboxInput
|
|
168
|
+
- Focus ring color: hardcoded `var(--color-brand-500)` in SCSS (NOT using `--focus-color-focus` token — known inconsistency, do not surface as intentional)
|
|
169
|
+
- Label text: `--checkbox-default-color-text` → `--color-grey-900`
|
|
170
|
+
- Disabled label text: `--checkbox-or-radio-button-group-disabled-color-text` → `--color-grey-600`
|
|
171
|
+
- Default indicator: border `--checkbox-or-radio-button-group-default-color-border` (grey-500), bg `--checkbox-or-radio-button-group-default-color-background` (white)
|
|
172
|
+
- Selected indicator: border `--checkbox-or-radio-button-group-selected-color-border` (brand-600), bg `--checkbox-or-radio-button-group-selected-color-background` (white), dot `--checkbox-or-radio-button-group-selected-color-check` (brand-600)
|
|
173
|
+
- Disabled indicator: border `--checkbox-or-radio-button-group-disabled-color-border` (grey-500), bg `--checkbox-or-radio-button-group-disabled-color-background` (grey-100), dot `--checkbox-or-radio-button-group-disabled-color-check` (grey-500)
|
|
174
|
+
- Hover (unselected): border grey-900, bg white — `--checkbox-or-radio-button-group-hover-color-border/background`
|
|
175
|
+
- Hover (selected): border brand-800, bg white, dot brand-800 — `--checkbox-or-radio-button-group-selected-hover-color-border/background/check`
|
|
176
|
+
- CRITICAL: `hasError` prop ONLY sets `aria-invalid` on the `<input>`. There are ZERO radio-specific error visual tokens in tokens.scss and ZERO error CSS rules in radioButtonInput.scss. The indicator does NOT change colour on error. Error state is semantic only (ARIA). Consumers must render their own error message below the group.
|
|
177
|
+
- Error message text in stories: use `--form-field-text-error-color-error-text` (grey-600) for message text, `--form-field-text-error-color-error-icon` (semantic-destructive-500) for any error icon alongside it
|
|
178
|
+
- Story wrapper: `maxWidth: '28rem'`, `padding: 'var(--spacing-xlarge)'` — matches CheckboxInput and FormField conventions
|
|
179
|
+
- AllStates vertical list gap: `--spacing-medium` — same as CheckboxInput (focus ring bleed concern)
|
|
180
|
+
- RadioButtonGroup: uses Fieldset internally — do NOT wrap RadioButtonGroup in a gap container; Fieldset already applies `--checkbox-or-radio-button-group-spacing-gap-vertical` as its internal flex gap
|
|
181
|
+
|
|
182
|
+
## Known Issues / Patterns
|
|
183
|
+
- Breadcrumb implementations commonly use `--color-grey-600` or `--type-body-hyperlink-color` when dedicated `--breadcrumbs-*` component tokens exist and should be used instead
|
|
184
|
+
- Breadcrumb hover state commonly missing color change; `--breadcrumbs-breadcrumb-link-hover-color-text` should be applied on hover alongside text-decoration
|
|
185
|
+
- `--type-body-bold-weight` DOES exist in tokens.scss (maps to `--font-weight-semi-bold`). It IS a valid semantic token. Use it for bold body text emphasis instead of reaching for the base token directly.
|
|
186
|
+
- Also note: `--hyperlink-default`, `--hyperlink-hover`, `--hyperlink-active` are component tokens for hyperlinks. These are the most specific link tokens and should be used in preference to `--type-body-hyperlink-color` for actual inline hyperlinks.
|
|
187
|
+
- `src/examples/` does NOT exist on the current branch (confirmed 2026-04-17) — was on a prior branch
|
|
188
|
+
- No `@use` import needed for tokens.scss in SCSS files - tokens are CSS custom properties, globally available
|
|
189
|
+
- Do NOT use breadcrumb component tokens (e.g. `--breadcrumbs-breadcrumb-link-default-color-text`) for non-breadcrumb elements like table row counts. Use `--color-grey-600` for secondary/supporting body text when no semantic token exists.
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# Dorothy Fact-Checker Memory
|
|
2
|
+
|
|
3
|
+
## Agent File Locations
|
|
4
|
+
- Agent definitions (all four): `.claude/agents/{blanche-designspert,dorothy-fact-checker,rose-storybookspert,sophia-componentspert}.md`
|
|
5
|
+
- Agent memory directories: `.claude/agent-memory/{agent-name}/`
|
|
6
|
+
- These files use relative paths (from project root) for portability across developer machines
|
|
7
|
+
- Frontmatter `name` field must match the filename (without .md extension)
|
|
8
|
+
|
|
9
|
+
## The Golden Girls Team
|
|
10
|
+
- blanche-designspert: model sonnet, color purple, design tokens & styling
|
|
11
|
+
- dorothy-fact-checker: model opus, color blue, QA & fact-checking
|
|
12
|
+
- rose-storybookspert: model sonnet, color pink, Storybook stories
|
|
13
|
+
- sophia-componentspert: model sonnet, color gold, component guidance
|
|
14
|
+
- Emojis: blanche=lipstick, dorothy=magnifying glass, rose=rose, sophia=purse
|
|
15
|
+
|
|
16
|
+
## Create-Page Skill Integration
|
|
17
|
+
- Skill at `.claude/skills/create-page/SKILL.md` calls agents at steps 3/4 (Sophia), 6 (Blanche), 8 (Rose), 10 (Dorothy)
|
|
18
|
+
- Agent names in backtick code refs are what matter for resolution, not the heading text
|
|
19
|
+
|
|
20
|
+
## Verification Patterns
|
|
21
|
+
- When checking agent file changes, compare against what was loaded into the system prompt (which reflects the pre-change state) since these files may be untracked and have no git history to diff against
|
|
22
|
+
- The Grep tool and memory system resolve relative paths against the project working directory
|
|
23
|
+
- Claude Code injects absolute paths at runtime even when files use relative paths -- this is expected behavior
|
|
24
|
+
|
|
25
|
+
## Common Issues to Watch For
|
|
26
|
+
- Absolute paths leaking into config files (especially `/Users/{username}/...` patterns)
|
|
27
|
+
- Files claimed to be committed but actually untracked (check `git ls-files` and `git status`)
|
|
28
|
+
- Agent memory directories may be empty (no MEMORY.md) until agents run for the first time -- this is by design
|
|
29
|
+
|
|
30
|
+
## Project Structure Notes
|
|
31
|
+
- `src/pages/` is a new directory added for page-level components (not in original architecture)
|
|
32
|
+
- No `Pages/*` path alias exists in tsconfig.json -- only `Components/*` and `Utils/*`
|
|
33
|
+
- Therefore page exports in `src/index.ts` must use relative paths (`./pages/...`) not aliases
|
|
34
|
+
- ESLint catches stylistic issues that `check-types` and `style-lint` do not -- always run all three
|
|
35
|
+
|
|
36
|
+
## Tag vs Badge Prop Spelling (confirmed 2026-04-17)
|
|
37
|
+
- Tag uses `color` (American spelling): `color?: TagColor` (Tag.tsx line 15)
|
|
38
|
+
- Badge uses `colour` (British spelling): `colour?: BadgeColour` (Badge.tsx line 12)
|
|
39
|
+
- TagColor includes a `'neutral'` option that BadgeColour/DotColour does NOT have
|
|
40
|
+
- Rose frequently conflates the two in BAD/GOOD doc snippets — always check spelling in prose code examples
|
|
41
|
+
|
|
42
|
+
## Phantom Token Names (common Rose/Blanche hallucinations, verified 2026-04-17)
|
|
43
|
+
These tokens DO NOT EXIST in src/tokens.scss — flag them in any story wrapper inline styles:
|
|
44
|
+
- `--color-grey-50` (smallest grey is `--color-grey-100`) — confirmed AGAIN 2026-04-17 in Icon.stories.tsx line 830 gallery cell `background: 'var(--color-grey-50)'`. Recurring Rose hallucination.
|
|
45
|
+
- CRITICAL DISTINCTION: `--color-grey-050` (THREE digits, leading zero) IS VALID — declared at tokens.scss:39 as `#f8f8f8`. Dorothy has wrongly flagged this as phantom — always verify the exact spelling (`-50` vs `-050`) before flagging. Both are real-looking; only the 3-digit `-050` variant actually exists.
|
|
46
|
+
- `--font-size-small` (actual scale: `--font-size-1-11`, `--font-size-2-13`, `--font-size-3-14`, `--font-size-4-16`, `--font-size-5-18`, `--font-size-6-22`, `--font-size-7-27`, `--font-size-8-40`)
|
|
47
|
+
- `--border-radius-medium` (actual: `--border-radius-xsmall` 4px, `--border-radius-small` 8px, `--border-radius-large` 16px, `--border-radius-round` 99px) — confirmed AGAIN 2026-04-17 in Heading.stories.tsx:362 wrapper style `borderRadius: 'var(--border-radius-medium)'`. Recurring Rose hallucination — the "medium" tier does NOT exist between small (8px) and large (16px). Fix is always `--border-radius-small`.
|
|
48
|
+
- `--color-semantic-info-200` does NOT exist (scale is 050, 100, 300, 500, 600, 700, 800, 900 — no 200 tier). Confirmed 2026-04-22 in Separator.stories.tsx:585.
|
|
49
|
+
- `--color-semantic-warning-200` does NOT exist either (same scale gaps).
|
|
50
|
+
When fact-checking stories, always verify every `var(--X)` reference against tokens.scss — the CSS won't throw on phantom names but demos will render with fallback defaults
|
|
51
|
+
|
|
52
|
+
## Banner Component Quirk (verified 2026-04-17)
|
|
53
|
+
- `banner.scss:44` references `var(--banner-neutral-color-background)` but this token is NOT declared in tokens.scss
|
|
54
|
+
- tokens.scss lines 457-459 only declare `--banner-neutral-color-{icon,text,border}` — NO background
|
|
55
|
+
- The CSS falls back to `transparent` (CSS spec behaviour for unresolved custom property on background-color) — so it renders correctly by accident
|
|
56
|
+
- If a story description claims "Neutral variant intentionally has no background token by design", flag that it's actually an undeclared reference that happens to fall back to transparent
|
|
57
|
+
- The four declared banner layout tokens are `--banner-radius`, `--banner-spacing-gap`, `--banner-spacing-vertical`, `--banner-spacing-horizontal` (all verified)
|
|
58
|
+
- Banner source at Banner.tsx has NO role attribute on root div — so "does not set role='alert'" claims are accurate
|
|
59
|
+
|
|
60
|
+
## Storybook Link Slug Verification Pattern (confirmed 2026-04-17)
|
|
61
|
+
- Always verify the actual `title:` in the target story file (not infer from component name)
|
|
62
|
+
- For Toast: `Components/Toast` → `components-toast--docs` ✓
|
|
63
|
+
- For Modal: `Components/Modals/Modal` → `components-modals-modal--docs` ✓ (note the `s` in Modals — this is a common hallucination trap)
|
|
64
|
+
- For Button: `Components/Button` → `components-button--docs` ✓
|
|
65
|
+
- For Icon: `Components/Icon` → `components-icon--docs` ✓
|
|
66
|
+
- For Banner: `Components/Banner` → `components-banner--docs` ✓
|
|
67
|
+
- For Badge: `Components/Badge` → `components-badge--docs` ✓
|
|
68
|
+
- For Separator: `Components/Separator` → `components-separator--docs` ✓
|
|
69
|
+
- For Dropdown: `Components/Dropdown` → `components-dropdown--docs` ✓
|
|
70
|
+
- For Section: `Components/Section` → `components-section--docs` ✓
|
|
71
|
+
|
|
72
|
+
## Progress Component (verified 2026-04-17) — see [progress_component.md](progress_component.md)
|
|
73
|
+
- Radix default `getValueLabel` returns `"X%"` (percentage string), NOT raw number. Agents frequently mischaracterise this (Rose wrote "raw number" in 5 places in Progress.stories.tsx).
|
|
74
|
+
- `value={null}` produces an INVISIBLE bar in this wrapper (Number(null)=0, translateX(-100%), no indeterminate CSS animation defined)
|
|
75
|
+
- Radix auto-sets `role="progressbar"`, `aria-valuemax/min/now`, and `aria-valuetext` from `getValueLabel`
|
|
76
|
+
- Radix silently coerces out-of-range `value` to null (console.error + aria-valuenow omitted) but wrapper's translateX math still uses raw value
|
|
77
|
+
- Progress uses `--border-radius-large` (16px), height `--spacing-small` (8px), root bg `--color-grey-100`, indicator bg `--color-brand-600`
|
|
78
|
+
- Exported at index.ts:39 — Progress only, no type exports
|
|
79
|
+
|
|
80
|
+
## Dot Component API (verified 2026-04-17)
|
|
81
|
+
- Props: `colour` (required, type `DotColour`) and `label` (optional string). That's IT. No size, className, style, onClick — deliberately closed API.
|
|
82
|
+
- `DotColour` = 7 values: purple, salmon, teal, yellow, green, orange, blue (Dot.tsx:3)
|
|
83
|
+
- `BadgeColour = DotColour` — literal type alias at Badge.tsx:5 (structurally guaranteed identical, not just "same values")
|
|
84
|
+
- Renders as `<span aria-hidden={label ? undefined : 'true'} aria-label={label} className="ds-dot ds-dot--{colour}" />` (Dot.tsx:10-18)
|
|
85
|
+
- Fixed 10×10px circle with `flex-shrink: 0` baked in (dot.scss:3-6)
|
|
86
|
+
- Each colour uses `--color-extended-colours-{colour}-500` (dot.scss:8-34, all 7 declared at tokens.scss:107, 111, 115, 119, 123, 127, 131)
|
|
87
|
+
- Exported at index.ts:18-19 as `Dot` + `DotColour`
|
|
88
|
+
|
|
89
|
+
## Separator Component (verified 2026-04-22)
|
|
90
|
+
- No forwardRef — plain function component wrapping RadixSeparator.Root
|
|
91
|
+
- Props: `className?: string` + all RadixSeparator.SeparatorProps (orientation, decorative, style, etc.)
|
|
92
|
+
- SCSS: `.ds-separator { width:100%; height:1px; background-color: var(--page-base-color-border); margin: var(--spacing-small) 0; }`
|
|
93
|
+
- --page-base-color-border resolves to --color-grey-050 (tokens.scss:171) -- SAME as --page-color-background (tokens.scss:254) -- so separator IS invisible on default bg
|
|
94
|
+
- No `--separator-*` tokens exist in tokens.scss
|
|
95
|
+
- Exported at index.ts:42, SCSS registered at index.scss:31
|
|
96
|
+
|
|
97
|
+
## Phantom CSS Class: `ds-text` (identified 2026-04-22)
|
|
98
|
+
- `ds-text` is NOT defined in any SCSS file in this codebase
|
|
99
|
+
- Used across 12+ story files as `className="ds-text"` on `<p>`, `<span>` elements — it's a no-op
|
|
100
|
+
- Not harmful (won't break anything) but applies no styling — it's a Rose convention with no backing SCSS
|
|
101
|
+
- Similar but DIFFERENT: `ds-text-input` (TextInput.tsx:19) and `ds-textarea` (TextArea.tsx:23) ARE real classes
|
|
102
|
+
|
|
103
|
+
## Rose Accuracy Trend (2026-04-17)
|
|
104
|
+
- Banner stories audit: 14/14 icons verified, all tokens verified, all slug links correct, no phantom tokens, no unsourced authority claims, no hardcoded px/rem/hex in wrappers. Zero hallucinations.
|
|
105
|
+
- Tag stories audit: 8/8 TagColor values verified, 3/3 icon names ('user', 'chevron-down', 'x') verified, 6/6 doc slugs correct (badge/pill/dot/combobox/icon/button), 13+ tokens verified, 4 behavioural claims verified against source (selected overrides color, e.stopPropagation, removeLabel default 'Remove', non-removable renders as span with no role/tabindex). `fn` imported from `storybook/test` correctly. Zero structural hallucinations. Only flag: 6 references to "Confluence design spec" that require human verification against the actual Confluence page — same pattern as other Rose stories, plausible per write-stories skill workflow.
|
|
106
|
+
- This is a significant improvement from prior audits where Rose conflated Tag `color` / Badge `colour` spelling and broke doc links (e.g. `components-formfield-inputs-checkboxinput--docs`)
|
|
107
|
+
- Rose now correctly imports `fn` from `storybook/test` (Storybook 9.1.8+ path — verified in node_modules/storybook/package.json exports)
|
|
108
|
+
- Dot stories audit (2026-04-17): 6 stories, zero hallucinations, zero phantom tokens, zero hardcoded values. `gap: 8` properly replaced with token. All slugs correct, all 7 extended-colour tokens verified, BadgeColour/DotColour equivalence correctly identified as type alias (not just shared values). Only caveats: one Confluence reference and a "de-facto Arbor colour semantics" table that require human/designer sign-off since they cite external context not in code.
|
|
109
|
+
- Separator stories audit (2026-04-22): 6 stories, ONE phantom token (`--color-semantic-info-200` at line 585 — does not exist, scale jumps from 100 to 300). All other tokens verified (20+ references). All behavioural claims correct (no forwardRef, invisible-on-default-bg, decorative role behaviour, vertical not sized by SCSS, Dropdown.Separator exists with ds-dropdown__separator). Both doc slug links correct. Zero ESLint/TS errors. Only other note: `ds-text` className used throughout is a phantom class with no backing SCSS.
|
|
110
|
+
|
|
111
|
+
## Tag Component API (verified 2026-04-17)
|
|
112
|
+
- Props: `children` (required ReactNode), `color?: TagColor`, `selected?: boolean`, `slotStart?: ReactNode`, `slotEnd?: ReactNode`, `onRemove?: () => void`, `removeLabel?: string` (default `'Remove'`), `removeButtonTabIndex?: 0 | -1` (default `0`)
|
|
113
|
+
- 8 TagColor values: `neutral` (default), `orange`, `blue`, `green`, `purple`, `teal`, `salmon`, `yellow`
|
|
114
|
+
- Renders as `<span className="ds-tag ds-tag--{color}">` with optional `.ds-tag--selected` modifier
|
|
115
|
+
- Non-removable: no `role`, no `tabIndex` on root span (correct a11y — non-interactive elements shouldn't be focusable)
|
|
116
|
+
- Removable: renders `<button type="button" className="ds-tag__remove" aria-label={removeLabel} onClick={e => { e.stopPropagation(); onRemove(); }} tabIndex={removeButtonTabIndex}>` with inner `<Icon name="x" size={12} />`
|
|
117
|
+
- `selected` state overrides `color` bg+border via `--tag-selected-color-background/border` (tokens.scss:234-235), text reverts to `--tag-neutral-color-text`
|
|
118
|
+
- All 8 `--tag-category-{colour}-color-{text,background}` tokens declared in tokens.scss:217-230 (orange/blue/green/purple/teal/salmon/yellow) plus `--tag-neutral-color-{text,background}` at :215-216
|
|
119
|
+
- Layout tokens: `--tag-radius` (border-radius-round/99px), `--tag-height` (size-control-xsmall), `--tag-spacing-horizontal` (spacing-medium), `--tag-spacing-gap-horizontal` (spacing-small), `--tag-spacing-vertical` (spacing-xsmall)
|
|
120
|
+
- Tag exported with `TagProps` and `TagColor` types at src/index.ts:16-17
|
|
121
|
+
|
|
122
|
+
## Common Hallucination Patterns
|
|
123
|
+
- Agents may claim "all quality checks passed" but only run check-types and style-lint, missing ESLint errors
|
|
124
|
+
- ESLint stylistic rules (@stylistic/arrow-parens, @stylistic/brace-style, @stylistic/operator-linebreak, @stylistic/jsx-one-expression-per-line) are frequently violated in generated code
|
|
125
|
+
- The `yarn eslint` command is not listed in CLAUDE.md's common commands but is critical for quality
|
|
126
|
+
- **NEW PAGE REGISTRATION**: Agents frequently create page component files but forget to register them in `src/index.ts` (export) and `src/index.scss` (@use). The existing pattern (see AssessmentsPage) requires BOTH. Without these, the SCSS is dead code and the component is not part of the library's public API
|
|
127
|
+
- Icon name grep: allowedIcons file is `.tsx` not `.ts` -- use correct extension when searching
|
|
128
|
+
- **Components that DO exist (agents have hallucinated these as missing):**
|
|
129
|
+
- `Badge` IS exported (index.ts line 20) with `BadgeColour`, `BadgeProps`, `BadgeSize` — `'sm'|'md'|'lg'` sizes, 7 colours
|
|
130
|
+
- `Dot` IS exported (index.ts line 18) with `DotColour`
|
|
131
|
+
- `CheckboxInput` IS exported (index.ts line 24)
|
|
132
|
+
- `SearchBar` IS exported (index.ts line 40)
|
|
133
|
+
- `BooleanCellRenderer` IS exported (index.ts line 47), also registered as `'dsBooleanCellRenderer'`
|
|
134
|
+
- **Sophia prop hallucinations (confirmed 2026-02-19):**
|
|
135
|
+
- Button `ghost` variant does NOT exist (actual: primary, secondary, tertiary, primary-destructive, secondary-destructive, text-link, dropdown) ✅ CORRECT
|
|
136
|
+
- Section `defaultExpanded` does NOT exist (actual: `collapsed` boolean, inverted logic) ✅ CORRECT
|
|
137
|
+
- Section `headerContent` does NOT exist (Section has buttonText/buttonOnClick for a single button, not an arbitrary ReactNode slot -- Table has headerContent) ✅ CORRECT
|
|
138
|
+
- Icon `grip-vertical` does NOT exist (actual name: `'grab'` which maps to GripVertical lucide icon) ✅ CORRECT
|
|
139
|
+
- CheckboxInput IS exported from index.ts (line 24, confirmed 2026-04-17) -- DO NOT claim it's internal
|
|
140
|
+
- Tree data: codebase uses `treeDataChildrenField` pattern, not `getDataPath` ✅ CORRECT
|
|
141
|
+
- TextInput has NO icon/prefix support -- it's a bare `<input>` wrapper with only `size` and `hasError` custom props; everything else is passthrough from InputHTMLAttributes ✅ CORRECT
|
|
142
|
+
- Dropdown has `Dropdown.SelectItem` in addition to `Dropdown.Item` -- agents often omit SelectItem ✅ CORRECT
|
|
143
|
+
- Table's `treeData`, `getDataPath`, `rowDragManaged` are AG Grid passthroughs (via AgGridReactProps), not Arbor-specific features -- agents should not present them as Table component features ✅ CORRECT
|
|
144
|
+
- **Dorothy's mistakes (corrected):**
|
|
145
|
+
- ❌ I incorrectly claimed Button `iconLeftName`/`iconRightName` props don't exist - THEY DO EXIST (Button.tsx). User caught this error. These props are real and should be used.
|
|
146
|
+
- ❌ I claimed CheckboxInput was NOT exported — WRONG. CheckboxInput IS exported (index.ts line 24, confirmed 2026-04-17).
|
|
147
|
+
- ❌ I claimed Badge does NOT exist — WRONG. Badge IS exported (index.ts line 20, confirmed 2026-04-17) with BadgeColour/BadgeProps/BadgeSize types.
|
|
148
|
+
|
|
149
|
+
## Table Cell Renderers (verified 2026-02-19)
|
|
150
|
+
- All three registered in Table.tsx `components` prop by string name:
|
|
151
|
+
- `dsInlineTextCellRenderer` -> InlineTextCellRenderer (renders span with value)
|
|
152
|
+
- `dsSelectDropdownCellRenderer` -> SelectDropdownCellRenderer (renders dropdown Button, paired with dsSelectDropdownCellEditor)
|
|
153
|
+
- `dsButtonCellRenderer` -> ButtonCellRenderer (spreads ButtonProps from value, supports iconLeftName/iconRightName for icon buttons)
|
|
154
|
+
- Only `Table.ButtonCellRenderer` is exposed as a static property; others used via string names in colDef
|
|
155
|
+
- `Table.DefaultValueFormatter` also exposed as static property
|
|
156
|
+
|
|
157
|
+
## Table Themes (verified 2026-02-19)
|
|
158
|
+
- `tidyTheme`: activated via `tableTheme="tidy"` prop. White bg, no borders, clean look. Full example in TidyTable story.
|
|
159
|
+
- `defaultTheme`: the standard theme, supports hasColumnBorders and tableSpacing settings
|
|
160
|
+
- TidyTable story demonstrates: treeData + treeDataChildrenField + inline editing + all cell renderers together
|
|
161
|
+
|
|
162
|
+
## Section Component (verified 2026-02-19)
|
|
163
|
+
- `title` prop is typed as `string` (NOT ReactNode) -- cannot directly embed JSX like Pill
|
|
164
|
+
- Chevron icon IS auto-added when `collapsible={true}` (chevron-down/chevron-up with aria-expanded)
|
|
165
|
+
- `collapsed` prop sets initial state (inverted from defaultExpanded)
|
|
166
|
+
- `buttonText`/`buttonOnClick` for optional action button in heading
|
|
167
|
+
- `titleIconName`/`titleIconColor`/`titleIconScreenReaderText` for icon next to title
|
|
168
|
+
|
|
169
|
+
## Sophia Accuracy Trend
|
|
170
|
+
- 2026-02-19 (earlier): Multiple hallucinations (ghost variant, defaultExpanded, headerContent on Section, grip-vertical icon)
|
|
171
|
+
- 2026-02-19 (Student Profile analysis): 100% accuracy on all prop claims across 8 components. Zero hallucinations. Only omissions were Dropdown.SelectItem, some Button variants, and SearchBar.placeholderText -- all editorial choices, not errors.
|
|
172
|
+
- 2026-02-19 (Migration mapping): 14/14 claims verified. Zero hallucinations. Correct on: Heading.InnerContainer, Button type Omit, Tabs semantic HTML, BulkActionsDropdown actions.length (not selected rows), both '3-dot' and 'ellipsis-vertical' icon aliases, setAgGridLicenseKey auto-call, all Table sub-component props. Minor omissions: Tabs.Item link mode, headerContent gatekeeper for search.
|
|
173
|
+
- TooltipWrapper: triggerProps.asChild is technically valid but redundant -- TooltipTrigger already hardcodes asChild={true}
|
|
174
|
+
|
|
175
|
+
## Icon Alias Gotcha (verified 2026-02-19)
|
|
176
|
+
- `'3-dot'` and `'ellipsis-vertical'` both map to Lucide `EllipsisVertical` -- they are interchangeable aliases
|
|
177
|
+
- `'grab'` maps to Lucide `GripVertical` (NOT `'grip-vertical'`)
|
|
178
|
+
- Prefer Lucide-style naming ('ellipsis-vertical') over legacy aliases ('3-dot') for consistency
|
|
179
|
+
|
|
180
|
+
## Table Search Gatekeeper (verified 2026-02-19)
|
|
181
|
+
- SearchBar in Table only renders when BOTH `headerContent` is truthy AND `hasSearch` is true (default)
|
|
182
|
+
- `headerContent` controls whether `<TableHeader>` renders at all (Table.tsx line 149)
|
|
183
|
+
- If `headerContent` is falsy, `hasSearch={true}` does nothing -- no search bar appears
|
|
184
|
+
- `quickFilterText` is wired to search state (Table.tsx line 196)
|
|
185
|
+
|
|
186
|
+
## Export Status (verified 2026-04-17 against src/index.ts)
|
|
187
|
+
- SearchBar: exported (line 40) ✅
|
|
188
|
+
- CheckboxInput: exported (line 24) ✅
|
|
189
|
+
- CheckboxGroup: NOT exported — internal only ✅
|
|
190
|
+
- RadioButtonGroup: NOT exported — internal only ✅
|
|
191
|
+
- BooleanCellRenderer: exported (line 47) ✅ — also registered as `'dsBooleanCellRenderer'` in Table
|
|
192
|
+
- New exports added recently: Toggle, Row, SingleUser, Combobox (+ types), TimeInput (+ types), DatePicker, DateTimePicker (+ types)
|
|
193
|
+
|
|
194
|
+
## Pill Component API
|
|
195
|
+
- `onclick` prop (lowercase c) accepts `(checked: boolean) => void`
|
|
196
|
+
- Uses `useEffect` to fire onclick on checked state change, including on mount
|
|
197
|
+
- State setters from `useState` are stable references, so passing them directly as onclick is safe
|
|
198
|
+
|
|
199
|
+
## Test Quality Benchmarks
|
|
200
|
+
- Tests should use accessible queries (getByRole, getByLabelText) over getByText/getByTestId
|
|
201
|
+
- userEvent is preferred over fireEvent for user interactions
|
|
202
|
+
- Tests should verify callback arguments, not just that callbacks were called
|
|
203
|
+
- Edge cases (empty state, no data) should be covered
|
|
204
|
+
|
|
205
|
+
## NumberInput Component (verified 2026-04-23) -- see [numberinput_component.md](numberinput_component.md)
|
|
206
|
+
- CRITICAL BUG IN STORIES: onBlur claim is INVERTED — `{...rest}` comes AFTER `onBlur={handleOnBlur}` so consumer's onBlur WINS (story says opposite)
|
|
207
|
+
- onChange fires from useEffect with manually constructed event — only `e.currentTarget.value` populated (correct in stories)
|
|
208
|
+
- `defaultValue` guard `if (!defaultValue)` blocks value prop sync when truthy (correct in stories)
|
|
209
|
+
- No forwardRef (correct in stories)
|
|
210
|
+
- Story title `Components/FormField/Inputs/Numeric` follows convention (Checkbox, RadioButton, etc.)
|
|
211
|
+
|
|
212
|
+
## Storybook Docs Link Slugs (verified 2026-04-17)
|
|
213
|
+
- Storybook slugifies the full `title` path, so `Components/FormField/Inputs/Checkbox` becomes `components-formfield-inputs-checkbox--docs`
|
|
214
|
+
- CheckboxInput's actual story title is `Components/FormField/Inputs/Checkbox` (NOT `CheckboxInput`) — link must be `?path=/docs/components-formfield-inputs-checkbox--docs`
|
|
215
|
+
- Common mistake: Rose wrote `components-formfield-inputs-checkboxinput--docs` in Button.stories.tsx Related components section — broken link
|
|
216
|
+
- When fact-checking docs links, always verify the actual `title:` in the target story file, not just the component name
|
|
217
|
+
|
|
218
|
+
## Recurring Bugs in Generated Pages (confirmed 2026-02-19)
|
|
219
|
+
- **Icon name `'edit'` does NOT exist** in allowedIcons.tsx -- correct name is `'pencil'`
|
|
220
|
+
- **CSS ds- prefix mismatch**: SCSS uses `.ds-{page-name}` but component JSX omits the `ds-` prefix. Always verify SCSS selectors match JSX classNames
|
|
221
|
+
- **Direct SCSS import in page components**: Pages in `src/pages/` should NOT `import './foo.scss'` directly (causes TS2307). Pages in `src/examples/pages/` CAN use direct imports because Storybook/Vitest handle them fine (global.d.ts declares `*.scss` modules). The `src/examples/` path is for example/demo pages that are NOT part of the library build
|
|
222
|
+
- **Pill renders as `<span>`, NOT `<button>`**: Tests that use `getByRole('button')` to find Pills will fail. Use `getByText` instead
|
|
223
|
+
- **SearchBar inactive state**: SearchBar with empty `searchValue` renders as a collapsed Button, not an input. Tests must click to activate it first, or initialize with non-empty searchValue
|
|
224
|
+
- **TS error downplaying**: Agents may call real TS compilation errors "warnings" or "expected" -- always verify exit code of `yarn check-types`
|
|
225
|
+
- **Page registration still forgotten**: AssessmentBuilderPage was missing from both index.ts and index.scss (same pattern as before)
|
|
226
|
+
- **`@stylistic/jsx-one-expression-per-line` is the #1 ESLint violation**: `<br />` tags inline with text content always trigger this. 12 of 13 ESLint errors in StudentProfilePage were this exact rule. The `--fix` flag can auto-resolve these
|
|
227
|
+
- **Unused imports in test files**: Agents import `beforeEach` from vitest but never use it (1 of 13 errors in StudentProfilePage)
|
|
228
|
+
- **`src/examples/pages/` is a separate convention** from `src/pages/`: example pages are NOT library exports and do NOT need registration in index.ts/index.scss. Direct SCSS imports are fine here
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: numberinput_component
|
|
3
|
+
description: NumberInput component API, behavioral gotchas, and verified story accuracy (2026-04-23)
|
|
4
|
+
type: project
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## NumberInput Component (verified 2026-04-23)
|
|
8
|
+
|
|
9
|
+
### Props (NumberInputProps)
|
|
10
|
+
- Custom: `hasError?: boolean`, `disableSpinners?: boolean`, `containerClassName?: string`
|
|
11
|
+
- Extends `InputHTMLAttributes<HTMLInputElement>` — so `className`, `disabled`, `step`, `min`, `max`, `value`, `defaultValue`, `onChange`, `placeholder`, `onBlur` etc. all come from HTML attributes
|
|
12
|
+
- Exported type: `NumberInput.Props` via TypeScript namespace (NumberInput.tsx:135-137)
|
|
13
|
+
- No `forwardRef` — component does not expose a ref
|
|
14
|
+
|
|
15
|
+
### Internal State Model
|
|
16
|
+
- Internal `value` state initialized from `defaultValue.toString() || passedValue.toString()`
|
|
17
|
+
- `defaultValue` defaults to `''` (falsy), `passedValue` (value prop) defaults to `''`
|
|
18
|
+
- useEffect guard: `if (!defaultValue) { setValue(passedValue.toString()) }` — means truthy defaultValue blocks value prop syncing
|
|
19
|
+
- Edge case: `defaultValue={0}` is falsy, so the guard passes and the component acts controlled despite having a "defaultValue"
|
|
20
|
+
|
|
21
|
+
### onChange Behavior
|
|
22
|
+
- Consumer's `onChange` is called from a useEffect watching internal `value` state (NOT from the native input event)
|
|
23
|
+
- Event is manually constructed: `{ currentTarget: { value } } as ChangeEvent<HTMLInputElement>`
|
|
24
|
+
- Only `e.currentTarget.value` is populated; `e.target` is undefined (accessing `.value` on it throws TypeError)
|
|
25
|
+
|
|
26
|
+
### CRITICAL: onBlur Prop Ordering Bug
|
|
27
|
+
- JSX order: `onBlur={handleOnBlur}` at line 111, then `{...rest}` at line 115
|
|
28
|
+
- `onBlur` is NOT destructured from props — it stays in `...rest`
|
|
29
|
+
- In React, LATER props override earlier ones — so consumer's `onBlur` in `{...rest}` OVERRIDES internal `handleOnBlur`
|
|
30
|
+
- **The story documentation claims the OPPOSITE** (that internal handler wins) — THIS IS WRONG
|
|
31
|
+
- Actual behavior: consumer's onBlur WINS, internal blur-clamping is SILENTLY LOST
|
|
32
|
+
- NOTE: `onChange` IS destructured (line 17), so it does NOT end up in `...rest` — no conflict there
|
|
33
|
+
|
|
34
|
+
### Spinner Buttons
|
|
35
|
+
- aria-labels: "Minus button" and "Plus button" (hardcoded, not customisable)
|
|
36
|
+
- Minus disabled when `Number(value) <= Number(min)`
|
|
37
|
+
- Plus disabled when `Number(value) >= Number(max)`
|
|
38
|
+
- Both disabled when `disabled` prop is set
|
|
39
|
+
- Uses Decimal.js for arithmetic precision
|
|
40
|
+
|
|
41
|
+
### CSS Classes
|
|
42
|
+
- Container: `ds-number-input__container`
|
|
43
|
+
- Error modifier: `ds-number-input__container--error`
|
|
44
|
+
- Disabled modifier: `ds-number-input__container--disabled`
|
|
45
|
+
- Input: `ds-input ds-number-input` (plus consumer's className)
|
|
46
|
+
- Spinner buttons: `ds-number-input__spinner-button`
|
|
47
|
+
|
|
48
|
+
### Story Title Convention
|
|
49
|
+
- Title: `Components/FormField/Inputs/Numeric` — uses "Numeric" not "NumberInput" (editorial choice, matches pattern where some use shortened names e.g. "Checkbox" not "CheckboxInput")
|
|
50
|
+
|
|
51
|
+
**Why:** NumberInput has a subtle and dangerous onBlur bug that the story documentation gets backwards. Future fact-checks of this component or its stories should always verify the prop ordering claim.
|
|
52
|
+
|
|
53
|
+
**How to apply:** When reviewing any NumberInput-related changes, always check whether the onBlur prop ordering documentation has been corrected. If not, flag it.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Progress Component Mechanics
|
|
3
|
+
description: Radix Progress behaviour quirks — default getValueLabel returns "X%" not raw number, value=null produces invisible bar in this wrapper
|
|
4
|
+
type: project
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Progress Component (verified 2026-04-17)
|
|
8
|
+
|
|
9
|
+
## Exports & API
|
|
10
|
+
- Exported at `src/index.ts:39` (just `Progress`, no type exports)
|
|
11
|
+
- `ProgressProps = RadixProgress.ProgressProps & { indicatorClassName?: string }` (Progress.tsx:4-6)
|
|
12
|
+
- Radix `ProgressProps` (from `@radix-ui/react-progress/dist/index.d.ts:7-11`) extends `PrimitiveDivProps` with `value?: number | null`, `max?: number`, `getValueLabel?(value, max): string`
|
|
13
|
+
- Since it spreads `PrimitiveDivProps`, ALL `aria-*` attrs, `className`, `style`, etc. flow through — including `aria-label` and `aria-labelledby`
|
|
14
|
+
|
|
15
|
+
## Critical Radix Default getValueLabel Behaviour
|
|
16
|
+
Radix's default `getValueLabel` returns `"${Math.round(value/max*100)}%"` — a PERCENTAGE STRING WITH `%` SIGN.
|
|
17
|
+
**Why:** Rose in Progress.stories.tsx wrote "Without getValueLabel, screen readers announce the raw number" in 5 places (lines 56, 127, 132, 306, 480). That's wrong — the default is "65%", not "65".
|
|
18
|
+
**How to apply:** When fact-checking any Progress story/docs, verify claims about default `aria-valuetext` output. The correct framing is "Radix's default is `X%`; custom `getValueLabel` lets you produce a full sentence instead."
|
|
19
|
+
|
|
20
|
+
## The Indeterminate Anti-Pattern (real, not a hallucination)
|
|
21
|
+
- `value={null}` is technically valid per Radix types
|
|
22
|
+
- In our wrapper: `Progress.tsx:9` `value = 0` default ONLY fires for `undefined`, not `null`
|
|
23
|
+
- `Progress.tsx:10`: `Number(null) = 0` → percentage = 0 → `translateX(-100%)` → indicator off-screen
|
|
24
|
+
- Radix DOES set `data-state="indeterminate"` on root (per `@radix-ui/react-progress/dist/index.mjs` `getProgressState`)
|
|
25
|
+
- BUT `progress.scss:1-13` has NO `[data-state="indeterminate"]` rules — no animation defined
|
|
26
|
+
- Net result: value=null renders an invisible bar. Rose's IndeterminateAntiPattern story correctly flags this.
|
|
27
|
+
- Consumers needing indeterminate UX should use a Spinner or pass custom `indicatorClassName` with keyframes targeting `[data-state="indeterminate"]`
|
|
28
|
+
|
|
29
|
+
## Radix Silently Coerces Out-of-Range value
|
|
30
|
+
At `index.mjs:25-27`: passing `value={150}` with `max={100}` triggers a `console.error` AND coerces `value` to `null` internally (so `aria-valuenow` is omitted). Wrapper still does its own math on the raw `value` prop though, so `Number(150)/100*100 = 150` → `translateX(50%)` (bar overflows past 100%). Silent divergence — worth mentioning in docs.
|
|
31
|
+
|
|
32
|
+
## Progress Styling Facts
|
|
33
|
+
- `progress.scss`: root uses `--border-radius-large` (16px, tokens.scss:196), NOT `--border-radius-small`
|
|
34
|
+
- Height is `var(--spacing-small)` (8px)
|
|
35
|
+
- Root bg: `--color-grey-100`, indicator bg: `--color-brand-600`
|
|
36
|
+
- Both root and indicator have `border-radius: var(--border-radius-large)`
|