@dsn-starter-kit/components-react 3.5.0 → 4.1.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.
Files changed (77) hide show
  1. package/dist/Card/Card.d.mts +86 -21
  2. package/dist/Card/Card.d.mts.map +1 -1
  3. package/dist/Card/Card2.mjs +100 -17
  4. package/dist/Card/Card2.mjs.map +1 -1
  5. package/dist/Card/index.d.mts +2 -2
  6. package/dist/DateInput/DateInput.d.mts +1 -1
  7. package/dist/DateInput/DateInput2.mjs +1 -1
  8. package/dist/DateInput/DateInput2.mjs.map +1 -1
  9. package/dist/DateInputGroup/DateInputGroup2.mjs +3 -3
  10. package/dist/DateInputGroup/DateInputGroup2.mjs.map +1 -1
  11. package/dist/Drawer/Drawer.d.mts +5 -2
  12. package/dist/Drawer/Drawer.d.mts.map +1 -1
  13. package/dist/Drawer/Drawer2.mjs +18 -3
  14. package/dist/Drawer/Drawer2.mjs.map +1 -1
  15. package/dist/EmailInput/EmailInput.d.mts +10 -5
  16. package/dist/EmailInput/EmailInput.d.mts.map +1 -1
  17. package/dist/EmailInput/EmailInput2.mjs +2 -2
  18. package/dist/EmailInput/EmailInput2.mjs.map +1 -1
  19. package/dist/ModalDialog/ModalDialog.d.mts +13 -3
  20. package/dist/ModalDialog/ModalDialog.d.mts.map +1 -1
  21. package/dist/ModalDialog/ModalDialog2.mjs +10 -3
  22. package/dist/ModalDialog/ModalDialog2.mjs.map +1 -1
  23. package/dist/NumberInput/NumberInput.d.mts +4 -4
  24. package/dist/NumberInput/NumberInput.d.mts.map +1 -1
  25. package/dist/NumberInput/NumberInput2.mjs +2 -2
  26. package/dist/NumberInput/NumberInput2.mjs.map +1 -1
  27. package/dist/PageHeader/PageHeader.d.mts.map +1 -1
  28. package/dist/PageHeader/PageHeader2.mjs +19 -2
  29. package/dist/PageHeader/PageHeader2.mjs.map +1 -1
  30. package/dist/PasswordInput/PasswordInput.d.mts +10 -5
  31. package/dist/PasswordInput/PasswordInput.d.mts.map +1 -1
  32. package/dist/PasswordInput/PasswordInput2.mjs +2 -2
  33. package/dist/PasswordInput/PasswordInput2.mjs.map +1 -1
  34. package/dist/Popover/Popover.d.mts +16 -23
  35. package/dist/Popover/Popover.d.mts.map +1 -1
  36. package/dist/Popover/Popover2.mjs +33 -119
  37. package/dist/Popover/Popover2.mjs.map +1 -1
  38. package/dist/PopoverMenu/PopoverMenu.d.mts +62 -0
  39. package/dist/PopoverMenu/PopoverMenu.d.mts.map +1 -0
  40. package/dist/PopoverMenu/PopoverMenu2.mjs +66 -0
  41. package/dist/PopoverMenu/PopoverMenu2.mjs.map +1 -0
  42. package/dist/PopoverMenu/index.d.mts +2 -0
  43. package/dist/SearchInput/SearchInput.d.mts +10 -5
  44. package/dist/SearchInput/SearchInput.d.mts.map +1 -1
  45. package/dist/SearchInput/SearchInput2.mjs +2 -2
  46. package/dist/SearchInput/SearchInput2.mjs.map +1 -1
  47. package/dist/Select/Select.d.mts +6 -6
  48. package/dist/Select/Select.d.mts.map +1 -1
  49. package/dist/Select/Select2.mjs +4 -4
  50. package/dist/Select/Select2.mjs.map +1 -1
  51. package/dist/TelephoneInput/TelephoneInput.d.mts +10 -5
  52. package/dist/TelephoneInput/TelephoneInput.d.mts.map +1 -1
  53. package/dist/TelephoneInput/TelephoneInput2.mjs +2 -2
  54. package/dist/TelephoneInput/TelephoneInput2.mjs.map +1 -1
  55. package/dist/TextArea/TextArea.d.mts +10 -5
  56. package/dist/TextArea/TextArea.d.mts.map +1 -1
  57. package/dist/TextArea/TextArea2.mjs +2 -2
  58. package/dist/TextArea/TextArea2.mjs.map +1 -1
  59. package/dist/TextInput/TextInput.d.mts +4 -4
  60. package/dist/TextInput/TextInput.d.mts.map +1 -1
  61. package/dist/TextInput/TextInput2.mjs +2 -2
  62. package/dist/TextInput/TextInput2.mjs.map +1 -1
  63. package/dist/TimeInput/TimeInput.d.mts +1 -1
  64. package/dist/TimeInput/TimeInput2.mjs +1 -1
  65. package/dist/TimeInput/TimeInput2.mjs.map +1 -1
  66. package/dist/index.cjs +315 -79
  67. package/dist/index.cjs.map +1 -1
  68. package/dist/index.css +5950 -5217
  69. package/dist/index.d.cts +243 -66
  70. package/dist/index.d.cts.map +1 -1
  71. package/dist/index.d.mts +4 -2
  72. package/dist/index.mjs +3 -2
  73. package/dist/utils/popover.d.mts +27 -0
  74. package/dist/utils/popover.d.mts.map +1 -0
  75. package/dist/utils/popover.mjs +142 -0
  76. package/dist/utils/popover.mjs.map +1 -0
  77. package/package.json +3 -3
@@ -25,8 +25,8 @@ import { jsx } from "react/jsx-runtime";
25
25
  * <PasswordInput invalid aria-invalid="true" aria-describedby="error" />
