@dashforge/tw 0.1.0-beta → 0.2.1-beta

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.
Files changed (195) hide show
  1. package/A11Y.md +130 -0
  2. package/CHANGELOG.md +231 -0
  3. package/dist/index.d.ts +1 -82
  4. package/dist/index.esm.js +1608 -11
  5. package/dist/src/components/AspectRatio/AspectRatio.d.ts +36 -0
  6. package/dist/src/components/AspectRatio/AspectRatio.d.ts.map +1 -0
  7. package/dist/src/components/AspectRatio/aspectRatio.types.d.ts +50 -0
  8. package/dist/src/components/AspectRatio/aspectRatio.types.d.ts.map +1 -0
  9. package/dist/src/components/Box/Box.d.ts +36 -0
  10. package/dist/src/components/Box/Box.d.ts.map +1 -0
  11. package/dist/src/components/Box/box.types.d.ts +54 -0
  12. package/dist/src/components/Box/box.types.d.ts.map +1 -0
  13. package/dist/src/components/Box/box.variants.d.ts +402 -0
  14. package/dist/src/components/Box/box.variants.d.ts.map +1 -0
  15. package/dist/src/components/Button/Button.d.ts.map +1 -1
  16. package/dist/src/components/Checkbox/Checkbox.d.ts.map +1 -1
  17. package/dist/src/components/Container/Container.d.ts +35 -0
  18. package/dist/src/components/Container/Container.d.ts.map +1 -0
  19. package/dist/src/components/Container/container.types.d.ts +41 -0
  20. package/dist/src/components/Container/container.types.d.ts.map +1 -0
  21. package/dist/src/components/Container/container.variants.d.ts +93 -0
  22. package/dist/src/components/Container/container.variants.d.ts.map +1 -0
  23. package/dist/src/components/Divider/Divider.d.ts +50 -0
  24. package/dist/src/components/Divider/Divider.d.ts.map +1 -0
  25. package/dist/src/components/Divider/divider.types.d.ts +47 -0
  26. package/dist/src/components/Divider/divider.types.d.ts.map +1 -0
  27. package/dist/src/components/Divider/divider.variants.d.ts +129 -0
  28. package/dist/src/components/Divider/divider.variants.d.ts.map +1 -0
  29. package/dist/src/components/Grid/Grid.d.ts +32 -0
  30. package/dist/src/components/Grid/Grid.d.ts.map +1 -0
  31. package/dist/src/components/Grid/grid.types.d.ts +103 -0
  32. package/dist/src/components/Grid/grid.types.d.ts.map +1 -0
  33. package/dist/src/components/Grid/grid.variants.d.ts +453 -0
  34. package/dist/src/components/Grid/grid.variants.d.ts.map +1 -0
  35. package/dist/src/components/NumberField/NumberField.d.ts.map +1 -1
  36. package/dist/src/components/RadioGroup/RadioGroup.d.ts.map +1 -1
  37. package/dist/src/components/Stack/Stack.d.ts +24 -0
  38. package/dist/src/components/Stack/Stack.d.ts.map +1 -0
  39. package/dist/src/components/Stack/stack.types.d.ts +61 -0
  40. package/dist/src/components/Stack/stack.types.d.ts.map +1 -0
  41. package/dist/src/components/Stack/stack.variants.d.ts +163 -0
  42. package/dist/src/components/Stack/stack.variants.d.ts.map +1 -0
  43. package/dist/src/components/Typography/Typography.d.ts +42 -0
  44. package/dist/src/components/Typography/Typography.d.ts.map +1 -0
  45. package/dist/src/components/Typography/typography.types.d.ts +61 -0
  46. package/dist/src/components/Typography/typography.types.d.ts.map +1 -0
  47. package/dist/src/components/Typography/typography.variants.d.ts +167 -0
  48. package/dist/src/components/Typography/typography.variants.d.ts.map +1 -0
  49. package/dist/src/components/VisuallyHidden/VisuallyHidden.d.ts +42 -0
  50. package/dist/src/components/VisuallyHidden/VisuallyHidden.d.ts.map +1 -0
  51. package/dist/src/components/VisuallyHidden/visuallyHidden.types.d.ts +42 -0
  52. package/dist/src/components/VisuallyHidden/visuallyHidden.types.d.ts.map +1 -0
  53. package/dist/src/index.d.ts +23 -1
  54. package/dist/src/index.d.ts.map +1 -1
  55. package/package.json +3 -3
  56. package/src/components/AspectRatio/AspectRatio.test.tsx +173 -0
  57. package/src/components/AspectRatio/AspectRatio.tsx +73 -0
  58. package/src/components/AspectRatio/aspectRatio.types.ts +54 -0
  59. package/src/components/Box/Box.test.tsx +349 -0
  60. package/src/components/Box/Box.tsx +83 -0
  61. package/src/components/Box/box.types.ts +61 -0
  62. package/src/components/Box/box.variants.ts +211 -0
  63. package/src/components/Button/Button.tsx +11 -0
  64. package/src/components/Checkbox/Checkbox.tsx +26 -2
  65. package/src/components/Container/Container.test.tsx +209 -0
  66. package/src/components/Container/Container.tsx +74 -0
  67. package/src/components/Container/container.types.ts +45 -0
  68. package/src/components/Container/container.variants.ts +81 -0
  69. package/src/components/Divider/Divider.test.tsx +241 -0
  70. package/src/components/Divider/Divider.tsx +140 -0
  71. package/src/components/Divider/divider.types.ts +52 -0
  72. package/src/components/Divider/divider.variants.ts +115 -0
  73. package/src/components/Grid/Grid.test.tsx +321 -0
  74. package/src/components/Grid/Grid.tsx +148 -0
  75. package/src/components/Grid/grid.types.ts +112 -0
  76. package/src/components/Grid/grid.variants.ts +133 -0
  77. package/src/components/NumberField/NumberField.tsx +30 -2
  78. package/src/components/RadioGroup/RadioGroup.tsx +23 -1
  79. package/src/components/Stack/Stack.test.tsx +309 -0
  80. package/src/components/Stack/Stack.tsx +115 -0
  81. package/src/components/Stack/stack.types.ts +68 -0
  82. package/src/components/Stack/stack.variants.ts +80 -0
  83. package/src/components/Typography/Typography.test.tsx +230 -0
  84. package/src/components/Typography/Typography.tsx +112 -0
  85. package/src/components/Typography/typography.types.ts +65 -0
  86. package/src/components/Typography/typography.variants.ts +113 -0
  87. package/src/components/VisuallyHidden/VisuallyHidden.test.tsx +86 -0
  88. package/src/components/VisuallyHidden/VisuallyHidden.tsx +59 -0
  89. package/src/components/VisuallyHidden/visuallyHidden.types.ts +45 -0
  90. package/src/index.ts +50 -1
  91. package/LICENSE +0 -21
  92. package/dist/components/AppShell/AppShell.d.ts +0 -32
  93. package/dist/components/AppShell/AppShell.d.ts.map +0 -1
  94. package/dist/components/AppShell/appShell.types.d.ts +0 -62
  95. package/dist/components/AppShell/appShell.types.d.ts.map +0 -1
  96. package/dist/components/AppShell/appShell.variants.d.ts +0 -65
  97. package/dist/components/AppShell/appShell.variants.d.ts.map +0 -1
  98. package/dist/components/Autocomplete/Autocomplete.d.ts +0 -32
  99. package/dist/components/Autocomplete/Autocomplete.d.ts.map +0 -1
  100. package/dist/components/Autocomplete/autocomplete.types.d.ts +0 -214
  101. package/dist/components/Autocomplete/autocomplete.types.d.ts.map +0 -1
  102. package/dist/components/Autocomplete/autocomplete.variants.d.ts +0 -214
  103. package/dist/components/Autocomplete/autocomplete.variants.d.ts.map +0 -1
  104. package/dist/components/Breadcrumbs/Breadcrumbs.d.ts +0 -23
  105. package/dist/components/Breadcrumbs/Breadcrumbs.d.ts.map +0 -1
  106. package/dist/components/Breadcrumbs/breadcrumbs.types.d.ts +0 -98
  107. package/dist/components/Breadcrumbs/breadcrumbs.types.d.ts.map +0 -1
  108. package/dist/components/Breadcrumbs/breadcrumbs.variants.d.ts +0 -85
  109. package/dist/components/Breadcrumbs/breadcrumbs.variants.d.ts.map +0 -1
  110. package/dist/components/Button/Button.d.ts +0 -44
  111. package/dist/components/Button/Button.d.ts.map +0 -1
  112. package/dist/components/Button/button.types.d.ts +0 -66
  113. package/dist/components/Button/button.types.d.ts.map +0 -1
  114. package/dist/components/Button/button.variants.d.ts +0 -104
  115. package/dist/components/Button/button.variants.d.ts.map +0 -1
  116. package/dist/components/Checkbox/Checkbox.d.ts +0 -31
  117. package/dist/components/Checkbox/Checkbox.d.ts.map +0 -1
  118. package/dist/components/Checkbox/checkbox.types.d.ts +0 -86
  119. package/dist/components/Checkbox/checkbox.types.d.ts.map +0 -1
  120. package/dist/components/Checkbox/checkbox.variants.d.ts +0 -109
  121. package/dist/components/Checkbox/checkbox.variants.d.ts.map +0 -1
  122. package/dist/components/ConfirmDialog/ConfirmDialog.d.ts +0 -38
  123. package/dist/components/ConfirmDialog/ConfirmDialog.d.ts.map +0 -1
  124. package/dist/components/ConfirmDialog/confirmDialog.types.d.ts +0 -80
  125. package/dist/components/ConfirmDialog/confirmDialog.types.d.ts.map +0 -1
  126. package/dist/components/ConfirmDialog/confirmDialog.variants.d.ts +0 -90
  127. package/dist/components/ConfirmDialog/confirmDialog.variants.d.ts.map +0 -1
  128. package/dist/components/DateTimePicker/DateTimePicker.d.ts +0 -49
  129. package/dist/components/DateTimePicker/DateTimePicker.d.ts.map +0 -1
  130. package/dist/components/DateTimePicker/dateTimePicker.types.d.ts +0 -95
  131. package/dist/components/DateTimePicker/dateTimePicker.types.d.ts.map +0 -1
  132. package/dist/components/DateTimePicker/dateTimePicker.variants.d.ts +0 -165
  133. package/dist/components/DateTimePicker/dateTimePicker.variants.d.ts.map +0 -1
  134. package/dist/components/LeftNav/LeftNav.d.ts +0 -34
  135. package/dist/components/LeftNav/LeftNav.d.ts.map +0 -1
  136. package/dist/components/LeftNav/leftNav.types.d.ts +0 -136
  137. package/dist/components/LeftNav/leftNav.types.d.ts.map +0 -1
  138. package/dist/components/LeftNav/leftNav.variants.d.ts +0 -143
  139. package/dist/components/LeftNav/leftNav.variants.d.ts.map +0 -1
  140. package/dist/components/NumberField/NumberField.d.ts +0 -18
  141. package/dist/components/NumberField/NumberField.d.ts.map +0 -1
  142. package/dist/components/NumberField/numberField.types.d.ts +0 -79
  143. package/dist/components/NumberField/numberField.types.d.ts.map +0 -1
  144. package/dist/components/NumberField/numberField.variants.d.ts +0 -169
  145. package/dist/components/NumberField/numberField.variants.d.ts.map +0 -1
  146. package/dist/components/OTPField/OTPField.d.ts +0 -25
  147. package/dist/components/OTPField/OTPField.d.ts.map +0 -1
  148. package/dist/components/OTPField/otpField.types.d.ts +0 -74
  149. package/dist/components/OTPField/otpField.types.d.ts.map +0 -1
  150. package/dist/components/OTPField/otpField.variants.d.ts +0 -105
  151. package/dist/components/OTPField/otpField.variants.d.ts.map +0 -1
  152. package/dist/components/RadioGroup/RadioGroup.d.ts +0 -35
  153. package/dist/components/RadioGroup/RadioGroup.d.ts.map +0 -1
  154. package/dist/components/RadioGroup/radioGroup.types.d.ts +0 -103
  155. package/dist/components/RadioGroup/radioGroup.types.d.ts.map +0 -1
  156. package/dist/components/RadioGroup/radioGroup.variants.d.ts +0 -166
  157. package/dist/components/RadioGroup/radioGroup.variants.d.ts.map +0 -1
  158. package/dist/components/Snackbar/Snackbar.d.ts +0 -37
  159. package/dist/components/Snackbar/Snackbar.d.ts.map +0 -1
  160. package/dist/components/Snackbar/snackbar.types.d.ts +0 -86
  161. package/dist/components/Snackbar/snackbar.types.d.ts.map +0 -1
  162. package/dist/components/Snackbar/snackbar.variants.d.ts +0 -153
  163. package/dist/components/Snackbar/snackbar.variants.d.ts.map +0 -1
  164. package/dist/components/Switch/Switch.d.ts +0 -15
  165. package/dist/components/Switch/Switch.d.ts.map +0 -1
  166. package/dist/components/Switch/switch.types.d.ts +0 -47
  167. package/dist/components/Switch/switch.types.d.ts.map +0 -1
  168. package/dist/components/Switch/switch.variants.d.ts +0 -105
  169. package/dist/components/Switch/switch.variants.d.ts.map +0 -1
  170. package/dist/components/TextField/TextField.d.ts +0 -25
  171. package/dist/components/TextField/TextField.d.ts.map +0 -1
  172. package/dist/components/TextField/textField.types.d.ts +0 -72
  173. package/dist/components/TextField/textField.types.d.ts.map +0 -1
  174. package/dist/components/TextField/textField.variants.d.ts +0 -160
  175. package/dist/components/TextField/textField.variants.d.ts.map +0 -1
  176. package/dist/components/Textarea/Textarea.d.ts +0 -16
  177. package/dist/components/Textarea/Textarea.d.ts.map +0 -1
  178. package/dist/components/Textarea/textarea.types.d.ts +0 -63
  179. package/dist/components/Textarea/textarea.types.d.ts.map +0 -1
  180. package/dist/components/Textarea/textarea.variants.d.ts +0 -197
  181. package/dist/components/Textarea/textarea.variants.d.ts.map +0 -1
  182. package/dist/components/TopBar/TopBar.d.ts +0 -33
  183. package/dist/components/TopBar/TopBar.d.ts.map +0 -1
  184. package/dist/components/TopBar/topBar.types.d.ts +0 -49
  185. package/dist/components/TopBar/topBar.types.d.ts.map +0 -1
  186. package/dist/components/TopBar/topBar.variants.d.ts +0 -79
  187. package/dist/components/TopBar/topBar.variants.d.ts.map +0 -1
  188. package/dist/components/_shared/resolveValidationState.d.ts +0 -42
  189. package/dist/components/_shared/resolveValidationState.d.ts.map +0 -1
  190. package/dist/hooks/useAccessState.d.ts +0 -37
  191. package/dist/hooks/useAccessState.d.ts.map +0 -1
  192. package/dist/index.d.ts.map +0 -1
  193. package/dist/tsconfig.lib.tsbuildinfo +0 -1
  194. package/dist/utils/cn.d.ts +0 -26
  195. package/dist/utils/cn.d.ts.map +0 -1
