@dev-dga/react 0.1.1 → 0.3.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 ADDED
@@ -0,0 +1,67 @@
1
+ # @dev-dga/react
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
+
51
+ ## 0.2.0
52
+
53
+ ### Minor Changes
54
+
55
+ - Add `useDir()` hook and wire Radix's `Direction.Provider` into `DgaProvider`.
56
+ - **`useDir(): 'ltr' | 'rtl'`** — public hook returning direction from the nearest `<DgaProvider>`. Throws when used outside a provider. Use this in custom components instead of `useDga().dir` when only direction is needed.
57
+ - **Auto-propagation to Radix portals** — `DgaProvider` now wraps its children in `<Direction.Provider value={dir}>`. Radix primitives that render in portals (Tooltip, Select, Toast, Modal, …) inherit direction from the provider tree instead of falling back to `document.body`'s dir. Future portal-based components will respect the configured direction with no per-component plumbing. **No effect on the four components shipped in this release** (`Button`, `Input`, `Textarea`, `Checkbox`) — none render portals — so this is forward-looking infrastructure, not a behavior change for current consumers. The one edge case: code rendering `radix-ui` portal primitives directly inside a `DgaProvider` will now see them pick up the provider direction (a fix, not a regression).
58
+
59
+ For LTR-locked content inside an RTL page (Saudi phone numbers, IBANs, National IDs), use the native `dir="ltr"` attribute plus the appropriate `inputMode` on `<Input>` directly — no new component required. See the `Foundations/RTL` Storybook page for examples.
60
+
61
+ ## 0.1.1
62
+
63
+ - Per-package README and metadata polish.
64
+
65
+ ## 0.1.0
66
+
67
+ - Initial release: `Button`, `Input`, `Textarea`, `Checkbox`, `DgaProvider`, `useDga`, `cn`. React 19, ref-as-prop, `cva` variants, BEM `ddga-*` classes resolving `var(--ddga-*)`.
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) and `useDga`.
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
 
package/dist/index.cjs CHANGED
@@ -23,14 +23,26 @@ __export(src_exports, {
23
23
  Button: () => Button,
24
24
  Checkbox: () => Checkbox,
25
25
  DgaProvider: () => DgaProvider,
26
+ FieldMessage: () => FieldMessage,
26
27
  Input: () => Input,
28
+ Radio: () => Radio,
29
+ RadioGroup: () => RadioGroup,
30
+ Select: () => Select,
31
+ SelectItem: () => SelectItem,
32
+ Switch: () => Switch,
27
33
  Textarea: () => Textarea,
28
34
  buttonVariants: () => buttonVariants,
29
35
  checkboxVariants: () => checkboxVariants,
30
36
  cn: () => cn,
31
37
  inputVariants: () => inputVariants,
38
+ radioGroupVariants: () => radioGroupVariants,
39
+ radioVariants: () => radioVariants,
40
+ selectTriggerVariants: () => selectTriggerVariants,
41
+ switchVariants: () => switchVariants,
32
42
  textareaVariants: () => textareaVariants,
33
- useDga: () => useDga
43
+ useDga: () => useDga,
44
+ useDir: () => useDir,
45
+ useFieldA11y: () => useFieldA11y
34
46
  });
35
47
  module.exports = __toCommonJS(src_exports);
36
48
 
@@ -101,6 +113,24 @@ function Minus(props) {
101
113
  }
102
114
  );
103
115
  }
116
+ function ChevronDown(props) {
117
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
118
+ "svg",
119
+ {
120
+ xmlns: "http://www.w3.org/2000/svg",
121
+ width: "1em",
122
+ height: "1em",
123
+ viewBox: "0 0 24 24",
124
+ fill: "none",
125
+ stroke: "currentColor",
126
+ strokeWidth: "2",
127
+ strokeLinecap: "round",
128
+ strokeLinejoin: "round",
129
+ ...props,
130
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m6 9 6 6 6-6" })
131
+ }
132
+ );
133
+ }
104
134
 
105
135
  // src/components/Button/Button.tsx
106
136
  var import_jsx_runtime2 = require("react/jsx-runtime");