26
26
  * ```
27
27
  */
28
- const PasswordInput = React.forwardRef(({ passwordAutocomplete = "current-password", className, invalid, width, autoComplete, ...props }, ref) => {
29
- const classes = classNames("dsn-text-input", width && `dsn-text-input--width-${width}`, className);
28
+ const PasswordInput = React.forwardRef(({ passwordAutocomplete = "current-password", className, invalid, inlineSize, autoComplete, ...props }, ref) => {
29
+ const classes = classNames("dsn-text-input", inlineSize && `dsn-text-input--inline-size-${inlineSize}`, className);
30
30
  return /* @__PURE__ */ jsx("input", {
31
31
  ref,
32
32
  type: "password",
@@ -1 +1 @@
1
- {"version":3,"file":"PasswordInput2.mjs","names":[],"sources":["../../src/PasswordInput/PasswordInput.tsx"],"sourcesContent":["import React from 'react';\nimport { classNames, FormControlWidth } from '@dsn-starter-kit/core';\nimport './PasswordInput.css';\n\nexport type PasswordAutocomplete = 'current-password' | 'new-password' | 'off';\n\nexport interface PasswordInputProps extends Omit<\n React.InputHTMLAttributes<HTMLInputElement>,\n 'type'\n> {\n /**\n * Autocomplete hint for password managers\n * - 'current-password': voor inlogformulieren (default)\n * - 'new-password': voor registratie- en wachtwoord wijzigformulieren\n * - 'off': autocomplete uitschakelen\n * @default 'current-password'\n */\n passwordAutocomplete?: PasswordAutocomplete;\n\n /**\n * Whether the input is in an invalid state\n * @default false\n */\n invalid?: boolean;\n\n /**\n * Width variant for the input\n * @default undefined (uses default max-width from form-control)\n */\n width?: FormControlWidth;\n\n /**\n * Additional CSS class names\n */\n className?: string;\n}\n\n/**\n * Password Input component\n * Uses type=\"password\" for secure text entry.\n * Het tonen/verbergen van het wachtwoord is bewust niet ingebouwd in dit component —\n * dat patroon wordt separaat gedefinieerd via een Button naast het invoerveld.\n *\n * @example\n * ```tsx\n * // Login form (current password)\n * <PasswordInput placeholder=\"Wachtwoord\" />\n *\n * // Registration form (new password)\n * <PasswordInput passwordAutocomplete=\"new-password\" placeholder=\"Nieuw wachtwoord\" />\n *\n * // With label\n * <FormFieldLabel htmlFor=\"password\">Wachtwoord</FormFieldLabel>\n * <PasswordInput id=\"password\" />\n *\n * // Invalid state\n * <PasswordInput invalid aria-invalid=\"true\" aria-describedby=\"error\" />\n * ```\n */\nexport const PasswordInput = React.forwardRef<\n HTMLInputElement,\n PasswordInputProps\n>(\n (\n {\n passwordAutocomplete = 'current-password',\n className,\n invalid,\n width,\n autoComplete,\n ...props\n },\n ref\n ) => {\n const classes = classNames(\n 'dsn-text-input',\n width && `dsn-text-input--width-${width}`,\n className\n );\n\n return (\n <input\n ref={ref}\n type=\"password\"\n className={classes}\n autoComplete={autoComplete || passwordAutocomplete}\n aria-invalid={invalid || undefined}\n {...props}\n />\n );\n }\n);\n\nPasswordInput.displayName = 'PasswordInput';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA2DA,MAAa,gBAAgB,MAAM,YAK/B,EACE,uBAAuB,oBACvB,WACA,SACA,OACA,cACA,GAAG,SAEL,QACG;CACH,MAAM,UAAU,WACd,kBACA,SAAS,yBAAyB,SAClC,SACF;CAEA,OACE,oBAAC,SAAD;EACO;EACL,MAAK;EACL,WAAW;EACX,cAAc,gBAAgB;EAC9B,gBAAc,WAAW,KAAA;EACzB,GAAI;CACL,CAAA;AAEL,CACF;AAEA,cAAc,cAAc"}
1
+ {"version":3,"file":"PasswordInput2.mjs","names":[],"sources":["../../src/PasswordInput/PasswordInput.tsx"],"sourcesContent":["import React from 'react';\nimport { classNames, FormControlInlineSize } from '@dsn-starter-kit/core';\nimport './PasswordInput.css';\n\n/**\n * Inline-size variants offered by PasswordInput. xs and sm are left out: they\n * are too narrow for the content this field is meant for.\n */\nexport type PasswordInputInlineSize = Exclude<\n FormControlInlineSize,\n 'xs' | 'sm'\n>;\n\nexport type PasswordAutocomplete = 'current-password' | 'new-password' | 'off';\n\nexport interface PasswordInputProps extends Omit<\n React.InputHTMLAttributes<HTMLInputElement>,\n 'type'\n> {\n /**\n * Autocomplete hint for password managers\n * - 'current-password': voor inlogformulieren (default)\n * - 'new-password': voor registratie- en wachtwoord wijzigformulieren\n * - 'off': autocomplete uitschakelen\n * @default 'current-password'\n */\n passwordAutocomplete?: PasswordAutocomplete;\n\n /**\n * Whether the input is in an invalid state\n * @default false\n */\n invalid?: boolean;\n\n /**\n * Inline-size variant for the input (md, lg, xl or full)\n * @default undefined (uses default max-inline-size from form-control)\n */\n inlineSize?: PasswordInputInlineSize;\n\n /**\n * Additional CSS class names\n */\n className?: string;\n}\n\n/**\n * Password Input component\n * Uses type=\"password\" for secure text entry.\n * Het tonen/verbergen van het wachtwoord is bewust niet ingebouwd in dit component —\n * dat patroon wordt separaat gedefinieerd via een Button naast het invoerveld.\n *\n * @example\n * ```tsx\n * // Login form (current password)\n * <PasswordInput placeholder=\"Wachtwoord\" />\n *\n * // Registration form (new password)\n * <PasswordInput passwordAutocomplete=\"new-password\" placeholder=\"Nieuw wachtwoord\" />\n *\n * // With label\n * <FormFieldLabel htmlFor=\"password\">Wachtwoord</FormFieldLabel>\n * <PasswordInput id=\"password\" />\n *\n * // Invalid state\n * <PasswordInput invalid aria-invalid=\"true\" aria-describedby=\"error\" />\n * ```\n */\nexport const PasswordInput = React.forwardRef<\n HTMLInputElement,\n PasswordInputProps\n>(\n (\n {\n passwordAutocomplete = 'current-password',\n className,\n invalid,\n inlineSize,\n autoComplete,\n ...props\n },\n ref\n ) => {\n const classes = classNames(\n 'dsn-text-input',\n inlineSize && `dsn-text-input--inline-size-${inlineSize}`,\n className\n );\n\n return (\n <input\n ref={ref}\n type=\"password\"\n className={classes}\n autoComplete={autoComplete || passwordAutocomplete}\n aria-invalid={invalid || undefined}\n {...props}\n />\n );\n }\n);\n\nPasswordInput.displayName = 'PasswordInput';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAoEA,MAAa,gBAAgB,MAAM,YAK/B,EACE,uBAAuB,oBACvB,WACA,SACA,YACA,cACA,GAAG,SAEL,QACG;CACH,MAAM,UAAU,WACd,kBACA,cAAc,+BAA+B,cAC7C,SACF;CAEA,OACE,oBAAC,SAAD;EACO;EACL,MAAK;EACL,WAAW;EACX,cAAc,gBAAgB;EAC9B,gBAAc,WAAW,KAAA;EACzB,GAAI;CACL,CAAA;AAEL,CACF;AAEA,cAAc,cAAc"}
@@ -1,19 +1,6 @@
1
+ import { PopoverPlacement } from "../utils/popover.mjs";
1
2
  import React from "react";
2
3
  //#region src/Popover/Popover.d.ts
3
- declare module 'react' {
4
- interface HTMLAttributes<T> {
5
- popover?: 'auto' | 'manual' | '' | undefined;
6
- popoverTarget?: string | undefined;
7
- popoverTargetAction?: 'hide' | 'show' | 'toggle' | undefined;
8
- }
9
- }
10
- declare global {
11
- interface HTMLElement {
12
- showPopover(): void;
13
- hidePopover(): void;
14
- }
15
- }
16
- type Placement = 'top' | 'bottom' | 'start' | 'end';
17
4
  interface PopoverProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> {
18
5
  /**
19
6
  * Bepaalt of de popover getoond wordt.
@@ -31,10 +18,10 @@ interface PopoverProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'child
31
18
  * Gewenste plaatsing relatief aan het triggerelement.
32
19
  * @default 'bottom'
33
20
  */
34
- placement?: Placement;
21
+ placement?: PopoverPlacement;
35
22
  /**
36
- * Toegankelijke naam voor popovers zonder `PopoverHeader`/`PopoverHeading`.
37
- * Gebruik `aria-labelledby` via context wanneer er wél een heading is.
23
+ * Toegankelijke naam voor een popover zonder `PopoverHeader`/`PopoverHeading`.
24
+ * Een zichtbare heading heeft de voorkeur; die koppelt via `aria-labelledby`.
38
25
  */
39
26
  label?: string;
40
27
  /**
@@ -44,21 +31,26 @@ interface PopoverProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'child
44
31
  }
45
32
  /**
46
33
  * Popover component
47
- * Lichtgewicht, contextgebonden overlay verankerd aan een triggerelement.
34
+ * Niet-modaal dialoogvenster verankerd aan een triggerelement
35
+ * (`role="dialog"`, `aria-modal="false"`).
48
36
  *
49
37
  * Gebaseerd op de HTML Popover API (`popover="auto"`) voor ingebakken
50
38
  * light-dismiss en top-layer gedrag. Positionering via JavaScript.
51
39
  *
40
+ * Voor een lijst met acties of links: gebruik `PopoverMenu`. Een lijst is
41
+ * geen dialoogvenster.
42
+ *
52
43
  * @example
53
44
  * ```tsx
54
45
  * const triggerRef = useRef<HTMLButtonElement>(null);
55
46
  * const [isOpen, setIsOpen] = useState(false);
56
47
  *
57
- * <Button ref={triggerRef} onClick={() => setIsOpen(true)}>Opties</Button>
58
- * <Popover isOpen={isOpen} onClose={() => setIsOpen(false)} triggerRef={triggerRef} label="Opties">
59
- * <PopoverBody>
60
- * <Menu>...</Menu>
61
- * </PopoverBody>
48
+ * <Button ref={triggerRef} onClick={() => setIsOpen((v) => !v)}>Filters</Button>
49
+ * <Popover isOpen={isOpen} onClose={() => setIsOpen(false)} triggerRef={triggerRef}>
50
+ * <PopoverHeader>
51
+ * <PopoverHeading>Filters</PopoverHeading>
52
+ * </PopoverHeader>
53
+ * <PopoverBody>...</PopoverBody>
62
54
  * </Popover>
63
55
  * ```
64
56
  */
@@ -89,6 +81,7 @@ interface PopoverHeadingProps extends React.HTMLAttributes<HTMLHeadingElement> {
89
81
  /**
90
82
  * PopoverHeading
91
83
  * De heading van de popover. ID wordt automatisch gegenereerd voor aria-labelledby.
84
+ * Krijgt `tabindex="-1"` zodat de Popover er bij openen de focus op kan zetten.
92
85
  */
93
86
  declare const PopoverHeading: React.ForwardRefExoticComponent<PopoverHeadingProps & React.RefAttributes<HTMLHeadingElement>>;
94
87
  interface PopoverBodyProps extends React.HTMLAttributes<HTMLDivElement> {
@@ -1 +1 @@
1
- {"version":3,"file":"Popover.d.mts","names":[],"sources":["../../src/Popover/Popover.tsx"],"mappings":";;;YAYY,eAAe;IACvB;IACA;IACA;;;QAQI;YACI;IACR;IACA;;;KAQC;UAwGY,qBAAqB,KACpC,MAAM,eAAe;;;;EAMrB;;;;EAKA;;;;EAKA,YAAY,MAAM,UAAU;;;;;EAM5B,YAAY;;;;;EAMZ;;;;EAKA,WAAW,MAAM;;;;;;;;;;;;;;;;;;;;;;cAuBN,SAAO,MAAA,0BAAA,eAAA,MAAA,cAAA;UAmIH,2BAA2B,MAAM,eAAe;;;;;EAK/D,WAAW,MAAM;;;;;;cAON,eAAa,MAAA,0BAAA,qBAAA,MAAA,cAAA;UAgCT,4BAA4B,MAAM,eAAe;;;;;EAKhE;;;;EAKA,WAAW,MAAM;;;;;;cAON,gBAAc,MAAA,0BAAA,sBAAA,MAAA,cAAA;UAyBV,yBAAyB,MAAM,eAAe;;;;EAI7D,WAAW,MAAM;;;;;;cAON,aAAW,MAAA,0BAAA,mBAAA,MAAA,cAAA;UAoBP,2BAA2B,MAAM,eAAe;;;;EAI/D,WAAW,MAAM;;;;;;cAON,eAAa,MAAA,0BAAA,qBAAA,MAAA,cAAA"}
1
+ {"version":3,"file":"Popover.d.mts","names":[],"sources":["../../src/Popover/Popover.tsx"],"mappings":";;;UA0BiB,qBAAqB,KACpC,MAAM,eAAe;;;;EAMrB;;;;EAKA;;;;EAKA,YAAY,MAAM,UAAU;;;;;EAM5B,YAAY;;;;;EAMZ;;;;EAKA,WAAW,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;cA4BN,SAAO,MAAA,0BAAA,eAAA,MAAA,cAAA;UAqEH,2BAA2B,MAAM,eAAe;;;;;EAK/D,WAAW,MAAM;;;;;;cAON,eAAa,MAAA,0BAAA,qBAAA,MAAA,cAAA;UAgCT,4BAA4B,MAAM,eAAe;;;;;EAKhE;;;;EAKA,WAAW,MAAM;;;;;;;cAQN,gBAAc,MAAA,0BAAA,sBAAA,MAAA,cAAA;UAyCV,yBAAyB,MAAM,eAAe;;;;EAI7D,WAAW,MAAM;;;;;;cAON,aAAW,MAAA,0BAAA,mBAAA,MAAA,cAAA;UAoBP,2BAA2B,MAAM,eAAe;;;;EAI/D,WAAW,MAAM;;;;;;cAON,eAAa,MAAA,0BAAA,qBAAA,MAAA,cAAA"}
@@ -1,80 +1,37 @@
1
1
  import { Icon } from "../Icon/Icon2.mjs";
2
2
  import { Button } from "../Button/Button2.mjs";
3
+ import { usePopover } from "../utils/popover.mjs";
3
4
  /* empty css */
4
5
  import React from "react";
5
6
  import { classNames } from "@dsn-starter-kit/core";
6
7
  import { jsx, jsxs } from "react/jsx-runtime";
7
8
  //#region src/Popover/Popover.tsx
8
- const GAP = 4;
9
- function positionPopover(popover, trigger, placement) {
10
- const triggerRect = trigger.getBoundingClientRect();
11
- const popoverRect = {
12
- width: popover.offsetWidth,
13
- height: popover.offsetHeight
14
- };
15
- const isRTL = document.documentElement.dir === "rtl" || getComputedStyle(trigger).direction === "rtl";
16
- let top;
17
- let left;
18
- switch (placement) {
19
- case "bottom":
20
- top = triggerRect.bottom + GAP;
21
- left = isRTL ? triggerRect.right - popoverRect.width : triggerRect.left;
22
- break;
23
- case "top":
24
- top = triggerRect.top - popoverRect.height - GAP;
25
- left = isRTL ? triggerRect.right - popoverRect.width : triggerRect.left;
26
- break;
27
- case "end":
28
- top = triggerRect.top;
29
- left = isRTL ? triggerRect.left - popoverRect.width - GAP : triggerRect.right + GAP;
30
- break;
31
- case "start":
32
- top = triggerRect.top;
33
- left = isRTL ? triggerRect.right + GAP : triggerRect.left - popoverRect.width - GAP;
34
- break;
35
- default:
36
- top = triggerRect.bottom + GAP;
37
- left = triggerRect.left;
38
- }
39
- const margin = 8;
40
- left = Math.max(margin, Math.min(left, window.innerWidth - popoverRect.width - margin));
41
- top = Math.max(margin, Math.min(top, window.innerHeight - popoverRect.height - margin));
42
- popover.style.top = `${top}px`;
43
- popover.style.left = `${left}px`;
44
- popover.style.right = "auto";
45
- popover.style.bottom = "auto";
46
- }
47
- function focusFirstInteractive(container) {
48
- const selector = [
49
- "button:not(:disabled)",
50
- "a[href]",
51
- "input:not(:disabled)",
52
- "select:not(:disabled)",
53
- "textarea:not(:disabled)",
54
- "[tabindex]:not([tabindex=\"-1\"])"
55
- ].join(", ");
56
- const first = container.querySelector(selector);
57
- if (first) first.focus();
58
- else container.focus();
59
- }
60
- const PopoverContext = React.createContext({ headingId: "" });
9
+ const PopoverContext = React.createContext({
10
+ headingId: "",
11
+ headingRef: { current: null }
12
+ });
61
13
  /**
62
14
  * Popover component
63
- * Lichtgewicht, contextgebonden overlay verankerd aan een triggerelement.
15
+ * Niet-modaal dialoogvenster verankerd aan een triggerelement
16
+ * (`role="dialog"`, `aria-modal="false"`).
64
17
  *
65
18
  * Gebaseerd op de HTML Popover API (`popover="auto"`) voor ingebakken
66
19
  * light-dismiss en top-layer gedrag. Positionering via JavaScript.
67
20
  *
21
+ * Voor een lijst met acties of links: gebruik `PopoverMenu`. Een lijst is
22
+ * geen dialoogvenster.
23
+ *
68
24
  * @example
69
25
  * ```tsx
70
26
  * const triggerRef = useRef<HTMLButtonElement>(null);
71
27
  * const [isOpen, setIsOpen] = useState(false);
72
28
  *
73
- * <Button ref={triggerRef} onClick={() => setIsOpen(true)}>Opties</Button>
74
- * <Popover isOpen={isOpen} onClose={() => setIsOpen(false)} triggerRef={triggerRef} label="Opties">
75
- * <PopoverBody>
76
- * <Menu>...</Menu>
77
- * </PopoverBody>
29
+ * <Button ref={triggerRef} onClick={() => setIsOpen((v) => !v)}>Filters</Button>
30
+ * <Popover isOpen={isOpen} onClose={() => setIsOpen(false)} triggerRef={triggerRef}>
31
+ * <PopoverHeader>
32
+ * <PopoverHeading>Filters</PopoverHeading>
33
+ * </PopoverHeader>
34
+ * <PopoverBody>...</PopoverBody>
78
35
  * </Popover>
79
36
  * ```
80
37
  */
@@ -82,71 +39,21 @@ const Popover = React.forwardRef(({ className, isOpen, onClose, triggerRef, plac
82
39
  const internalRef = React.useRef(null);
83
40
  const popoverRef = ref ?? internalRef;
84
41
  const headingId = React.useId();
85
- React.useEffect(() => {
86
- const el = popoverRef.current;
87
- if (!el) return;
88
- if (isOpen) {
89
- el.showPopover();
90
- const trigger = triggerRef.current;
91
- if (trigger) positionPopover(el, trigger, placement);
92
- focusFirstInteractive(el);
93
- } else try {
94
- el.hidePopover();
95
- } catch {}
96
- }, [
97
- isOpen,
98
- placement,
42
+ const headingRef = React.useRef(null);
43
+ usePopover({
44
+ popoverRef,
99
45
  triggerRef,
100
- popoverRef
101
- ]);
102
- React.useEffect(() => {
103
- const trigger = triggerRef.current;
104
- if (!trigger) return;
105
- trigger.setAttribute("aria-expanded", isOpen ? "true" : "false");
106
- return () => {
107
- trigger.setAttribute("aria-expanded", "false");
108
- };
109
- }, [isOpen, triggerRef]);
110
- const onCloseRef = React.useRef(onClose);
111
- onCloseRef.current = onClose;
112
- React.useEffect(() => {
113
- const el = popoverRef.current;
114
- if (!el) return;
115
- const handleToggle = (event) => {
116
- if (event.newState === "closed") {
117
- onCloseRef.current?.();
118
- triggerRef.current?.focus();
119
- }
120
- };
121
- el.addEventListener("toggle", handleToggle);
122
- return () => {
123
- el.removeEventListener("toggle", handleToggle);
124
- };
125
- }, [triggerRef, popoverRef]);
126
- React.useEffect(() => {
127
- if (!isOpen) return;
128
- const el = popoverRef.current;
129
- if (!el) return;
130
- const handlePointerDown = (event) => {
131
- const target = event.target;
132
- if (el.contains(target) || triggerRef.current?.contains(target)) return;
133
- onCloseRef.current?.();
134
- triggerRef.current?.focus();
135
- };
136
- document.addEventListener("pointerdown", handlePointerDown);
137
- return () => {
138
- document.removeEventListener("pointerdown", handlePointerDown);
139
- };
140
- }, [
141
46
  isOpen,
142
- triggerRef,
143
- popoverRef
144
- ]);
47
+ onClose,
48
+ placement,
49
+ initialFocusRef: headingRef
50
+ });
145
51
  const classes = classNames("dsn-popover", `dsn-popover--placement-${placement}`, className);
146
52
  const ariaProps = label ? { "aria-label": label } : { "aria-labelledby": headingId };
147
53
  return /* @__PURE__ */ jsx(PopoverContext.Provider, {
148
54
  value: {
149
55
  headingId,
56
+ headingRef,
150
57
  onClose
151
58
  },
152
59
  children: /* @__PURE__ */ jsx("div", {
@@ -190,14 +97,21 @@ PopoverHeader.displayName = "PopoverHeader";
190
97
  /**
191
98
  * PopoverHeading
192
99
  * De heading van de popover. ID wordt automatisch gegenereerd voor aria-labelledby.
100
+ * Krijgt `tabindex="-1"` zodat de Popover er bij openen de focus op kan zetten.
193
101
  */
194
102
  const PopoverHeading = React.forwardRef(({ className, level = 2, children, ...props }, ref) => {
195
- const { headingId } = React.useContext(PopoverContext);
103
+ const { headingId, headingRef } = React.useContext(PopoverContext);
196
104
  const Tag = `h${level}`;
105
+ const setRef = React.useCallback((node) => {
106
+ headingRef.current = node;
107
+ if (typeof ref === "function") ref(node);
108
+ else if (ref) ref.current = node;
109
+ }, [headingRef, ref]);
197
110
  return /* @__PURE__ */ jsx(Tag, {
198
- ref,
111
+ ref: setRef,
199
112
  id: headingId,
200
113
  className: classNames("dsn-popover-heading", className),
114
+ tabIndex: -1,
201
115
  ...props,
202
116
  children
203
117
  });
@@ -1 +1 @@
1
- {"version":3,"file":"Popover2.mjs","names":["toggleEvent"],"sources":["../../src/Popover/Popover.tsx"],"sourcesContent":["import React from 'react';\nimport { classNames } from '@dsn-starter-kit/core';\nimport { Button } from '../Button';\nimport { Icon } from '../Icon';\nimport './Popover.css';\n\n/*\n * React 18 ondersteunt het `popover`-attribuut en `onToggle` niet natively in de\n * JSX-types. Module-uitbreiding voegt de ontbrekende types toe.\n */\ndeclare module 'react' {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n interface HTMLAttributes<T> {\n popover?: 'auto' | 'manual' | '' | undefined;\n popoverTarget?: string | undefined;\n popoverTargetAction?: 'hide' | 'show' | 'toggle' | undefined;\n }\n}\n\n/*\n * HTMLElement.showPopover / hidePopover zijn nog niet in alle TypeScript DOM-libs.\n * Declaratie toegevoegd voor veilig gebruik.\n */\ndeclare global {\n interface HTMLElement {\n showPopover(): void;\n hidePopover(): void;\n }\n}\n\n// =============================================================================\n// Types\n// =============================================================================\n\ntype Placement = 'top' | 'bottom' | 'start' | 'end';\n\n// =============================================================================\n// Positioneringshelper\n// =============================================================================\n\nconst GAP = 4; // px afstand tussen trigger en popover\n\nfunction positionPopover(\n popover: HTMLElement,\n trigger: HTMLElement,\n placement: Placement\n): void {\n const triggerRect = trigger.getBoundingClientRect();\n // offsetWidth/offsetHeight — onaangepast door CSS transforms (zoals @starting-style scale).\n // getBoundingClientRect geeft de getransformeerde afmetingen, wat leidt tot onjuiste clamping.\n const popoverWidth = popover.offsetWidth;\n const popoverHeight = popover.offsetHeight;\n const popoverRect = { width: popoverWidth, height: popoverHeight };\n const isRTL =\n document.documentElement.dir === 'rtl' ||\n getComputedStyle(trigger).direction === 'rtl';\n\n let top: number;\n let left: number;\n\n switch (placement) {\n case 'bottom':\n top = triggerRect.bottom + GAP;\n left = isRTL ? triggerRect.right - popoverRect.width : triggerRect.left;\n break;\n case 'top':\n top = triggerRect.top - popoverRect.height - GAP;\n left = isRTL ? triggerRect.right - popoverRect.width : triggerRect.left;\n break;\n case 'end':\n top = triggerRect.top;\n left = isRTL\n ? triggerRect.left - popoverRect.width - GAP\n : triggerRect.right + GAP;\n break;\n case 'start':\n top = triggerRect.top;\n left = isRTL\n ? triggerRect.right + GAP\n : triggerRect.left - popoverRect.width - GAP;\n break;\n default:\n top = triggerRect.bottom + GAP;\n left = triggerRect.left;\n }\n\n // Klamp binnen het viewport (8px marge)\n const margin = 8;\n left = Math.max(\n margin,\n Math.min(left, window.innerWidth - popoverRect.width - margin)\n );\n top = Math.max(\n margin,\n Math.min(top, window.innerHeight - popoverRect.height - margin)\n );\n\n popover.style.top = `${top}px`;\n popover.style.left = `${left}px`;\n popover.style.right = 'auto';\n popover.style.bottom = 'auto';\n}\n\n// Zet focus op het eerste interactieve element in de popover\nfunction focusFirstInteractive(container: HTMLElement): void {\n const selector = [\n 'button:not(:disabled)',\n 'a[href]',\n 'input:not(:disabled)',\n 'select:not(:disabled)',\n 'textarea:not(:disabled)',\n '[tabindex]:not([tabindex=\"-1\"])',\n ].join(', ');\n const first = container.querySelector<HTMLElement>(selector);\n if (first) {\n first.focus();\n } else {\n container.focus();\n }\n}\n\n// =============================================================================\n// Context\n// =============================================================================\n\ninterface PopoverContextValue {\n headingId: string;\n onClose?: () => void;\n}\n\nconst PopoverContext = React.createContext<PopoverContextValue>({\n headingId: '',\n});\n\n// =============================================================================\n// Popover (root)\n// =============================================================================\n\nexport interface PopoverProps extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n 'children'\n> {\n /**\n * Bepaalt of de popover getoond wordt.\n */\n isOpen: boolean;\n\n /**\n * Callback bij sluiten (Escape, klik buiten, sluitknop in header).\n */\n onClose?: () => void;\n\n /**\n * Referentie naar het triggerelement voor positionering en focus-herstel.\n */\n triggerRef: React.RefObject<HTMLElement>;\n\n /**\n * Gewenste plaatsing relatief aan het triggerelement.\n * @default 'bottom'\n */\n placement?: Placement;\n\n /**\n * Toegankelijke naam voor popovers zonder `PopoverHeader`/`PopoverHeading`.\n * Gebruik `aria-labelledby` via context wanneer er wél een heading is.\n */\n label?: string;\n\n /**\n * Subcomponenten: `PopoverHeader`, `PopoverBody`, `PopoverFooter`\n */\n children?: React.ReactNode;\n}\n\n/**\n * Popover component\n * Lichtgewicht, contextgebonden overlay verankerd aan een triggerelement.\n *\n * Gebaseerd op de HTML Popover API (`popover=\"auto\"`) voor ingebakken\n * light-dismiss en top-layer gedrag. Positionering via JavaScript.\n *\n * @example\n * ```tsx\n * const triggerRef = useRef<HTMLButtonElement>(null);\n * const [isOpen, setIsOpen] = useState(false);\n *\n * <Button ref={triggerRef} onClick={() => setIsOpen(true)}>Opties</Button>\n * <Popover isOpen={isOpen} onClose={() => setIsOpen(false)} triggerRef={triggerRef} label=\"Opties\">\n * <PopoverBody>\n * <Menu>...</Menu>\n * </PopoverBody>\n * </Popover>\n * ```\n */\nexport const Popover = React.forwardRef<HTMLDivElement, PopoverProps>(\n (\n {\n className,\n isOpen,\n onClose,\n triggerRef,\n placement = 'bottom',\n label,\n children,\n ...props\n },\n ref\n ) => {\n const internalRef = React.useRef<HTMLDivElement>(null);\n const popoverRef = (ref as React.RefObject<HTMLDivElement>) ?? internalRef;\n const headingId = React.useId();\n\n // Toon/verberg popover via de Popover API\n React.useEffect(() => {\n const el = popoverRef.current;\n if (!el) return;\n\n if (isOpen) {\n el.showPopover();\n const trigger = triggerRef.current;\n if (trigger) {\n positionPopover(el, trigger, placement);\n }\n focusFirstInteractive(el);\n } else {\n // hidePopover gooit een fout als de popover al gesloten is\n try {\n el.hidePopover();\n } catch {\n // popover was al gesloten — geen actie nodig\n }\n }\n }, [isOpen, placement, triggerRef, popoverRef]);\n\n // Synchroniseer aria-expanded op het triggerelement\n React.useEffect(() => {\n const trigger = triggerRef.current;\n if (!trigger) return;\n trigger.setAttribute('aria-expanded', isOpen ? 'true' : 'false');\n return () => {\n trigger.setAttribute('aria-expanded', 'false');\n };\n }, [isOpen, triggerRef]);\n\n // Verwerk het toggle-event van de Popover API (Escape / klik buiten binnen hetzelfde document)\n // onToggle is niet beschikbaar als JSX-prop op div in React 18 — gebruik addEventListener.\n // Gebruik een ref voor onClose zodat de listener stabiel blijft en nooit stale is.\n const onCloseRef = React.useRef(onClose);\n onCloseRef.current = onClose;\n\n React.useEffect(() => {\n const el = popoverRef.current;\n if (!el) return;\n\n const handleToggle = (event: Event) => {\n const toggleEvent = event as Event & { newState?: string };\n if (toggleEvent.newState === 'closed') {\n onCloseRef.current?.();\n triggerRef.current?.focus();\n }\n };\n\n el.addEventListener('toggle', handleToggle);\n return () => {\n el.removeEventListener('toggle', handleToggle);\n };\n }, [triggerRef, popoverRef]);\n\n // Fallback light-dismiss via pointerdown — vangt klikken buiten het popover op\n // in contexten waar de Popover API's native light-dismiss niet bereikbaar is\n // (bijv. iframe-grenzen, of oudere browsers zonder Popover API).\n React.useEffect(() => {\n if (!isOpen) return;\n const el = popoverRef.current;\n if (!el) return;\n\n const handlePointerDown = (event: PointerEvent) => {\n const target = event.target as Node;\n // Sluit niet als er op de popover zelf of het triggerelement geklikt wordt\n if (el.contains(target) || triggerRef.current?.contains(target)) return;\n onCloseRef.current?.();\n triggerRef.current?.focus();\n };\n\n document.addEventListener('pointerdown', handlePointerDown);\n return () => {\n document.removeEventListener('pointerdown', handlePointerDown);\n };\n }, [isOpen, triggerRef, popoverRef]);\n\n const classes = classNames(\n 'dsn-popover',\n `dsn-popover--placement-${placement}`,\n className\n );\n\n const ariaProps = label\n ? { 'aria-label': label }\n : { 'aria-labelledby': headingId };\n\n return (\n <PopoverContext.Provider value={{ headingId, onClose }}>\n <div\n ref={popoverRef}\n popover=\"auto\"\n className={classes}\n role=\"dialog\"\n aria-modal=\"false\"\n tabIndex={-1}\n {...ariaProps}\n {...props}\n >\n {children}\n </div>\n </PopoverContext.Provider>\n );\n }\n);\n\nPopover.displayName = 'Popover';\n\n// =============================================================================\n// PopoverHeader\n// =============================================================================\n\nexport interface PopoverHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Inhoud van de header — doorgaans een `PopoverHeading`.\n * De sluitknop wordt automatisch geïnjecteerd.\n */\n children?: React.ReactNode;\n}\n\n/**\n * PopoverHeader\n * De headerstrook van de popover met heading en sluitknop.\n */\nexport const PopoverHeader = React.forwardRef<\n HTMLDivElement,\n PopoverHeaderProps\n>(({ className, children, ...props }, ref) => {\n const { onClose } = React.useContext(PopoverContext);\n\n return (\n <div\n ref={ref}\n className={classNames('dsn-popover__header', className)}\n {...props}\n >\n {children}\n <Button\n variant=\"subtle\"\n size=\"small\"\n iconOnly\n onClick={onClose}\n iconStart={<Icon name=\"x\" aria-hidden />}\n >\n Sluiten\n </Button>\n </div>\n );\n});\n\nPopoverHeader.displayName = 'PopoverHeader';\n\n// =============================================================================\n// PopoverHeading\n// =============================================================================\n\nexport interface PopoverHeadingProps extends React.HTMLAttributes<HTMLHeadingElement> {\n /**\n * Semantisch heading-niveau. Kies op basis van documenthiërarchie.\n * @default 2\n */\n level?: 1 | 2 | 3 | 4 | 5 | 6;\n\n /**\n * De zichtbare heading-tekst.\n */\n children?: React.ReactNode;\n}\n\n/**\n * PopoverHeading\n * De heading van de popover. ID wordt automatisch gegenereerd voor aria-labelledby.\n */\nexport const PopoverHeading = React.forwardRef<\n HTMLHeadingElement,\n PopoverHeadingProps\n>(({ className, level = 2, children, ...props }, ref) => {\n const { headingId } = React.useContext(PopoverContext);\n const Tag = `h${level}` as React.ElementType;\n\n return (\n <Tag\n ref={ref}\n id={headingId}\n className={classNames('dsn-popover-heading', className)}\n {...props}\n >\n {children}\n </Tag>\n );\n});\n\nPopoverHeading.displayName = 'PopoverHeading';\n\n// =============================================================================\n// PopoverBody\n// =============================================================================\n\nexport interface PopoverBodyProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * De hoofdinhoud van de popover — willekeurige slot-content.\n */\n children?: React.ReactNode;\n}\n\n/**\n * PopoverBody\n * De inhoudssectie van de popover.\n */\nexport const PopoverBody = React.forwardRef<HTMLDivElement, PopoverBodyProps>(\n ({ className, children, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={classNames('dsn-popover__body', className)}\n {...props}\n >\n {children}\n </div>\n );\n }\n);\n\nPopoverBody.displayName = 'PopoverBody';\n\n// =============================================================================\n// PopoverFooter\n// =============================================================================\n\nexport interface PopoverFooterProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Actieknoppen of slotzin van de popover.\n */\n children?: React.ReactNode;\n}\n\n/**\n * PopoverFooter\n * De voettekst van de popover met actieknoppen.\n */\nexport const PopoverFooter = React.forwardRef<\n HTMLDivElement,\n PopoverFooterProps\n>(({ className, children, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={classNames('dsn-popover__footer', className)}\n {...props}\n >\n {children}\n </div>\n );\n});\n\nPopoverFooter.displayName = 'PopoverFooter';\n"],"mappings":"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"}
1
+ {"version":3,"file":"Popover2.mjs","names":[],"sources":["../../src/Popover/Popover.tsx"],"sourcesContent":["import React from 'react';\nimport { classNames } from '@dsn-starter-kit/core';\nimport { Button } from '../Button';\nimport { Icon } from '../Icon';\nimport { usePopover, type PopoverPlacement } from '../utils/popover';\nimport './Popover.css';\n\n// =============================================================================\n// Context\n// =============================================================================\n\ninterface PopoverContextValue {\n headingId: string;\n headingRef: React.MutableRefObject<HTMLHeadingElement | null>;\n onClose?: () => void;\n}\n\nconst PopoverContext = React.createContext<PopoverContextValue>({\n headingId: '',\n headingRef: { current: null },\n});\n\n// =============================================================================\n// Popover (root)\n// =============================================================================\n\nexport interface PopoverProps extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n 'children'\n> {\n /**\n * Bepaalt of de popover getoond wordt.\n */\n isOpen: boolean;\n\n /**\n * Callback bij sluiten (Escape, klik buiten, sluitknop in header).\n */\n onClose?: () => void;\n\n /**\n * Referentie naar het triggerelement voor positionering en focus-herstel.\n */\n triggerRef: React.RefObject<HTMLElement>;\n\n /**\n * Gewenste plaatsing relatief aan het triggerelement.\n * @default 'bottom'\n */\n placement?: PopoverPlacement;\n\n /**\n * Toegankelijke naam voor een popover zonder `PopoverHeader`/`PopoverHeading`.\n * Een zichtbare heading heeft de voorkeur; die koppelt via `aria-labelledby`.\n */\n label?: string;\n\n /**\n * Subcomponenten: `PopoverHeader`, `PopoverBody`, `PopoverFooter`\n */\n children?: React.ReactNode;\n}\n\n/**\n * Popover component\n * Niet-modaal dialoogvenster verankerd aan een triggerelement\n * (`role=\"dialog\"`, `aria-modal=\"false\"`).\n *\n * Gebaseerd op de HTML Popover API (`popover=\"auto\"`) voor ingebakken\n * light-dismiss en top-layer gedrag. Positionering via JavaScript.\n *\n * Voor een lijst met acties of links: gebruik `PopoverMenu`. Een lijst is\n * geen dialoogvenster.\n *\n * @example\n * ```tsx\n * const triggerRef = useRef<HTMLButtonElement>(null);\n * const [isOpen, setIsOpen] = useState(false);\n *\n * <Button ref={triggerRef} onClick={() => setIsOpen((v) => !v)}>Filters</Button>\n * <Popover isOpen={isOpen} onClose={() => setIsOpen(false)} triggerRef={triggerRef}>\n * <PopoverHeader>\n * <PopoverHeading>Filters</PopoverHeading>\n * </PopoverHeader>\n * <PopoverBody>...</PopoverBody>\n * </Popover>\n * ```\n */\nexport const Popover = React.forwardRef<HTMLDivElement, PopoverProps>(\n (\n {\n className,\n isOpen,\n onClose,\n triggerRef,\n placement = 'bottom',\n label,\n children,\n ...props\n },\n ref\n ) => {\n const internalRef = React.useRef<HTMLDivElement>(null);\n const popoverRef = (ref as React.RefObject<HTMLDivElement>) ?? internalRef;\n const headingId = React.useId();\n const headingRef = React.useRef<HTMLHeadingElement | null>(null);\n\n /*\n * Bij openen krijgt de heading de focus, niet de sluitknop: zo wordt de\n * titel als eerste voorgelezen, net als bij ModalDialog en Drawer. Zonder\n * heading krijgt de popover zelf de focus (die heeft `tabindex=\"-1\"`).\n * Zie docs/decisions/DR-2026-12-dialogs-focus-bij-openen-op-de-heading.md.\n */\n usePopover({\n popoverRef,\n triggerRef,\n isOpen,\n onClose,\n placement,\n initialFocusRef: headingRef,\n });\n\n const classes = classNames(\n 'dsn-popover',\n `dsn-popover--placement-${placement}`,\n className\n );\n\n const ariaProps = label\n ? { 'aria-label': label }\n : { 'aria-labelledby': headingId };\n\n return (\n <PopoverContext.Provider value={{ headingId, headingRef, onClose }}>\n <div\n ref={popoverRef}\n popover=\"auto\"\n className={classes}\n role=\"dialog\"\n aria-modal=\"false\"\n tabIndex={-1}\n {...ariaProps}\n {...props}\n >\n {children}\n </div>\n </PopoverContext.Provider>\n );\n }\n);\n\nPopover.displayName = 'Popover';\n\n// =============================================================================\n// PopoverHeader\n// =============================================================================\n\nexport interface PopoverHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Inhoud van de header — doorgaans een `PopoverHeading`.\n * De sluitknop wordt automatisch geïnjecteerd.\n */\n children?: React.ReactNode;\n}\n\n/**\n * PopoverHeader\n * De headerstrook van de popover met heading en sluitknop.\n */\nexport const PopoverHeader = React.forwardRef<\n HTMLDivElement,\n PopoverHeaderProps\n>(({ className, children, ...props }, ref) => {\n const { onClose } = React.useContext(PopoverContext);\n\n return (\n <div\n ref={ref}\n className={classNames('dsn-popover__header', className)}\n {...props}\n >\n {children}\n <Button\n variant=\"subtle\"\n size=\"small\"\n iconOnly\n onClick={onClose}\n iconStart={<Icon name=\"x\" aria-hidden />}\n >\n Sluiten\n </Button>\n </div>\n );\n});\n\nPopoverHeader.displayName = 'PopoverHeader';\n\n// =============================================================================\n// PopoverHeading\n// =============================================================================\n\nexport interface PopoverHeadingProps extends React.HTMLAttributes<HTMLHeadingElement> {\n /**\n * Semantisch heading-niveau. Kies op basis van documenthiërarchie.\n * @default 2\n */\n level?: 1 | 2 | 3 | 4 | 5 | 6;\n\n /**\n * De zichtbare heading-tekst.\n */\n children?: React.ReactNode;\n}\n\n/**\n * PopoverHeading\n * De heading van de popover. ID wordt automatisch gegenereerd voor aria-labelledby.\n * Krijgt `tabindex=\"-1\"` zodat de Popover er bij openen de focus op kan zetten.\n */\nexport const PopoverHeading = React.forwardRef<\n HTMLHeadingElement,\n PopoverHeadingProps\n>(({ className, level = 2, children, ...props }, ref) => {\n const { headingId, headingRef } = React.useContext(PopoverContext);\n const Tag = `h${level}` as React.ElementType;\n\n // De Popover heeft de heading nodig om er bij openen de focus op te zetten;\n // een eventuele meegegeven ref blijft daarnaast gewoon werken.\n const setRef = React.useCallback(\n (node: HTMLHeadingElement | null) => {\n headingRef.current = node;\n if (typeof ref === 'function') {\n ref(node);\n } else if (ref) {\n (ref as React.MutableRefObject<HTMLHeadingElement | null>).current =\n node;\n }\n },\n [headingRef, ref]\n );\n\n return (\n <Tag\n ref={setRef}\n id={headingId}\n className={classNames('dsn-popover-heading', className)}\n tabIndex={-1}\n {...props}\n >\n {children}\n </Tag>\n );\n});\n\nPopoverHeading.displayName = 'PopoverHeading';\n\n// =============================================================================\n// PopoverBody\n// =============================================================================\n\nexport interface PopoverBodyProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * De hoofdinhoud van de popover — willekeurige slot-content.\n */\n children?: React.ReactNode;\n}\n\n/**\n * PopoverBody\n * De inhoudssectie van de popover.\n */\nexport const PopoverBody = React.forwardRef<HTMLDivElement, PopoverBodyProps>(\n ({ className, children, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={classNames('dsn-popover__body', className)}\n {...props}\n >\n {children}\n </div>\n );\n }\n);\n\nPopoverBody.displayName = 'PopoverBody';\n\n// =============================================================================\n// PopoverFooter\n// =============================================================================\n\nexport interface PopoverFooterProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Actieknoppen of slotzin van de popover.\n */\n children?: React.ReactNode;\n}\n\n/**\n * PopoverFooter\n * De voettekst van de popover met actieknoppen.\n */\nexport const PopoverFooter = React.forwardRef<\n HTMLDivElement,\n PopoverFooterProps\n>(({ className, children, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={classNames('dsn-popover__footer', className)}\n {...props}\n >\n {children}\n </div>\n );\n});\n\nPopoverFooter.displayName = 'PopoverFooter';\n"],"mappings":";;;;;;;;AAiBA,MAAM,iBAAiB,MAAM,cAAmC;CAC9D,WAAW;CACX,YAAY,EAAE,SAAS,KAAK;AAC9B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;AAoED,MAAa,UAAU,MAAM,YAEzB,EACE,WACA,QACA,SACA,YACA,YAAY,UACZ,OACA,UACA,GAAG,SAEL,QACG;CACH,MAAM,cAAc,MAAM,OAAuB,IAAI;CACrD,MAAM,aAAc,OAA2C;CAC/D,MAAM,YAAY,MAAM,MAAM;CAC9B,MAAM,aAAa,MAAM,OAAkC,IAAI;CAQ/D,WAAW;EACT;EACA;EACA;EACA;EACA;EACA,iBAAiB;CACnB,CAAC;CAED,MAAM,UAAU,WACd,eACA,0BAA0B,aAC1B,SACF;CAEA,MAAM,YAAY,QACd,EAAE,cAAc,MAAM,IACtB,EAAE,mBAAmB,UAAU;CAEnC,OACE,oBAAC,eAAe,UAAhB;EAAyB,OAAO;GAAE;GAAW;GAAY;EAAQ;EAC/D,UAAA,oBAAC,OAAD;GACE,KAAK;GACL,SAAQ;GACR,WAAW;GACX,MAAK;GACL,cAAW;GACX,UAAU;GACV,GAAI;GACJ,GAAI;GAEH;EACE,CAAA;CACkB,CAAA;AAE7B,CACF;AAEA,QAAQ,cAAc;;;;;AAkBtB,MAAa,gBAAgB,MAAM,YAGhC,EAAE,WAAW,UAAU,GAAG,SAAS,QAAQ;CAC5C,MAAM,EAAE,YAAY,MAAM,WAAW,cAAc;CAEnD,OACE,qBAAC,OAAD;EACO;EACL,WAAW,WAAW,uBAAuB,SAAS;EACtD,GAAI;EAHN,UAAA,CAKG,UACD,oBAAC,QAAD;GACE,SAAQ;GACR,MAAK;GACL,UAAA;GACA,SAAS;GACT,WAAW,oBAAC,MAAD;IAAM,MAAK;IAAI,eAAA;GAAa,CAAA;GACxC,UAAA;EAEO,CAAA,CACL;;AAET,CAAC;AAED,cAAc,cAAc;;;;;;AAwB5B,MAAa,iBAAiB,MAAM,YAGjC,EAAE,WAAW,QAAQ,GAAG,UAAU,GAAG,SAAS,QAAQ;CACvD,MAAM,EAAE,WAAW,eAAe,MAAM,WAAW,cAAc;CACjE,MAAM,MAAM,IAAI;CAIhB,MAAM,SAAS,MAAM,aAClB,SAAoC;EACnC,WAAW,UAAU;EACrB,IAAI,OAAO,QAAQ,YACjB,IAAI,IAAI;OACH,IAAI,KACT,IAA2D,UACzD;CAEN,GACA,CAAC,YAAY,GAAG,CAClB;CAEA,OACE,oBAAC,KAAD;EACE,KAAK;EACL,IAAI;EACJ,WAAW,WAAW,uBAAuB,SAAS;EACtD,UAAU;EACV,GAAI;EAEH;CACE,CAAA;AAET,CAAC;AAED,eAAe,cAAc;;;;;AAiB7B,MAAa,cAAc,MAAM,YAC9B,EAAE,WAAW,UAAU,GAAG,SAAS,QAAQ;CAC1C,OACE,oBAAC,OAAD;EACO;EACL,WAAW,WAAW,qBAAqB,SAAS;EACpD,GAAI;EAEH;CACE,CAAA;AAET,CACF;AAEA,YAAY,cAAc;;;;;AAiB1B,MAAa,gBAAgB,MAAM,YAGhC,EAAE,WAAW,UAAU,GAAG,SAAS,QAAQ;CAC5C,OACE,oBAAC,OAAD;EACO;EACL,WAAW,WAAW,uBAAuB,SAAS;EACtD,GAAI;EAEH;CACE,CAAA;AAET,CAAC;AAED,cAAc,cAAc"}
@@ -0,0 +1,62 @@
1
+ import { PopoverPlacement } from "../utils/popover.mjs";
2
+ import React from "react";
3
+ //#region src/PopoverMenu/PopoverMenu.d.ts
4
+ interface PopoverMenuProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> {
5
+ /**
6
+ * Bepaalt of het menu getoond wordt.
7
+ */
8
+ isOpen: boolean;
9
+ /**
10
+ * Callback bij sluiten: Escape, klik buiten het menu, of het activeren van
11
+ * een `MenuButton` of `MenuLink`.
12
+ */
13
+ onClose?: () => void;
14
+ /**
15
+ * Referentie naar het triggerelement voor positionering, `aria-expanded`
16
+ * en focusherstel.
17
+ */
18
+ triggerRef: React.RefObject<HTMLElement>;
19
+ /**
20
+ * Gewenste plaatsing relatief aan het triggerelement.
21
+ * @default 'bottom'
22
+ */
23
+ placement?: PopoverPlacement;
24
+ /**
25
+ * Doorgegeven aan `Menu`: geeft aan dat de items een `iconStart` hebben.
26
+ * @default false
27
+ */
28
+ iconStart?: boolean;
29
+ /**
30
+ * `MenuButton`- en/of `MenuLink`-items
31
+ */
32
+ children?: React.ReactNode;
33
+ }
34
+ /**
35
+ * PopoverMenu component
36
+ * Lijst met acties of links in een zwevend paneel, geopend vanuit een
37
+ * triggerknop.
38
+ *
39
+ * Anders dan `Popover` is dit géén dialoogvenster: het paneel heeft geen rol,
40
+ * de inhoud is een gewone lijst (`<ul role="list">`). Ook bewust geen
41
+ * `role="menu"`: die rol belooft pijltjesnavigatie en zet screenreaders in
42
+ * een applicatiemodus. De context komt uit het label van de trigger.
43
+ *
44
+ * Het menu sluit zichzelf zodra een item wordt geactiveerd, en zet de focus
45
+ * dan terug op de trigger.
46
+ *
47
+ * @example
48
+ * ```tsx
49
+ * const triggerRef = useRef<HTMLButtonElement>(null);
50
+ * const [isOpen, setIsOpen] = useState(false);
51
+ *
52
+ * <Button ref={triggerRef} onClick={() => setIsOpen((v) => !v)}>Acties</Button>
53
+ * <PopoverMenu isOpen={isOpen} onClose={() => setIsOpen(false)} triggerRef={triggerRef}>
54
+ * <MenuButton onClick={edit}>Bewerken</MenuButton>
55
+ * <MenuButton onClick={duplicate}>Dupliceren</MenuButton>
56
+ * </PopoverMenu>
57
+ * ```
58
+ */
59
+ declare const PopoverMenu: React.ForwardRefExoticComponent<PopoverMenuProps & React.RefAttributes<HTMLDivElement>>;
60
+ //#endregion
61
+ export { PopoverMenu, PopoverMenuProps };
62
+ //# sourceMappingURL=PopoverMenu.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PopoverMenu.d.mts","names":[],"sources":["../../src/PopoverMenu/PopoverMenu.tsx"],"mappings":";;;UAaiB,yBAAyB,KACxC,MAAM,eAAe;;;;EAMrB;;;;;EAMA;;;;;EAMA,YAAY,MAAM,UAAU;;;;;EAM5B,YAAY;;;;;EAMZ;;;;EAKA,WAAW,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;cA4BN,aAAW,MAAA,0BAAA,mBAAA,MAAA,cAAA"}
@@ -0,0 +1,66 @@
1
+ import { Menu } from "../Menu/Menu2.mjs";
2
+ import { usePopover } from "../utils/popover.mjs";
3
+ /* empty css */
4
+ import React from "react";
5
+ import { classNames } from "@dsn-starter-kit/core";
6
+ import { jsx } from "react/jsx-runtime";
7
+ //#region src/PopoverMenu/PopoverMenu.tsx
8
+ const ITEM_SELECTOR = ".dsn-menu-button__button, .dsn-menu-link__link";
9
+ /**
10
+ * PopoverMenu component
11
+ * Lijst met acties of links in een zwevend paneel, geopend vanuit een
12
+ * triggerknop.
13
+ *
14
+ * Anders dan `Popover` is dit géén dialoogvenster: het paneel heeft geen rol,
15
+ * de inhoud is een gewone lijst (`<ul role="list">`). Ook bewust geen
16
+ * `role="menu"`: die rol belooft pijltjesnavigatie en zet screenreaders in
17
+ * een applicatiemodus. De context komt uit het label van de trigger.
18
+ *
19
+ * Het menu sluit zichzelf zodra een item wordt geactiveerd, en zet de focus
20
+ * dan terug op de trigger.
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * const triggerRef = useRef<HTMLButtonElement>(null);
25
+ * const [isOpen, setIsOpen] = useState(false);
26
+ *
27
+ * <Button ref={triggerRef} onClick={() => setIsOpen((v) => !v)}>Acties</Button>
28
+ * <PopoverMenu isOpen={isOpen} onClose={() => setIsOpen(false)} triggerRef={triggerRef}>
29
+ * <MenuButton onClick={edit}>Bewerken</MenuButton>
30
+ * <MenuButton onClick={duplicate}>Dupliceren</MenuButton>
31
+ * </PopoverMenu>
32
+ * ```
33
+ */
34
+ const PopoverMenu = React.forwardRef(({ className, isOpen, onClose, triggerRef, placement = "bottom", iconStart = false, onClick, children, ...props }, ref) => {
35
+ const internalRef = React.useRef(null);
36
+ const popoverRef = ref ?? internalRef;
37
+ usePopover({
38
+ popoverRef,
39
+ triggerRef,
40
+ isOpen,
41
+ onClose,
42
+ placement
43
+ });
44
+ const handleClick = (event) => {
45
+ onClick?.(event);
46
+ if (event.target.closest(ITEM_SELECTOR)) onClose?.();
47
+ };
48
+ const classes = classNames("dsn-popover-menu", `dsn-popover-menu--placement-${placement}`, className);
49
+ return /* @__PURE__ */ jsx("div", {
50
+ ref: popoverRef,
51
+ popover: "auto",
52
+ className: classes,
53
+ onClick: handleClick,
54
+ ...props,
55
+ children: /* @__PURE__ */ jsx(Menu, {
56
+ role: "list",
57
+ iconStart,
58
+ children
59
+ })
60
+ });
61
+ });
62
+ PopoverMenu.displayName = "PopoverMenu";
63
+ //#endregion
64
+ export { PopoverMenu };
65
+
66
+ //# sourceMappingURL=PopoverMenu2.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PopoverMenu2.mjs","names":[],"sources":["../../src/PopoverMenu/PopoverMenu.tsx"],"sourcesContent":["import React from 'react';\nimport { classNames } from '@dsn-starter-kit/core';\nimport { Menu } from '../Menu';\nimport { usePopover, type PopoverPlacement } from '../utils/popover';\nimport './PopoverMenu.css';\n\n/*\n * Klikken op deze elementen activeert een item en sluit het menu. De\n * uitklapknop van een MenuLink met sub-items staat er bewust niet bij: die\n * toont meer items en is dus geen keuze.\n */\nconst ITEM_SELECTOR = '.dsn-menu-button__button, .dsn-menu-link__link';\n\nexport interface PopoverMenuProps extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n 'children'\n> {\n /**\n * Bepaalt of het menu getoond wordt.\n */\n isOpen: boolean;\n\n /**\n * Callback bij sluiten: Escape, klik buiten het menu, of het activeren van\n * een `MenuButton` of `MenuLink`.\n */\n onClose?: () => void;\n\n /**\n * Referentie naar het triggerelement voor positionering, `aria-expanded`\n * en focusherstel.\n */\n triggerRef: React.RefObject<HTMLElement>;\n\n /**\n * Gewenste plaatsing relatief aan het triggerelement.\n * @default 'bottom'\n */\n placement?: PopoverPlacement;\n\n /**\n * Doorgegeven aan `Menu`: geeft aan dat de items een `iconStart` hebben.\n * @default false\n */\n iconStart?: boolean;\n\n /**\n * `MenuButton`- en/of `MenuLink`-items\n */\n children?: React.ReactNode;\n}\n\n/**\n * PopoverMenu component\n * Lijst met acties of links in een zwevend paneel, geopend vanuit een\n * triggerknop.\n *\n * Anders dan `Popover` is dit géén dialoogvenster: het paneel heeft geen rol,\n * de inhoud is een gewone lijst (`<ul role=\"list\">`). Ook bewust geen\n * `role=\"menu\"`: die rol belooft pijltjesnavigatie en zet screenreaders in\n * een applicatiemodus. De context komt uit het label van de trigger.\n *\n * Het menu sluit zichzelf zodra een item wordt geactiveerd, en zet de focus\n * dan terug op de trigger.\n *\n * @example\n * ```tsx\n * const triggerRef = useRef<HTMLButtonElement>(null);\n * const [isOpen, setIsOpen] = useState(false);\n *\n * <Button ref={triggerRef} onClick={() => setIsOpen((v) => !v)}>Acties</Button>\n * <PopoverMenu isOpen={isOpen} onClose={() => setIsOpen(false)} triggerRef={triggerRef}>\n * <MenuButton onClick={edit}>Bewerken</MenuButton>\n * <MenuButton onClick={duplicate}>Dupliceren</MenuButton>\n * </PopoverMenu>\n * ```\n */\nexport const PopoverMenu = React.forwardRef<HTMLDivElement, PopoverMenuProps>(\n (\n {\n className,\n isOpen,\n onClose,\n triggerRef,\n placement = 'bottom',\n iconStart = false,\n onClick,\n children,\n ...props\n },\n ref\n ) => {\n const internalRef = React.useRef<HTMLDivElement>(null);\n const popoverRef = (ref as React.RefObject<HTMLDivElement>) ?? internalRef;\n\n usePopover({ popoverRef, triggerRef, isOpen, onClose, placement });\n\n // Sluit na het activeren van een item. Het eigen onClick van het item is\n // dan al uitgevoerd, want dit event bubbelt vanaf het item omhoog.\n const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {\n onClick?.(event);\n const target = event.target as Element;\n if (target.closest(ITEM_SELECTOR)) {\n onClose?.();\n }\n };\n\n const classes = classNames(\n 'dsn-popover-menu',\n `dsn-popover-menu--placement-${placement}`,\n className\n );\n\n return (\n <div\n ref={popoverRef}\n popover=\"auto\"\n className={classes}\n onClick={handleClick}\n {...props}\n >\n <Menu role=\"list\" iconStart={iconStart}>\n {children}\n </Menu>\n </div>\n );\n }\n);\n\nPopoverMenu.displayName = 'PopoverMenu';\n"],"mappings":";;;;;;;AAWA,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;AAkEtB,MAAa,cAAc,MAAM,YAE7B,EACE,WACA,QACA,SACA,YACA,YAAY,UACZ,YAAY,OACZ,SACA,UACA,GAAG,SAEL,QACG;CACH,MAAM,cAAc,MAAM,OAAuB,IAAI;CACrD,MAAM,aAAc,OAA2C;CAE/D,WAAW;EAAE;EAAY;EAAY;EAAQ;EAAS;CAAU,CAAC;CAIjE,MAAM,eAAe,UAA4C;EAC/D,UAAU,KAAK;EAEf,IADe,MAAM,OACV,QAAQ,aAAa,GAC9B,UAAU;CAEd;CAEA,MAAM,UAAU,WACd,oBACA,+BAA+B,aAC/B,SACF;CAEA,OACE,oBAAC,OAAD;EACE,KAAK;EACL,SAAQ;EACR,WAAW;EACX,SAAS;EACT,GAAI;EAEJ,UAAA,oBAAC,MAAD;GAAM,MAAK;GAAkB;GAC1B;EACG,CAAA;CACH,CAAA;AAET,CACF;AAEA,YAAY,cAAc"}
@@ -0,0 +1,2 @@
1
+ import { PopoverMenu, PopoverMenuProps } from "./PopoverMenu.mjs";
2
+ export { PopoverMenu, type PopoverMenuProps };
@@ -1,6 +1,11 @@
1
1
  import React from "react";
2
- import { FormControlWidth } from "@dsn-starter-kit/core";
2
+ import { FormControlInlineSize } from "@dsn-starter-kit/core";
3
3
  //#region src/SearchInput/SearchInput.d.ts
4
+ /**
5
+ * Inline-size variants offered by SearchInput. xs and sm are left out: they
6
+ * are too narrow for the content this field is meant for.
7
+ */
8
+ type SearchInputInlineSize = Exclude<FormControlInlineSize, 'xs' | 'sm'>;
4
9
  interface SearchInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {
5
10
  /**
6
11
  * Whether the input is in an invalid state
@@ -8,10 +13,10 @@ interface SearchInputProps extends Omit<React.InputHTMLAttributes<HTMLInputEleme
8
13
  */
9
14
  invalid?: boolean;
10
15
  /**
11
- * Width variant for the input
12
- * @default undefined (uses default max-width from form-control)
16
+ * Inline-size variant for the input (md, lg, xl or full)
17
+ * @default undefined (uses default max-inline-size from form-control)
13
18
  */
14
- width?: FormControlWidth;
19
+ inlineSize?: SearchInputInlineSize;
15
20
  /**
16
21
  * Additional CSS class names
17
22
  */
@@ -37,5 +42,5 @@ interface SearchInputProps extends Omit<React.InputHTMLAttributes<HTMLInputEleme
37
42
  */
38
43
  declare const SearchInput: React.ForwardRefExoticComponent<SearchInputProps & React.RefAttributes<HTMLInputElement>>;
39
44
  //#endregion
40
- export { SearchInput, SearchInputProps };
45
+ export { SearchInput, SearchInputInlineSize, SearchInputProps };
41
46
  //# sourceMappingURL=SearchInput.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SearchInput.d.mts","names":[],"sources":["../../src/SearchInput/SearchInput.tsx"],"mappings":";;;UAKiB,yBAAyB,KACxC,MAAM,oBAAoB;;;;;EAO1B;;;;;EAMA,QAAQ;;;;EAKR;;;;;;;;;;;;;;;;;;;;cAqBW,aAAW,MAAA,0BAAA,mBAAA,MAAA,cAAA"}
1
+ {"version":3,"file":"SearchInput.d.mts","names":[],"sources":["../../src/SearchInput/SearchInput.tsx"],"mappings":";;;;;;;KASY,wBAAwB,QAAQ;UAE3B,yBAAyB,KACxC,MAAM,oBAAoB;;;;;EAO1B;;;;;EAMA,aAAa;;;;EAKb;;;;;;;;;;;;;;;;;;;;cAqBW,aAAW,MAAA,0BAAA,mBAAA,MAAA,cAAA"}
@@ -22,8 +22,8 @@ import { jsx, jsxs } from "react/jsx-runtime";
22
22
  * <SearchInput invalid aria-invalid="true" aria-describedby="error" />
23
23
  * ```
24
24
  */
25
- const SearchInput = React.forwardRef(({ className, invalid, width, ...props }, ref) => {
26
- const wrapperClasses = classNames("dsn-search-input-wrapper", width && `dsn-search-input-wrapper--width-${width}`);
25
+ const SearchInput = React.forwardRef(({ className, invalid, inlineSize, ...props }, ref) => {
26
+ const wrapperClasses = classNames("dsn-search-input-wrapper", inlineSize && `dsn-search-input-wrapper--inline-size-${inlineSize}`);
27
27
  const inputClasses = classNames("dsn-text-input", "dsn-search-input", className);
28
28
  return /* @__PURE__ */ jsxs("div", {
29
29
  className: wrapperClasses,
@@ -1 +1 @@
1
- {"version":3,"file":"SearchInput2.mjs","names":[],"sources":["../../src/SearchInput/SearchInput.tsx"],"sourcesContent":["import React from 'react';\nimport { classNames, FormControlWidth } from '@dsn-starter-kit/core';\nimport { Icon } from '../Icon';\nimport './SearchInput.css';\n\nexport interface SearchInputProps extends Omit<\n React.InputHTMLAttributes<HTMLInputElement>,\n 'type'\n> {\n /**\n * Whether the input is in an invalid state\n * @default false\n */\n invalid?: boolean;\n\n /**\n * Width variant for the input\n * @default undefined (uses default max-width from form-control)\n */\n width?: FormControlWidth;\n\n /**\n * Additional CSS class names\n */\n className?: string;\n}\n\n/**\n * Search Input component\n * Search input with a non-interactive search icon positioned at inline-start.\n * The icon has the same color as the input text (dsn-text-input-color).\n *\n * @example\n * ```tsx\n * // Basic usage\n * <SearchInput placeholder=\"Zoeken...\" />\n *\n * // With label\n * <FormFieldLabel htmlFor=\"search\">Zoeken</FormFieldLabel>\n * <SearchInput id=\"search\" />\n *\n * // Invalid state\n * <SearchInput invalid aria-invalid=\"true\" aria-describedby=\"error\" />\n * ```\n */\nexport const SearchInput = React.forwardRef<HTMLInputElement, SearchInputProps>(\n ({ className, invalid, width, ...props }, ref) => {\n const wrapperClasses = classNames(\n 'dsn-search-input-wrapper',\n width && `dsn-search-input-wrapper--width-${width}`\n );\n const inputClasses = classNames(\n 'dsn-text-input',\n 'dsn-search-input',\n className\n );\n\n return (\n <div className={wrapperClasses}>\n <Icon name=\"search\" className=\"dsn-search-input__icon\" aria-hidden />\n <input\n ref={ref}\n type=\"search\"\n className={inputClasses}\n aria-invalid={invalid || undefined}\n {...props}\n />\n </div>\n );\n }\n);\n\nSearchInput.displayName = 'SearchInput';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA6CA,MAAa,cAAc,MAAM,YAC9B,EAAE,WAAW,SAAS,OAAO,GAAG,SAAS,QAAQ;CAChD,MAAM,iBAAiB,WACrB,4BACA,SAAS,mCAAmC,OAC9C;CACA,MAAM,eAAe,WACnB,kBACA,oBACA,SACF;CAEA,OACE,qBAAC,OAAD;EAAK,WAAW;EAAhB,UAAA,CACE,oBAAC,MAAD;GAAM,MAAK;GAAS,WAAU;GAAyB,eAAA;EAAa,CAAA,GACpE,oBAAC,SAAD;GACO;GACL,MAAK;GACL,WAAW;GACX,gBAAc,WAAW,KAAA;GACzB,GAAI;EACL,CAAA,CACE;;AAET,CACF;AAEA,YAAY,cAAc"}
1
+ {"version":3,"file":"SearchInput2.mjs","names":[],"sources":["../../src/SearchInput/SearchInput.tsx"],"sourcesContent":["import React from 'react';\nimport { classNames, FormControlInlineSize } from '@dsn-starter-kit/core';\nimport { Icon } from '../Icon';\nimport './SearchInput.css';\n\n/**\n * Inline-size variants offered by SearchInput. xs and sm are left out: they\n * are too narrow for the content this field is meant for.\n */\nexport type SearchInputInlineSize = Exclude<FormControlInlineSize, 'xs' | 'sm'>;\n\nexport interface SearchInputProps extends Omit<\n React.InputHTMLAttributes<HTMLInputElement>,\n 'type'\n> {\n /**\n * Whether the input is in an invalid state\n * @default false\n */\n invalid?: boolean;\n\n /**\n * Inline-size variant for the input (md, lg, xl or full)\n * @default undefined (uses default max-inline-size from form-control)\n */\n inlineSize?: SearchInputInlineSize;\n\n /**\n * Additional CSS class names\n */\n className?: string;\n}\n\n/**\n * Search Input component\n * Search input with a non-interactive search icon positioned at inline-start.\n * The icon has the same color as the input text (dsn-text-input-color).\n *\n * @example\n * ```tsx\n * // Basic usage\n * <SearchInput placeholder=\"Zoeken...\" />\n *\n * // With label\n * <FormFieldLabel htmlFor=\"search\">Zoeken</FormFieldLabel>\n * <SearchInput id=\"search\" />\n *\n * // Invalid state\n * <SearchInput invalid aria-invalid=\"true\" aria-describedby=\"error\" />\n * ```\n */\nexport const SearchInput = React.forwardRef<HTMLInputElement, SearchInputProps>(\n ({ className, invalid, inlineSize, ...props }, ref) => {\n const wrapperClasses = classNames(\n 'dsn-search-input-wrapper',\n inlineSize && `dsn-search-input-wrapper--inline-size-${inlineSize}`\n );\n const inputClasses = classNames(\n 'dsn-text-input',\n 'dsn-search-input',\n className\n );\n\n return (\n <div className={wrapperClasses}>\n <Icon name=\"search\" className=\"dsn-search-input__icon\" aria-hidden />\n <input\n ref={ref}\n type=\"search\"\n className={inputClasses}\n aria-invalid={invalid || undefined}\n {...props}\n />\n </div>\n );\n }\n);\n\nSearchInput.displayName = 'SearchInput';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAmDA,MAAa,cAAc,MAAM,YAC9B,EAAE,WAAW,SAAS,YAAY,GAAG,SAAS,QAAQ;CACrD,MAAM,iBAAiB,WACrB,4BACA,cAAc,yCAAyC,YACzD;CACA,MAAM,eAAe,WACnB,kBACA,oBACA,SACF;CAEA,OACE,qBAAC,OAAD;EAAK,WAAW;EAAhB,UAAA,CACE,oBAAC,MAAD;GAAM,MAAK;GAAS,WAAU;GAAyB,eAAA;EAAa,CAAA,GACpE,oBAAC,SAAD;GACO;GACL,MAAK;GACL,WAAW;GACX,gBAAc,WAAW,KAAA;GACzB,GAAI;EACL,CAAA,CACE;;AAET,CACF;AAEA,YAAY,cAAc"}
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { FormControlWidth } from "@dsn-starter-kit/core";
2
+ import { FormControlInlineSize } from "@dsn-starter-kit/core";
3
3
  //#region src/Select/Select.d.ts
4
4
  interface SelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {
5
5
  /**
@@ -8,10 +8,10 @@ interface SelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {
8
8
  */
9
9
  invalid?: boolean;
10
10
  /**
11
- * Width variant for the select
12
- * @default undefined (uses default max-width from form-control)
11
+ * Inline-size variant for the select
12
+ * @default undefined (uses default max-inline-size from form-control)
13
13
  */
14
- width?: FormControlWidth;
14
+ inlineSize?: FormControlInlineSize;
15
15
  /**
16
16
  * Additional CSS class names
17
17
  */
@@ -40,8 +40,8 @@ interface SelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {
40
40
  * </Select>
41
41
  * </FormField>
42
42
  *
43
- * // Width variant
44
- * <Select width="lg">...</Select>
43
+ * // Inline-size variant
44
+ * <Select inlineSize="lg">...</Select>
45
45
  *
46
46
  * // Invalid state
47
47
  * <Select invalid aria-invalid="true" aria-describedby="error">...</Select>
@@ -1 +1 @@
1
- {"version":3,"file":"Select.d.mts","names":[],"sources":["../../src/Select/Select.tsx"],"mappings":";;;UAKiB,oBAAoB,MAAM,qBAAqB;;;;;EAK9D;;;;;EAMA,QAAQ;;;;EAKR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAiCW,QAAM,MAAA,0BAAA,cAAA,MAAA,cAAA"}
1
+ {"version":3,"file":"Select.d.mts","names":[],"sources":["../../src/Select/Select.tsx"],"mappings":";;;UAKiB,oBAAoB,MAAM,qBAAqB;;;;;EAK9D;;;;;EAMA,aAAa;;;;EAKb;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAiCW,QAAM,MAAA,0BAAA,cAAA,MAAA,cAAA"}