@dev-dga/react 0.2.0 → 0.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,53 @@
1
1
  # @dev-dga/react
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Breaking Changes
6
+
7
+ - **`DgaContextValue.rootRef` (RefObject) → `rootEl` (`HTMLElement | null`).** The `useDga()` return shape changed: `rootRef.current` is now `rootEl` directly. Callers reading `useDga().rootRef` will see a TypeScript error. Reason: the previous `useRef`-based exposure had a race — `useRef` doesn't trigger re-renders when the element mounts, so portal-rendering components reading `rootRef.current` raced to `<body>` on the first commit. The new state-backed callback ref triggers a re-render once the element is available. Pre-1.0 release; almost certainly no external consumers existed.
8
+
9
+ ### Minor Changes
10
+
11
+ - **Theming API — `theme` prop on `<DgaProvider>`.** Accepts a `DgaTheme` (from `@dev-dga/tokens`'s new `buildTheme()`); memoizes the resulting CSS vars and merges them into the root element's `style`. Three input shapes supported: palette name (`'lavender'`), single CSS color (hover/active derived via `color-mix()` — requires Safari 16.4+), or explicit `{ base, hover, active, foreground? }` triplet. Consumer's manual `style` override still wins (escape hatch preserved):
12
+
13
+ ```tsx
14
+ // palette name
15
+ <DgaProvider theme={{ primary: 'lavender' }}>{…}</DgaProvider>
16
+
17
+ // single hex (derives hover + active)
18
+ <DgaProvider theme={{ primary: '#7C3AED' }}>{…}</DgaProvider>
19
+
20
+ // explicit triplet
21
+ <DgaProvider theme={{ primary: { base: '#A', hover: '#B', active: '#C', foreground: '#D' } }}>
22
+ {…}
23
+ </DgaProvider>
24
+ ```
25
+
26
+ Theme types re-exported from `@dev-dga/react`: `DgaTheme`, `PaletteName`, `ThemeColor`.
27
+
28
+ - **Public field primitive — `useFieldA11y` + `FieldMessage`.** Promoted from `internal/` to the public surface. The same primitive every shipped form control uses; lets consumers build custom fields (date picker, file upload, combobox) with the library's id generation, helper/error precedence, ARIA wiring, and dev-time accessible-name warning. Exports: `useFieldA11y`, `FieldMessage`, `UseFieldA11yOptions`, `FieldA11y`, `FieldMessageProps`.
29
+
30
+ - **Three new components on the field primitive.** Each ships with tests, an autodocs story (EN + RTL/AR copy), and per-component CSS in `@dev-dga/css`:
31
+ - **`RadioGroup` + `Radio`** — Radix RadioGroup. Compound shape with `label`/`helperText`/`error`/`errorMessage`/`size`/`orientation` on the group; size + error cascade from group to items via CSS. Group label associated via `aria-labelledby`.
32
+ - **`Switch`** — single boolean. Same `useFieldA11y` wiring as Checkbox.
33
+ - **`Select` + `SelectItem`** — Radix Select. Closed shape — Trigger + Portal + Content + Viewport rendered internally; consumers compose with `<SelectItem>` children. Trigger height matches `Input` (32/36) so triggers + text inputs align in form rows. Content panel width matches trigger via `--radix-select-trigger-width`.
34
+
35
+ - **`asChild` composition on Button (Radix Slot).** New `asChild?: boolean` prop. When true, renders via `SlotPrimitive.Slot` with `Slottable` wrapping `children` so icons land as siblings inside the consumer's element. Unblocks router-link composition without invalid `<button><a>` nesting:
36
+
37
+ ```tsx
38
+ <Button asChild>
39
+ <Link href="/about">About</Link>
40
+ </Button>
41
+ ```
42
+
43
+ In Slot mode the `type` and native `disabled` attributes are dropped (no effect on non-form-controls); the disabled state still applies via `aria-disabled` + `tabIndex={-1}`. `@dev-dga/css` matches `[aria-disabled='true']` alongside `:disabled` so the visual disabled state works on any rendered element. Convention set for future visual components (Card, Badge, Avatar, Alert, …); form-control components that already wrap Radix primitives skip `asChild` — the primitive handles Slot internally.
44
+
45
+ - **Select dropdown anchored to the DgaProvider root for theme + dark-mode inheritance.** Radix Portal defaults to `<body>`, which lives above the `DgaProvider` root in the DOM tree — `data-theme`, custom theme vars set via the new `theme` prop, and `.ddga-theme-*` classes set on the root never reached the dropdown. Select now re-anchors via `<Select.Portal container={ctx.rootEl}>`. Same pattern documented in `DgaContext.ts` for future portal-using components (Tooltip / Modal / Toast / Popover / DropdownMenu).
46
+
47
+ ### Patch Changes
48
+
49
+ - **`errorMessage` alone now sets `aria-invalid` and renders the message.** Previously, `useFieldA11y` (and the form components that use it) gated rendering on `error: true`, so passing `errorMessage="…"` without `error` was a silent no-op — a footgun. `hasError` is now derived as `error || isPresent(errorMessage)`. The `error` boolean is still useful for the message-less invalid case (form-level validation marking the field invalid without per-field copy). Backward-compatible for callers passing both `error` + `errorMessage`.
50
+
3
51
  ## 0.2.0
4
52
 
5
53
  ### Minor Changes
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  React 19 components for the **DGA (Digital Government Authority)** design system , Saudi Arabia's national reference for government digital platforms.
4
4
 
5
- Accessible (WCAG 2.2 AA), RTL-native, dark-mode ready. **Ships no CSS** , pair with `@dev-dga/css`.
5
+ Accessible (WCAG 2.2 AA), RTL-native, dark-mode ready, themable. **Ships no CSS** , pair with `@dev-dga/css`.
6
6
 
7
7
  ## Install
8
8
 
@@ -16,29 +16,77 @@ Requires `react@^19` and `react-dom@^19` (peer dependencies).
16
16
 
17
17
  ```tsx
18
18
  import '@dev-dga/css'; // required , components ship no styles
19
- import { DgaProvider, Button, Input, Textarea, Checkbox } from '@dev-dga/react';
19
+ import { DgaProvider, Button, Input, Select, SelectItem, Switch } from '@dev-dga/react';
20
20
 
21
21
  export default function App() {
22
22
  return (
23
23
  <DgaProvider>
24
- <Button>Hello</Button>
25
24
  <Input label="Email" type="email" helperText="We'll never share it." />
25
+ <Select label="Country" placeholder="Choose…">
26
+ <SelectItem value="sa">Saudi Arabia</SelectItem>
27
+ <SelectItem value="ae">United Arab Emirates</SelectItem>
28
+ </Select>
29
+ <Switch label="Subscribe to the newsletter" />
30
+ <Button>Submit</Button>
26
31
  </DgaProvider>
27
32
  );
28
33
  }
29
34
  ```
30
35
 
31
- For RTL or dark mode, configure the provider:
36
+ For RTL, dark mode, or a brand color, configure the provider:
32
37
 
33
38
  ```tsx
34
- <DgaProvider dir="rtl" mode="dark">
39
+ <DgaProvider dir="rtl" mode="dark" theme={{ primary: 'lavender' }}>
35
40
  {/* … */}
36
41
  </DgaProvider>
37
42
  ```
38
43
 
44
+ ## Compose with router links (`asChild`)
45
+
46
+ Visual components support `asChild` so you can render them as your router's `Link` (Next.js, React Router, TanStack Router, …) without invalid `<button><a>` nesting:
47
+
48
+ ```tsx
49
+ <Button asChild>
50
+ <Link href="/about">About</Link>
51
+ </Button>
52
+ ```
53
+
54
+ ## Build your own field
55
+
56
+ `useFieldA11y` + `FieldMessage` are public , the same primitives every shipped form control uses. Build a custom field (date picker, file upload, combobox) with the library's a11y wiring, helper/error precedence, and dev-time warnings:
57
+
58
+ ```tsx
59
+ import { useFieldA11y, FieldMessage } from '@dev-dga/react';
60
+
61
+ function MyField({ label, helperText, error, errorMessage, ...rest }) {
62
+ const { fieldId, errorId, helperId, hasError, hasErrorMessage, hasHelper, controlProps } =
63
+ useFieldA11y({ name: 'MyField', label, error, errorMessage, helperText });
64
+ return (
65
+ <div className="ddga-field">
66
+ {label && <label htmlFor={fieldId}>{label}</label>}
67
+ <input {...controlProps} {...rest} />
68
+ {hasErrorMessage && (
69
+ <FieldMessage id={errorId} variant="error">
70
+ {errorMessage}
71
+ </FieldMessage>
72
+ )}
73
+ {hasHelper && (
74
+ <FieldMessage id={helperId} variant="helper">
75
+ {helperText}
76
+ </FieldMessage>
77
+ )}
78
+ </div>
79
+ );
80
+ }
81
+ ```
82
+
39
83
  ## Components
40
84
 
41
- `Button` · `Input` · `Textarea` · `Checkbox` · `DgaProvider` , plus `cn` (clsx), `useDga`, and `useDir`.
85
+ **Form:** `Button` · `Input` · `Textarea` · `Checkbox` · `RadioGroup` + `Radio` · `Switch` · `Select` + `SelectItem`
86
+
87
+ **Primitives:** `DgaProvider` · `useDga` · `useDir` · `useFieldA11y` · `FieldMessage` · `cn`
88
+
89
+ **Theming types:** `DgaTheme` · `PaletteName` · `ThemeColor`
42
90
 
43
91
  ## Docs
44
92