@@ -179,7 +209,7 @@ function Button({
179
209
  // src/components/Input/Input.tsx
180
210
  var import_class_variance_authority2 = require("class-variance-authority");
181
211
 
182
- // src/internal/field/use-field-a11y.ts
212
+ // src/field/use-field-a11y.ts
183
213
  var import_react2 = require("react");
184
214
  function isPresent(value) {
185
215
  return value != null && value !== "";
@@ -189,7 +219,7 @@ function useFieldA11y(options) {
189
219
  const fieldId = options.id ?? generatedId;
190
220
  const helperId = `${fieldId}-helper`;
191
221
  const errorId = `${fieldId}-error`;
192
- const hasError = !!options.error;
222
+ const hasError = !!options.error || isPresent(options.errorMessage);
193
223
  const hasErrorMessage = hasError && isPresent(options.errorMessage);
194
224
  const hasHelper = !hasErrorMessage && isPresent(options.helperText);
195
225
  if (process.env.NODE_ENV === "development" && !options.label) {
@@ -214,7 +244,7 @@ function useFieldA11y(options) {
214
244
  };
215
245
  }
216
246
 
217
- // src/internal/field/FieldMessage.tsx
247
+ // src/field/FieldMessage.tsx
218
248
  var import_jsx_runtime3 = require("react/jsx-runtime");
219
249
  function FieldMessage({ id, variant, children }) {
220
250
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -328,7 +358,7 @@ var textareaVariants = (0, import_class_variance_authority3.cva)("ddga-textarea"
328
358
  true: "ddga-textarea--error"
329
359
  },
330
360
  // Horizontal/both resize breaks form layout (same reason it's not the
331
- // default) only vertical or locked are offered.
361
+ // default) , only vertical or locked are offered.
332
362
  resize: {
333
363
  vertical: "ddga-textarea--resize-vertical",
334
364
  none: "ddga-textarea--resize-none"
@@ -455,66 +485,338 @@ function Checkbox({
455
485
  ] });
456
486
  }
457
487
 
458
- // src/providers/DgaProvider.tsx
459
- var import_react4 = require("react");
488
+ // src/components/Radio/Radio.tsx
489
+ var import_react3 = require("react");
460
490
  var import_radix_ui2 = require("radix-ui");
491
+ var import_class_variance_authority5 = require("class-variance-authority");
492
+ var import_jsx_runtime7 = require("react/jsx-runtime");
493
+ var radioGroupVariants = (0, import_class_variance_authority5.cva)("ddga-radio-group", {
494
+ variants: {
495
+ size: {
496
+ sm: "ddga-radio-group--sm",
497
+ md: "ddga-radio-group--md"
498
+ },
499
+ orientation: {
500
+ vertical: "ddga-radio-group--vertical",
501
+ horizontal: "ddga-radio-group--horizontal"
502
+ },
503
+ error: {
504
+ true: "ddga-radio-group--error"
505
+ }
506
+ },
507
+ defaultVariants: {
508
+ size: "md",
509
+ orientation: "vertical"
510
+ }
511
+ });
512
+ var radioVariants = (0, import_class_variance_authority5.cva)("ddga-radio");
513
+ function RadioGroup({
514
+ id: externalId,
515
+ label,
516
+ helperText,
517
+ errorMessage,
518
+ error,
519
+ size,
520
+ orientation,
521
+ required,
522
+ className,
523
+ children,
524
+ ...props
525
+ }) {
526
+ const { errorId, helperId, hasError, hasErrorMessage, hasHelper, controlProps, fieldId } = useFieldA11y({
527
+ name: "RadioGroup",
528
+ id: externalId,
529
+ label,
530
+ error,
531
+ errorMessage,
532
+ helperText,
533
+ "aria-label": props["aria-label"],
534
+ "aria-labelledby": props["aria-labelledby"]
535
+ });
536
+ const labelId = `${fieldId}-label`;
537
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { "data-slot": "radio-group-field", className: "ddga-field", children: [
538
+ label && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { id: labelId, className: "ddga-radio-group__label", children: [
539
+ label,
540
+ required && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "aria-hidden": "true", className: "ddga-radio-group__required", children: "*" })
541
+ ] }),
542
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
543
+ import_radix_ui2.RadioGroup.Root,
544
+ {
545
+ ...props,
546
+ "aria-invalid": controlProps["aria-invalid"],
547
+ "aria-describedby": controlProps["aria-describedby"],
548
+ "aria-labelledby": label ? labelId : props["aria-labelledby"],
549
+ orientation: orientation === "horizontal" ? "horizontal" : "vertical",
550
+ required,
551
+ "data-slot": "radio-group",
552
+ className: cn(radioGroupVariants({ size, orientation, error: hasError }), className),
553
+ children
554
+ }
555
+ ),
556
+ hasErrorMessage && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(FieldMessage, { id: errorId, variant: "error", children: errorMessage }),
557
+ hasHelper && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(FieldMessage, { id: helperId, variant: "helper", children: helperText })
558
+ ] });
559
+ }
560
+ function Radio({ id: externalId, label, className, ...props }) {
561
+ const generatedId = (0, import_react3.useId)();
562
+ const itemId = externalId ?? generatedId;
563
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "ddga-radio-row", children: [
564
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
565
+ import_radix_ui2.RadioGroup.Item,
566
+ {
567
+ ...props,
568
+ id: itemId,
569
+ "data-slot": "radio",
570
+ className: cn(radioVariants(), className),
571
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
572
+ import_radix_ui2.RadioGroup.Indicator,
573
+ {
574
+ "data-slot": "radio-indicator",
575
+ className: "ddga-radio__indicator",
576
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ddga-radio__dot", "aria-hidden": "true" })
577
+ }
578
+ )
579
+ }
580
+ ),
581
+ label && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("label", { htmlFor: itemId, className: "ddga-radio__label", children: label })
582
+ ] });
583
+ }
584
+
585
+ // src/components/Switch/Switch.tsx
586
+ var import_radix_ui3 = require("radix-ui");
587
+ var import_class_variance_authority6 = require("class-variance-authority");
588
+ var import_jsx_runtime8 = require("react/jsx-runtime");
589
+ var switchVariants = (0, import_class_variance_authority6.cva)("ddga-switch", {
590
+ variants: {
591
+ size: {
592
+ sm: "ddga-switch--sm",
593
+ md: "ddga-switch--md"
594
+ },
595
+ error: {
596
+ true: "ddga-switch--error"
597
+ }
598
+ },
599
+ defaultVariants: {
600
+ size: "md"
601
+ }
602
+ });
603
+ function Switch({
604
+ id: externalId,
605
+ label,
606
+ helperText,
607
+ errorMessage,
608
+ error,
609
+ size,
610
+ required,
611
+ className,
612
+ ...props
613
+ }) {
614
+ const { fieldId, errorId, helperId, hasError, hasErrorMessage, hasHelper, controlProps } = useFieldA11y({
615
+ name: "Switch",
616
+ id: externalId,
617
+ label,
618
+ error,
619
+ errorMessage,
620
+ helperText,
621
+ "aria-label": props["aria-label"],
622
+ "aria-labelledby": props["aria-labelledby"]
623
+ });
624
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { "data-slot": "switch-field", className: "ddga-field", children: [
625
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "ddga-switch-row", children: [
626
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
627
+ import_radix_ui3.Switch.Root,
628
+ {
629
+ ...props,
630
+ ...controlProps,
631
+ "data-slot": "switch",
632
+ required,
633
+ className: cn(switchVariants({ size, error: hasError }), className),
634
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_radix_ui3.Switch.Thumb, { "data-slot": "switch-thumb", className: "ddga-switch__thumb" })
635
+ }
636
+ ),
637
+ label && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { htmlFor: fieldId, className: "ddga-switch__label", children: [
638
+ label,
639
+ required && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { "aria-hidden": "true", className: "ddga-switch__required", children: "*" })
640
+ ] })
641
+ ] }),
642
+ hasErrorMessage && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(FieldMessage, { id: errorId, variant: "error", children: errorMessage }),
643
+ hasHelper && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(FieldMessage, { id: helperId, variant: "helper", children: helperText })
644
+ ] });
645
+ }
646
+
647
+ // src/components/Select/Select.tsx
648
+ var import_react5 = require("react");
649
+ var import_radix_ui4 = require("radix-ui");
650
+ var import_class_variance_authority7 = require("class-variance-authority");
461
651
 