@@ -0,0 +1,113 @@
1
+ import { tv, type VariantProps } from 'tailwind-variants';
2
+
3
+ /**
4
+ * `typographyVariants` — the full type scale for the @dashforge/tw library.
5
+ *
6
+ * Mirrors the MUI Typography variant set (h1–h6 · subtitle1/2 · body1/2 ·
7
+ * caption · overline) so the mental model carries over for developers
8
+ * moving between the two ecosystems. Each variant baseline maps to a
9
+ * Tailwind utility chain that resolves through the @dashforge/tw-tokens
10
+ * scale (so the visual stays in sync with the rest of the system when the
11
+ * token theme is patched).
12
+ *
13
+ * Variant axes are intentionally ORTHOGONAL — `variant` chooses the type
14
+ * scale, `weight` overrides the variant's default weight (useful for "h2
15
+ * but lighter"), `color` picks the intent, `align` picks the axis. They
16
+ * never collide, so consumers can mix them freely.
17
+ *
18
+ * Two boolean flags (`truncate`, `noWrap`) encode the most common one-line
19
+ * patterns; `gutterBottom` adds the conventional bottom margin used when a
20
+ * heading precedes a paragraph block (mirror of MUI's same flag).
21
+ */
22
+ export const typographyVariants = tv({
23
+ base: 'text-inherit',
24
+
25
+ variants: {
26
+ /*
27
+ * `variant` is the type-scale axis.
28
+ *
29
+ * The default font-weight is baked into each variant (headings come
30
+ * with semibold/bold by default). Consumers override per-instance via
31
+ * the `weight` axis below — when set, `weight` wins because it's
32
+ * declared later in the cn() chain and tailwind-merge resolves the
33
+ * last `font-*` to win.
34
+ */
35
+ variant: {
36
+ h1: 'text-5xl font-bold leading-[1.05] tracking-[-0.025em]',
37
+ h2: 'text-4xl font-bold leading-[1.1] tracking-[-0.022em]',
38
+ h3: 'text-3xl font-semibold leading-[1.15] tracking-[-0.02em]',
39
+ h4: 'text-2xl font-semibold leading-snug tracking-[-0.015em]',
40
+ h5: 'text-xl font-semibold leading-snug',
41
+ h6: 'text-lg font-semibold leading-normal',
42
+ subtitle1: 'text-base font-medium leading-relaxed',
43
+ subtitle2: 'text-sm font-medium leading-relaxed',
44
+ body1: 'text-base font-normal leading-relaxed',
45
+ body2: 'text-sm font-normal leading-relaxed',
46
+ caption: 'text-xs font-normal leading-normal',
47
+ overline: 'text-xs font-semibold uppercase tracking-[0.12em] leading-normal',
48
+ },
49
+
50
+ /*
51
+ * `color` is the intent axis. Pairs with the @dashforge/tw-theme
52
+ * reactive colour vars so the choice survives theme patches and dark
53
+ * mode flips. `inherit` is the escape hatch — used inside a Box that
54
+ * has set its own color (e.g. `<Box variant="solid" color="primary">`
55
+ * paints white text).
56
+ */
57
+ color: {
58
+ inherit: 'text-inherit',
59
+ primary: 'text-primary-700 dark:text-primary-400',
60
+ secondary: 'text-secondary-700 dark:text-secondary-400',
61
+ success: 'text-success-700 dark:text-success-400',
62
+ warning: 'text-warning-700 dark:text-warning-400',
63
+ danger: 'text-danger-700 dark:text-danger-400',
64
+ info: 'text-info-700 dark:text-info-400',
65
+ neutral: 'text-neutral-900 dark:text-neutral-100',
66
+ muted: 'text-neutral-600 dark:text-neutral-400',
67
+ },
68
+
69
+ /*
70
+ * `weight` overrides the variant's default weight. When unset, the
71
+ * variant's own weight wins. When set, this axis appears LATER in the
72
+ * cn() chain so tailwind-merge resolves to this value.
73
+ */
74
+ weight: {
75
+ normal: 'font-normal',
76
+ medium: 'font-medium',
77
+ semibold: 'font-semibold',
78
+ bold: 'font-bold',
79
+ extrabold: 'font-extrabold',
80
+ },
81
+
82
+ align: {
83
+ left: 'text-left',
84
+ center: 'text-center',
85
+ right: 'text-right',
86
+ justify: 'text-justify',
87
+ },
88
+
89
+ /*
90
+ * `truncate` collapses to a one-line ellipsis. `noWrap` is the looser
91
+ * sibling — keeps the text on one line but lets it overflow without
92
+ * the `…`. Mutually-exclusive intent-wise; if both are passed,
93
+ * `truncate` wins (later in the cn() chain).
94
+ */
95
+ truncate: { true: 'truncate' },
96
+ noWrap: { true: 'whitespace-nowrap' },
97
+
98
+ /*
99
+ * `gutterBottom` adds the conventional bottom margin used when a
100
+ * heading precedes a paragraph block. Mirror of MUI's same prop —
101
+ * familiar to developers crossing from the MUI side.
102
+ */
103
+ gutterBottom: { true: 'mb-3' },
104
+ },
105
+
106
+ defaultVariants: {
107
+ variant: 'body1',
108
+ color: 'inherit',
109
+ align: 'left',
110
+ },
111
+ });
112
+
113
+ export type TypographyVariants = VariantProps<typeof typographyVariants>;
@@ -0,0 +1,86 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, it, expect } from 'vitest';
3
+ import { render } from '@testing-library/react';
4
+ import { VisuallyHidden } from './VisuallyHidden.js';
5
+
6
+ describe('<VisuallyHidden>', () => {
7
+ it('renders a <span> by default', () => {
8
+ const { container } = render(<VisuallyHidden>label</VisuallyHidden>);
9
+ expect(container.firstElementChild?.tagName).toBe('SPAN');
10
+ });
11
+
12
+ it('has the sr-only class', () => {
13
+ const { container } = render(<VisuallyHidden>label</VisuallyHidden>);
14
+ expect(container.firstElementChild?.className).toContain('sr-only');
15
+ });
16
+
17
+ it('preserves the text content (so screen readers can read it)', () => {
18
+ const { container } = render(<VisuallyHidden>Close dialog</VisuallyHidden>);
19
+ expect(container.firstElementChild?.textContent).toBe('Close dialog');
20
+ });
21
+
22
+ it('as="div" renders <div>', () => {
23
+ const { container } = render(<VisuallyHidden as="div">label</VisuallyHidden>);
24
+ expect(container.firstElementChild?.tagName).toBe('DIV');
25
+ });
26
+
27
+ it('sx adds utilities (merged with sr-only)', () => {
28
+ const { container } = render(
29
+ <VisuallyHidden sx="text-xs">label</VisuallyHidden>,
30
+ );
31
+ const cls = container.firstElementChild?.className ?? '';
32
+ expect(cls).toContain('sr-only');
33
+ expect(cls).toContain('text-xs');
34
+ });
35
+
36
+ it('forwards data-* + aria-*', () => {
37
+ const { container } = render(
38
+ <VisuallyHidden data-testid="vh" aria-live="polite">x</VisuallyHidden>,
39
+ );
40
+ const el = container.firstElementChild;
41
+ expect(el?.getAttribute('data-testid')).toBe('vh');
42
+ expect(el?.getAttribute('aria-live')).toBe('polite');
43
+ });
44
+
45
+ it('typical pattern — icon button label', () => {
46
+ const { container } = render(
47
+ <button type="button">
48
+ <span aria-hidden="true">✕</span>
49
+ <VisuallyHidden>Close</VisuallyHidden>
50
+ </button>,
51
+ );
52
+ const btn = container.querySelector('button');
53
+ // textContent includes the hidden label so AT can read it
54
+ expect(btn?.textContent).toBe('✕Close');
55
+ // visually, the Close span has sr-only
56
+ const hidden = btn?.querySelector('.sr-only');
57
+ expect(hidden?.textContent).toBe('Close');
58
+ });
59
+
60
+ // ─── F11-bis edge cases ─────────────────────────────────────────────
61
+ it('empty children renders empty element with sr-only', () => {
62
+ const { container } = render(<VisuallyHidden>{''}</VisuallyHidden>);
63
+ const el = container.firstElementChild;
64
+ expect(el?.className).toContain('sr-only');
65
+ expect(el?.textContent).toBe('');
66
+ });
67
+
68
+ it('aria-live="polite" pattern for status announcements', () => {
69
+ const { container } = render(
70
+ <VisuallyHidden aria-live="polite">Saved successfully</VisuallyHidden>,
71
+ );
72
+ const el = container.firstElementChild;
73
+ expect(el?.getAttribute('aria-live')).toBe('polite');
74
+ expect(el?.textContent).toBe('Saved successfully');
75
+ });
76
+
77
+ it('as="div" renders block element (consumer choice for block hidden content)', () => {
78
+ const { container } = render(
79
+ <VisuallyHidden as="div">
80
+ Block content hidden from sight but readable by AT
81
+ </VisuallyHidden>,
82
+ );
83
+ expect(container.firstElementChild?.tagName).toBe('DIV');
84
+ expect(container.firstElementChild?.className).toContain('sr-only');
85
+ });
86
+ });
@@ -0,0 +1,59 @@
1
+ import { forwardRef, type ElementType } from 'react';
2
+ import { cn } from '../../utils/cn.js';
3
+ import type { VisuallyHiddenProps } from './visuallyHidden.types.js';
4
+
5
+ /**
6
+ * `<VisuallyHidden>` — the accessibility primitive.
7
+ *
8
+ * Hides children from sighted users (zero pixels rendered, no layout
9
+ * impact) while keeping them in the accessibility tree. Screen readers
10
+ * speak the content; voice control software uses it for click targets;
11
+ * keyboard users see nothing — same as if it weren't there.
12
+ *
13
+ * Implementation: Tailwind's built-in `sr-only` utility, which expands to:
14
+ *
15
+ * .sr-only {
16
+ * position: absolute;
17
+ * width: 1px;
18
+ * height: 1px;
19
+ * padding: 0;
20
+ * margin: -1px;
21
+ * overflow: hidden;
22
+ * clip: rect(0,0,0,0);
23
+ * white-space: nowrap;
24
+ * border-width: 0;
25
+ * }
26
+ *
27
+ * This is the canonical "visually-hidden but screen-reader-accessible"
28
+ * pattern, also known as the WebAIM clip technique. Critically:
29
+ *
30
+ * • NOT `display: none` (removes from a11y tree)
31
+ * • NOT `visibility: hidden` (also removes from a11y tree)
32
+ * • NOT `opacity: 0` (technically still rendered, doesn't help AT)
33
+ * • NOT `width/height: 0` (collapses, some AT skips it)
34
+ *
35
+ * Default tag is `<span>` (inline) — the 99% case is "label inside a
36
+ * button or link". For block content, override with `as="div"`, but
37
+ * be aware nesting block inside inline is invalid HTML.
38
+ *
39
+ * The component is intentionally tiny (~30 LoC total): one className,
40
+ * one tag. The value is the COMPONENT NAME — `<VisuallyHidden>` reads
41
+ * as an intentional a11y decision in code review, while
42
+ * `className="sr-only"` looks like a typo or a forgotten utility.
43
+ */
44
+ export const VisuallyHidden = forwardRef<HTMLElement, VisuallyHiddenProps>(
45
+ function VisuallyHidden(props, ref) {
46
+ const { as, sx, children, ...rest } = props;
47
+
48
+ const Tag = (as ?? 'span') as ElementType;
49
+ const classes = cn('sr-only', sx);
50
+
51
+ return (
52
+ <Tag ref={ref as never} className={classes} {...rest}>
53
+ {children}
54
+ </Tag>
55
+ );
56
+ },
57
+ );
58
+
59
+ VisuallyHidden.displayName = 'VisuallyHidden';
@@ -0,0 +1,45 @@
1
+ import type { ElementType, HTMLAttributes, ReactElement } from 'react';
2
+
3
+ /**
4
+ * Props for `<VisuallyHidden>` — the a11y primitive.
5
+ *
6
+ * Hides content visually (zero pixels rendered) while keeping it
7
+ * accessible to assistive technology (screen readers, voice control).
8
+ * The canonical use case: icon-only buttons that need an audible label
9
+ * for non-visual users.
10
+ *
11
+ * <button>
12
+ * <CloseIcon />
13
+ * <VisuallyHidden>Close dialog</VisuallyHidden>
14
+ * </button>
15
+ *
16
+ * Implementation uses Tailwind's `sr-only` utility, which sets
17
+ * `position: absolute; width: 1px; height: 1px; padding: 0;
18
+ * margin: -1px; overflow: hidden; clip: rect(0,0,0,0);
19
+ * white-space: nowrap; border-width: 0;` — the canonical
20
+ * "visually-hidden but screen-reader-accessible" pattern. Better
21
+ * than `display: none` (removes from a11y tree) and better than
22
+ * `visibility: hidden` (also removes from a11y tree).
23
+ */
24
+ export interface VisuallyHiddenProps
25
+ extends Omit<HTMLAttributes<HTMLElement>, 'className'> {
26
+ /**
27
+ * The text or markup hidden from sighted users but read by AT.
28
+ * Most often a string label.
29
+ */
30
+ children?: ReactElement | string | number;
31
+
32
+ /**
33
+ * Override the rendered HTML tag. Defaults to `'span'` — inline
34
+ * placement is the most common case (label inside a button or link).
35
+ * Use `as="div"` when the hidden content is block-level, but be
36
+ * aware: nesting a block inside an inline parent is invalid HTML.
37
+ */
38
+ as?: ElementType;
39
+
40
+ /**
41
+ * Utility classes appended to `sr-only`. RARELY needed for this
42
+ * primitive (it's just hidden text) — kept for API parity.
43
+ */
44
+ sx?: string;
45
+ }
package/src/index.ts CHANGED
@@ -174,6 +174,55 @@ export type {
174
174
  } from './components/Snackbar/snackbar.types.js';