462
652
  // src/providers/DgaContext.ts
463
- var import_react3 = require("react");
464
- var DgaContext = (0, import_react3.createContext)(null);
653
+ var import_react4 = require("react");
654
+ var DgaContext = (0, import_react4.createContext)(null);
465
655
  function useDga() {
466
- const ctx = (0, import_react3.useContext)(DgaContext);
656
+ const ctx = (0, import_react4.useContext)(DgaContext);
467
657
  if (!ctx) {
468
658
  throw new Error("useDga must be used within a <DgaProvider>");
469
659
  }
470
660
  return ctx;
471
661
  }
472
662
 
663
+ // src/components/Select/Select.tsx
664
+ var import_jsx_runtime9 = require("react/jsx-runtime");
665
+ var selectTriggerVariants = (0, import_class_variance_authority7.cva)("ddga-select-trigger", {
666
+ variants: {
667
+ size: {
668
+ sm: "ddga-select-trigger--sm",
669
+ md: "ddga-select-trigger--md"
670
+ },
671
+ error: {
672
+ true: "ddga-select-trigger--error"
673
+ }
674
+ },
675
+ defaultVariants: {
676
+ size: "md"
677
+ }
678
+ });
679
+ function Select({
680
+ id: externalId,
681
+ label,
682
+ helperText,
683
+ errorMessage,
684
+ error,
685
+ size,
686
+ required,
687
+ placeholder,
688
+ className,
689
+ children,
690
+ ...rootProps
691
+ }) {
692
+ const { fieldId, errorId, helperId, hasError, hasErrorMessage, hasHelper, controlProps } = useFieldA11y({
693
+ name: "Select",
694
+ id: externalId,
695
+ label,
696
+ error,
697
+ errorMessage,
698
+ helperText,
699
+ "aria-label": rootProps["aria-label"],
700
+ "aria-labelledby": rootProps["aria-labelledby"]
701
+ });
702
+ const ctx = (0, import_react5.useContext)(DgaContext);
703
+ const portalContainer = ctx?.rootEl ?? void 0;
704
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { "data-slot": "select-field", className: "ddga-field", children: [
705
+ label && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { htmlFor: fieldId, className: "ddga-select__label", children: [
706
+ label,
707
+ required && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { "aria-hidden": "true", className: "ddga-select__required", children: "*" })
708
+ ] }),
709
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_radix_ui4.Select.Root, { required, ...rootProps, children: [
710
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
711
+ import_radix_ui4.Select.Trigger,
712
+ {
713
+ ...controlProps,
714
+ "data-slot": "select-trigger",
715
+ className: cn(selectTriggerVariants({ size, error: hasError }), className),
716
+ children: [
717
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_radix_ui4.Select.Value, { placeholder }),
718
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_radix_ui4.Select.Icon, { asChild: true, className: "ddga-select__icon", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChevronDown, { "aria-hidden": "true" }) })
719
+ ]
720
+ }
721
+ ),
722
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_radix_ui4.Select.Portal, { container: portalContainer, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
723
+ import_radix_ui4.Select.Content,
724
+ {
725
+ "data-slot": "select-content",
726
+ className: "ddga-select-content",
727
+ position: "popper",
728
+ sideOffset: 4,
729
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_radix_ui4.Select.Viewport, { className: "ddga-select-viewport", children })
730
+ }
731
+ ) })
732
+ ] }),
733
+ hasErrorMessage && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(FieldMessage, { id: errorId, variant: "error", children: errorMessage }),
734
+ hasHelper && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(FieldMessage, { id: helperId, variant: "helper", children: helperText })
735
+ ] });
736
+ }
737
+ function SelectItem({ className, children, ...props }) {
738
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
739
+ import_radix_ui4.Select.Item,
740
+ {
741
+ ...props,
742
+ "data-slot": "select-item",
743
+ className: cn("ddga-select-item", className),
744
+ children: [
745
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_radix_ui4.Select.ItemText, { children }),
746
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_radix_ui4.Select.ItemIndicator, { className: "ddga-select-item__indicator", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Check, { className: "ddga-select-item__check", "aria-hidden": "true" }) })
747
+ ]
748
+ }
749
+ );
750
+ }
751
+
473
752
  // src/providers/DgaProvider.tsx