175
175
  export { snackbarVariants } from './components/Snackbar/snackbar.variants.js';
176
176
 
177
+ // F9 foundation: typography + layout primitives. These sit BENEATH
178
+ // every other component (Button's label, TextField's helper text, the
179
+ // surface of an AppShell card) and are what app-level code reaches for
180
+ // before it reaches for any specific UI atom.
181
+
182
+ export { Typography } from './components/Typography/Typography.js';
183
+ export type { TypographyProps } from './components/Typography/typography.types.js';
184
+ export { typographyVariants } from './components/Typography/typography.variants.js';
185
+
186
+ export { Box } from './components/Box/Box.js';
187
+ export type { BoxProps } from './components/Box/box.types.js';
188
+ export { boxVariants } from './components/Box/box.variants.js';
189
+
190
+ export { Stack } from './components/Stack/Stack.js';
191
+ export type { StackProps } from './components/Stack/stack.types.js';
192
+ export { stackVariants } from './components/Stack/stack.variants.js';
193
+
194
+ export { Grid } from './components/Grid/Grid.js';
195
+ export type {
196
+ GridProps,
197
+ GridContainerProps,
198
+ GridItemProps,
199
+ ColSpan,
200
+ GridSpacingStep,
201
+ } from './components/Grid/grid.types.js';
202
+ export { gridVariants } from './components/Grid/grid.variants.js';
203
+
204
+ // F10 foundation completions: Container, Divider, AspectRatio, VisuallyHidden.
205
+ // The page-root chrome + visual separators + content-shape primitives +
206
+ // the a11y baseline. With these, the foundation layer matches what
207
+ // Chakra/Mantine/Joy ship for layout-level primitives.
208
+
209
+ export { Container } from './components/Container/Container.js';
210
+ export type { ContainerProps } from './components/Container/container.types.js';
211
+ export { containerVariants } from './components/Container/container.variants.js';
212
+
213
+ export { Divider } from './components/Divider/Divider.js';
214
+ export type { DividerProps } from './components/Divider/divider.types.js';
215
+ export {
216
+ dividerVariants,
217
+ dividerLineVariants,
218
+ } from './components/Divider/divider.variants.js';
219
+
220
+ export { AspectRatio } from './components/AspectRatio/AspectRatio.js';
221
+ export type { AspectRatioProps } from './components/AspectRatio/aspectRatio.types.js';
222
+
223
+ export { VisuallyHidden } from './components/VisuallyHidden/VisuallyHidden.js';
224
+ export type { VisuallyHiddenProps } from './components/VisuallyHidden/visuallyHidden.types.js';
225
+
177
226
  // ───── Hooks ─────
178
227
  export { useAccessState } from './hooks/useAccessState.js';
179
228
 
@@ -188,4 +237,4 @@ export type { VariantProps } from 'tailwind-variants';
188
237
  /**
189
238
  * Package version (synced with `package.json` at publish time).
190
239
  */
191
- export const VERSION = '0.1.0-beta';
240
+ export const VERSION = '0.2.1-beta';
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Dashforge
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,32 +0,0 @@
1
- import type { AppShellProps } from './appShell.types.js';
2
- /**
3
- * Dashforge TW AppShell — top-level layout orchestrator.
4
- *
5
- * ┌──────────────────────────────────────────┐
6
- * │ header │ ← `header` slot
7
- * ├────────┬─────────────────────────────────┤
8
- * │ │ │
9
- * │ nav │ main │ ← `nav` + `children`
10
- * │ │ │
11
- * ├────────┴─────────────────────────────────┤
12
- * │ footer │ ← `footer` slot
13
- * └──────────────────────────────────────────┘
14
- *
15
- * **Responsive**:
16
- * - `≥ md`: nav stays in the inline slot at the left.
17
- * - `< md`: nav becomes a slide-in drawer toggled by `navOpen`.
18
- * A backdrop closes the drawer on click (mirrors `<Dialog>` UX).
19
- *
20
- * **Body scroll lock**: when the mobile drawer is open we add
21
- * `overflow-hidden` to `<body>` to prevent dual-scroll wobble. The
22
- * effect cleans up on close + on unmount.
23
- *
24
- * **A11y**:
25
- * - `<main>` landmark wraps the content area.
26
- * - `<header>` / `<footer>` come for free from their HTML tags.
27
- * - The mobile drawer + backdrop participate in the standard
28
- * "click outside to close" pattern — `Escape` closes the drawer
29
- * too (added via global keydown).
30
- */
31
- export declare function AppShell(props: AppShellProps): import("react/jsx-runtime").JSX.Element;
32
- //# sourceMappingURL=AppShell.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"AppShell.d.ts","sourceRoot":"","sources":["../../../src/components/AppShell/AppShell.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,2CA+E5C"}
@@ -1,62 +0,0 @@
1
- import type { ReactNode } from 'react';
2
- import type { AppShellVariants } from './appShell.variants.js';
3
- export interface AppShellSlotProps {
4
- root?: {
5
- className?: string;
6
- };
7
- header?: {
8
- className?: string;
9
- };
10
- nav?: {
11
- className?: string;
12
- };
13
- navMobile?: {
14
- className?: string;
15
- };
16
- main?: {
17
- className?: string;
18
- };
19
- footer?: {
20
- className?: string;
21
- };
22
- backdrop?: {
23
- className?: string;
24
- };
25
- }
26
- /**
27
- * Props for `<AppShell>`.
28
- *
29
- * Top-level layout orchestrator with 4 named slots:
30
- *
31
- * - `header` (top, sticky)
32
- * - `nav` (left side, fixed-width on desktop, drawer on mobile)
33
- * - `footer` (bottom, non-sticky)
34
- * - `children` (main content, scrolls independently)
35
- *
36
- * Responsive behavior:
37
- * - **≥ md (768px)**: nav is rendered inline at the left.
38
- * - **< md**: nav slides in as an overlay drawer when
39
- * `navOpen={true}` (controlled).
40
- *
41
- * The shell is router-agnostic — pass any nav/header content. Typical
42
- * pairing is `<LeftNav>` + `<TopBar>` (with a hamburger button in
43
- * `TopBar.start` that flips `navOpen`).
44
- */
45
- export interface AppShellProps extends AppShellVariants {
46
- header?: ReactNode;
47
- nav?: ReactNode;
48
- footer?: ReactNode;
49
- children?: ReactNode;
50
- /**
51
- * Mobile drawer open state. Controlled — provide
52
- * `onNavOpenChange` to update. When `undefined`, the mobile drawer
53
- * is always closed (consumer hasn't wired up the toggle yet).
54
- */
55
- navOpen?: boolean;
56
- onNavOpenChange?: (open: boolean) => void;
57
- /** Root className shortcut. */
58
- sx?: string;
59
- /** Per-slot className overrides. */
60
- slotProps?: AppShellSlotProps;
61
- }
62
- //# sourceMappingURL=appShell.types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"appShell.types.d.ts","sourceRoot":"","sources":["../../../src/components/AppShell/appShell.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9B,MAAM,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAChC,GAAG,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7B,SAAS,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnC,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9B,MAAM,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAChC,QAAQ,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,aAAc,SAAQ,gBAAgB;IACrD,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAC1C,+BAA+B;IAC/B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,oCAAoC;IACpC,SAAS,CAAC,EAAE,iBAAiB,CAAC;CAC/B"}
@@ -1,65 +0,0 @@
1
- import { type VariantProps } from 'tailwind-variants';
2
- /**
3
- * Tailwind-variants recipe for `<AppShell>`.
4
- *
5
- * Slots:
6
- * - `root` — outer flex column (full viewport)
7
- * - `header` — top region
8
- * - `body` — flex row holding nav + main
9
- * - `nav` — desktop nav rail (md+)
10
- * - `navMobile` — mobile drawer (slide-in)
11
- * - `main` — scrollable content area
12
- * - `footer` — bottom region
13
- * - `backdrop` — semi-opaque overlay behind the mobile drawer
14
- */
15
- export declare const appShellVariants: import("tailwind-variants").TVReturnType<{
16
- navOpen: {
17
- true: {
18
- navMobile: string;
19
- backdrop: string;
20
- };
21
- };
22
- }, {
23
- root: string;
24
- header: string;
25
- body: string;
26
- nav: string;
27
- navMobile: string[];
28
- main: string;
29
- footer: string;
30
- backdrop: string[];
31
- }, undefined, {
32
- navOpen: {
33
- true: {
34
- navMobile: string;
35
- backdrop: string;
36
- };
37
- };
38
- }, {
39
- root: string;
40
- header: string;
41
- body: string;
42
- nav: string;
43
- navMobile: string[];
44
- main: string;
45
- footer: string;
46
- backdrop: string[];
47
- }, import("tailwind-variants").TVReturnType<{
48
- navOpen: {
49
- true: {
50
- navMobile: string;
51
- backdrop: string;
52
- };
53
- };
54
- }, {
55
- root: string;
56
- header: string;
57
- body: string;
58
- nav: string;
59
- navMobile: string[];
60
- main: string;
61
- footer: string;
62
- backdrop: string[];
63
- }, undefined, unknown, unknown, undefined>>;
64
- export type AppShellVariants = VariantProps<typeof appShellVariants>;
65
- //# sourceMappingURL=appShell.variants.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"appShell.variants.d.ts","sourceRoot":"","sources":["../../../src/components/AppShell/appShell.variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAM,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE1D;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2CA8B3B,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,YAAY,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
@@ -1,32 +0,0 @@
1
- import type { AutocompleteOption, AutocompleteProps } from './autocomplete.types.js';
2
- /**
3
- * Dashforge TW Autocomplete — bridge-integrated single-select combobox.
4
- *
5
- * **Implementation note (F5-A, rev. 2)**
6
- *
7
- * The first revision wrapped `react-aria-components`' `<ComboBox>` to get
8
- * AAA-grade a11y for free. In practice React Aria's internal state
9
- * machine fought every controlled-state pattern we tried (the input text
10
- * would not clear when `selectedKey` went to `null`, even with a forced
11
- * remount via `key` bumping). To get deterministic behaviour we own all
12
- * of the state ourselves:
13
- *
14
- * - `inputValue` — the visible text in the `<input>`
15
- * - `isOpen` — popover visibility
16
- * - `highlightedIndex` — keyboard navigation cursor
17
- *
18
- * A11y is still solid:
19
- * - `role="combobox"` + `aria-autocomplete="list"` on the input
20
- * - `aria-expanded`, `aria-controls`, `aria-activedescendant`
21
- * - `role="listbox"` / `role="option"` with `aria-selected`
22
- * - Full keyboard nav (↑/↓ navigate, Enter selects, Escape closes,
23
- * Home/End jump, Tab closes, click-outside closes)
24
- * - Required field gets `aria-required="true"`
25
- *
26
- * F5-A scope: single-select, static options, contains-substring filter
27
- * (case-insensitive). Multi-select, free-solo text, generic option
28
- * shapes, and async runtime options (`optionsFromFieldData`) are
29
- * deferred to F5-A-bis.
30
- */
31
- export declare function Autocomplete<TOption = AutocompleteOption>(props: AutocompleteProps<TOption>): import("react/jsx-runtime").JSX.Element | null;
32
- //# sourceMappingURL=Autocomplete.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Autocomplete.d.ts","sourceRoot":"","sources":["../../../src/components/Autocomplete/Autocomplete.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EACV,kBAAkB,EAClB,iBAAiB,EAElB,MAAM,yBAAyB,CAAC;AAuCjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAG,kBAAkB,EACvD,KAAK,EAAE,iBAAiB,CAAC,OAAO,CAAC,kDAu4BlC"}