474
- var import_jsx_runtime7 = require("react/jsx-runtime");
753
+ var import_react6 = require("react");
754
+ var import_radix_ui5 = require("radix-ui");
755
+ var import_tokens = require("@dev-dga/tokens");
756
+ var import_jsx_runtime10 = require("react/jsx-runtime");
475
757
  function DgaProvider({
476
758
  dir = "ltr",
477
759
  locale,
478
760
  mode = "light",
761
+ theme,
479
762
  as: Component = "div",
480
763
  className,
481
764
  style: consumerStyle,
482
765
  children
483
766
  }) {
484
- const parentCtx = (0, import_react4.useContext)(DgaContext);
767
+ const parentCtx = (0, import_react6.useContext)(DgaContext);
485
768
  const isNested = parentCtx !== null;
486
- const rootRef = (0, import_react4.useRef)(null);
769
+ const [rootEl, setRootEl] = (0, import_react6.useState)(null);
487
770
  const resolvedLocale = locale ?? (dir === "rtl" ? "ar" : "en");
488
- const ctxValue = (0, import_react4.useMemo)(
489
- () => ({ dir, locale: resolvedLocale, mode, rootRef }),
490
- [dir, resolvedLocale, mode]
771
+ const ctxValue = (0, import_react6.useMemo)(
772
+ () => ({ dir, locale: resolvedLocale, mode, rootEl }),
773
+ [dir, resolvedLocale, mode, rootEl]
491
774
  );
492
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DgaContext.Provider, { value: ctxValue, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
775
+ const themeVars = (0, import_react6.useMemo)(() => theme ? (0, import_tokens.buildTheme)(theme) : null, [theme]);
776
+ const inner = /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_radix_ui5.Direction.Provider, { dir, children });
777
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DgaContext.Provider, { value: ctxValue, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
493
778
  Component,
494
779
  {
495
- ref: rootRef,
780
+ ref: setRootEl,
496
781
  dir,
497
782
  lang: resolvedLocale,
498
783
  "data-slot": "dga-root",
499
784
  "data-theme": mode,
500
785
  className,
501
- style: { colorScheme: mode, ...consumerStyle },
502
- children: isNested ? children : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_radix_ui2.Tooltip.Provider, { delayDuration: 300, skipDelayDuration: 100, children })
786
+ style: { colorScheme: mode, ...themeVars ?? {}, ...consumerStyle },
787
+ children: isNested ? inner : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_radix_ui5.Tooltip.Provider, { delayDuration: 300, skipDelayDuration: 100, children: inner })
503
788
  }
504
789
  ) });
505
790
  }
791
+
792
+ // src/hooks/useDir.ts
793
+ function useDir() {
794
+ return useDga().dir;
795
+ }
506
796
  // Annotate the CommonJS export names for ESM import in node:
507
797
  0 && (module.exports = {
508
798
  Button,
509
799
  Checkbox,
510
800
  DgaProvider,
801
+ FieldMessage,
511
802
  Input,
803
+ Radio,
804
+ RadioGroup,
805
+ Select,
806
+ SelectItem,
807
+ Switch,
512
808
  Textarea,
513
809
  buttonVariants,
514
810
  checkboxVariants,
515
811
  cn,
516
812
  inputVariants,
813
+ radioGroupVariants,
814
+ radioVariants,
815
+ selectTriggerVariants,
816
+ switchVariants,
517
817
  textareaVariants,
518
- useDga
818
+ useDga,
819
+ useDir,
820
+ useFieldA11y
519
821
  });
520
822
  //# sourceMappingURL=index.cjs.map