@dsn-starter-kit/components-react 3.3.0 → 3.5.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/dist/DateInput/DateInput.d.mts.map +1 -1
- package/dist/DateInput/DateInput2.mjs +0 -1
- package/dist/DateInput/DateInput2.mjs.map +1 -1
- package/dist/Drawer/Drawer.d.mts +11 -3
- package/dist/Drawer/Drawer.d.mts.map +1 -1
- package/dist/Drawer/Drawer2.mjs +28 -4
- package/dist/Drawer/Drawer2.mjs.map +1 -1
- package/dist/ModalDialog/ModalDialog.d.mts +12 -2
- package/dist/ModalDialog/ModalDialog.d.mts.map +1 -1
- package/dist/ModalDialog/ModalDialog2.mjs +35 -8
- package/dist/ModalDialog/ModalDialog2.mjs.map +1 -1
- package/dist/PageHeader/PageHeader2.mjs +1 -0
- package/dist/PageHeader/PageHeader2.mjs.map +1 -1
- package/dist/SearchInput/SearchInput.d.mts.map +1 -1
- package/dist/SearchInput/SearchInput2.mjs +0 -1
- package/dist/SearchInput/SearchInput2.mjs.map +1 -1
- package/dist/Select/Select.d.mts.map +1 -1
- package/dist/Select/Select2.mjs +0 -1
- package/dist/Select/Select2.mjs.map +1 -1
- package/dist/TimeInput/TimeInput.d.mts.map +1 -1
- package/dist/TimeInput/TimeInput2.mjs +0 -1
- package/dist/TimeInput/TimeInput2.mjs.map +1 -1
- package/dist/index.cjs +157 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +4661 -3168
- package/dist/index.d.cts +23 -5
- package/dist/index.d.cts.map +1 -1
- package/dist/utils/focusTrap.mjs +99 -0
- package/dist/utils/focusTrap.mjs.map +1 -0
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DateInput.d.mts","names":[],"sources":["../../src/DateInput/DateInput.tsx"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"DateInput.d.mts","names":[],"sources":["../../src/DateInput/DateInput.tsx"],"mappings":";;UAMiB,uBAAuB,KACtC,MAAM,oBAAoB;;;;;EAO1B;;;;EAKA;;;;;;;;;;;;;;;;;;;;;;;cAwBW,WAAS,MAAA,0BAAA,iBAAA,MAAA,cAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DateInput2.mjs","names":[],"sources":["../../src/DateInput/DateInput.tsx"],"sourcesContent":["import React from 'react';\nimport { classNames } from '@dsn-starter-kit/core';\nimport { Button } from '../Button';\nimport { Icon } from '../Icon';\nimport '
|
|
1
|
+
{"version":3,"file":"DateInput2.mjs","names":[],"sources":["../../src/DateInput/DateInput.tsx"],"sourcesContent":["import React from 'react';\nimport { classNames } from '@dsn-starter-kit/core';\nimport { Button } from '../Button';\nimport { Icon } from '../Icon';\nimport './DateInput.css';\n\nexport interface DateInputProps 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 * Additional CSS class names\n */\n className?: string;\n}\n\n/**\n * Date Input component\n * Date input with an interactive calendar button at inline-end that opens the native date picker.\n * Fixed width (sm) — not configurable, as date inputs have a predictable content width.\n *\n * @example\n * ```tsx\n * // Basic usage\n * <DateInput />\n *\n * // With label\n * <FormFieldLabel htmlFor=\"date\">Datum</FormFieldLabel>\n * <DateInput id=\"date\" />\n *\n * // With default value\n * <DateInput defaultValue=\"2025-03-15\" />\n *\n * // Invalid state\n * <DateInput invalid aria-invalid=\"true\" aria-describedby=\"error\" />\n * ```\n */\nexport const DateInput = React.forwardRef<HTMLInputElement, DateInputProps>(\n ({ className, invalid, disabled, readOnly, ...props }, ref) => {\n const inputRef = React.useRef<HTMLInputElement>(null);\n\n // Merge external ref with internal ref\n const handleRef = React.useCallback(\n (node: HTMLInputElement | null) => {\n (inputRef as React.MutableRefObject<HTMLInputElement | null>).current =\n node;\n if (typeof ref === 'function') {\n ref(node);\n } else if (ref) {\n (ref as React.MutableRefObject<HTMLInputElement | null>).current =\n node;\n }\n },\n [ref]\n );\n\n const handleButtonClick = () => {\n if (inputRef.current && 'showPicker' in inputRef.current) {\n inputRef.current.showPicker();\n }\n };\n\n const inputClasses = classNames(\n 'dsn-text-input',\n 'dsn-date-input',\n className\n );\n\n return (\n <div className=\"dsn-date-input-wrapper\">\n <input\n ref={handleRef}\n type=\"date\"\n className={inputClasses}\n aria-invalid={invalid || undefined}\n disabled={disabled}\n readOnly={readOnly}\n {...props}\n />\n {!disabled && !readOnly && (\n <Button\n variant=\"subtle\"\n size=\"small\"\n iconOnly\n iconStart={<Icon name=\"calendar-event\" aria-hidden />}\n className=\"dsn-date-input__button\"\n onClick={handleButtonClick}\n >\n Datumkiezer openen\n </Button>\n )}\n </div>\n );\n }\n);\n\nDateInput.displayName = 'DateInput';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,MAAa,YAAY,MAAM,YAC5B,EAAE,WAAW,SAAS,UAAU,UAAU,GAAG,SAAS,QAAQ;CAC7D,MAAM,WAAW,MAAM,OAAyB,IAAI;CAGpD,MAAM,YAAY,MAAM,aACrB,SAAkC;EACjC,SAA8D,UAC5D;EACF,IAAI,OAAO,QAAQ,YACjB,IAAI,IAAI;OACH,IAAI,KACT,IAAyD,UACvD;CAEN,GACA,CAAC,GAAG,CACN;CAEA,MAAM,0BAA0B;EAC9B,IAAI,SAAS,WAAW,gBAAgB,SAAS,SAC/C,SAAS,QAAQ,WAAW;CAEhC;CAEA,MAAM,eAAe,WACnB,kBACA,kBACA,SACF;CAEA,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACE,oBAAC,SAAD;GACE,KAAK;GACL,MAAK;GACL,WAAW;GACX,gBAAc,WAAW,KAAA;GACf;GACA;GACV,GAAI;EACL,CAAA,GACA,CAAC,YAAY,CAAC,YACb,oBAAC,QAAD;GACE,SAAQ;GACR,MAAK;GACL,UAAA;GACA,WAAW,oBAAC,MAAD;IAAM,MAAK;IAAiB,eAAA;GAAa,CAAA;GACpD,WAAU;GACV,SAAS;GACV,UAAA;EAEO,CAAA,CAEP;;AAET,CACF;AAEA,UAAU,cAAc"}
|
package/dist/Drawer/Drawer.d.mts
CHANGED
|
@@ -13,12 +13,19 @@ interface DrawerProps extends Omit<React.HTMLAttributes<HTMLDialogElement>, 'chi
|
|
|
13
13
|
/**
|
|
14
14
|
* Modaal of non-modaal gedrag.
|
|
15
15
|
* - `true` (standaard): opent via `.showModal()` — achtergrond geblokkeerd,
|
|
16
|
-
*
|
|
17
|
-
* - `false`: opent via `.show()` — achtergrond blijft interactief
|
|
18
|
-
* Escape sluit via `keydown`-listener.
|
|
16
|
+
* focus blijft binnen het paneel, Escape sluit via `cancel`-event.
|
|
17
|
+
* - `false`: opent via `.show()` — achtergrond blijft interactief en de
|
|
18
|
+
* gebruiker kan er met Tab naartoe, Escape sluit via `keydown`-listener.
|
|
19
19
|
* @default true
|
|
20
20
|
*/
|
|
21
21
|
modal?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Ref naar het element dat het zijpaneel opent. Wanneer je die meegeeft,
|
|
24
|
+
* houdt het zijpaneel `aria-expanded` op dat element synchroon met de
|
|
25
|
+
* open-staat: `false` wanneer het paneel gesloten is, `true` wanneer het
|
|
26
|
+
* open staat.
|
|
27
|
+
*/
|
|
28
|
+
triggerRef?: React.RefObject<HTMLElement | null>;
|
|
22
29
|
/**
|
|
23
30
|
* De kant van de viewport vanwaar het zijpaneel inschuift.
|
|
24
31
|
* @default "right"
|
|
@@ -82,6 +89,7 @@ interface DrawerHeadingProps extends React.HTMLAttributes<HTMLHeadingElement> {
|
|
|
82
89
|
/**
|
|
83
90
|
* DrawerHeading
|
|
84
91
|
* De heading van het zijpaneel. ID wordt automatisch gegenereerd voor aria-labelledby.
|
|
92
|
+
* Krijgt `tabindex="-1"` zodat de Drawer er bij openen de focus op kan zetten.
|
|
85
93
|
*/
|
|
86
94
|
declare const DrawerHeading: React.ForwardRefExoticComponent<DrawerHeadingProps & React.RefAttributes<HTMLHeadingElement>>;
|
|
87
95
|
interface DrawerBodyProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Drawer.d.mts","names":[],"sources":["../../src/Drawer/Drawer.tsx"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"Drawer.d.mts","names":[],"sources":["../../src/Drawer/Drawer.tsx"],"mappings":";;UA0BiB,oBAAoB,KACnC,MAAM,eAAe;;;;EAMrB;;;;;EAMA;;;;;;;;;EAUA;;;;;;;EAQA,aAAa,MAAM,UAAU;;;;;EAM7B;;;;;EAMA,WAAW,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA6BN,QAAM,MAAA,0BAAA,cAAA,MAAA,cAAA;UAuHF,0BAA0B,MAAM,eAAe;;;;EAI9D,WAAW,MAAM;;;;;;cAON,cAAY,MAAA,0BAAA,oBAAA,MAAA,cAAA;UA+BR,2BAA2B,MAAM,eAAe;;;;;EAK/D;;;;EAKA,WAAW,MAAM;;;;;;;cAQN,eAAa,MAAA,0BAAA,qBAAA,MAAA,cAAA;UAyCT,wBAAwB,MAAM,eAAe;;;;EAI5D,WAAW,MAAM;;;;;;cAON,YAAU,MAAA,0BAAA,kBAAA,MAAA,cAAA;UAoBN,0BAA0B,MAAM,eAAe;;;;EAI9D,WAAW,MAAM;;;;;;cAON,cAAY,MAAA,0BAAA,oBAAA,MAAA,cAAA"}
|
package/dist/Drawer/Drawer2.mjs
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { Icon } from "../Icon/Icon2.mjs";
|
|
2
2
|
import { Button } from "../Button/Button2.mjs";
|
|
3
|
+
import { useFocusTrap } from "../utils/focusTrap.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/Drawer/Drawer.tsx
|
|
8
|
-
const DrawerContext = React.createContext({
|
|
9
|
+
const DrawerContext = React.createContext({
|
|
10
|
+
headingId: "",
|
|
11
|
+
headingRef: { current: null }
|
|
12
|
+
});
|
|
9
13
|
/**
|
|
10
14
|
* Drawer component
|
|
11
15
|
* Zijpaneel dat vanuit links of rechts de viewport inschuift.
|
|
@@ -32,22 +36,33 @@ const DrawerContext = React.createContext({ headingId: "" });
|
|
|
32
36
|
* </Drawer>
|
|
33
37
|
* ```
|
|
34
38
|
*/
|
|
35
|
-
const Drawer = React.forwardRef(({ className, isOpen, onClose, modal = true, side = "right", children, ...props }, ref) => {
|
|
39
|
+
const Drawer = React.forwardRef(({ className, isOpen, onClose, modal = true, triggerRef, side = "right", children, ...props }, ref) => {
|
|
36
40
|
const internalRef = React.useRef(null);
|
|
37
41
|
const dialogRef = ref ?? internalRef;
|
|
38
42
|
const headingId = React.useId();
|
|
43
|
+
const headingRef = React.useRef(null);
|
|
39
44
|
React.useEffect(() => {
|
|
40
45
|
const dialog = dialogRef.current;
|
|
41
46
|
if (!dialog) return;
|
|
42
47
|
if (isOpen && !dialog.open) {
|
|
43
48
|
if (modal) dialog.showModal();
|
|
44
49
|
else dialog.show();
|
|
50
|
+
(headingRef.current ?? dialog).focus();
|
|
45
51
|
} else if (!isOpen && dialog.open) dialog.close();
|
|
46
52
|
}, [
|
|
47
53
|
isOpen,
|
|
48
54
|
modal,
|
|
49
55
|
dialogRef
|
|
50
56
|
]);
|
|
57
|
+
useFocusTrap(dialogRef, isOpen && modal);
|
|
58
|
+
React.useEffect(() => {
|
|
59
|
+
const trigger = triggerRef?.current;
|
|
60
|
+
if (!trigger) return;
|
|
61
|
+
trigger.setAttribute("aria-expanded", isOpen ? "true" : "false");
|
|
62
|
+
return () => {
|
|
63
|
+
trigger.setAttribute("aria-expanded", "false");
|
|
64
|
+
};
|
|
65
|
+
}, [isOpen, triggerRef]);
|
|
51
66
|
React.useEffect(() => {
|
|
52
67
|
if (modal) return;
|
|
53
68
|
const dialog = dialogRef.current;
|
|
@@ -71,6 +86,7 @@ const Drawer = React.forwardRef(({ className, isOpen, onClose, modal = true, sid
|
|
|
71
86
|
return /* @__PURE__ */ jsx(DrawerContext.Provider, {
|
|
72
87
|
value: {
|
|
73
88
|
headingId,
|
|
89
|
+
headingRef,
|
|
74
90
|
onClose
|
|
75
91
|
},
|
|
76
92
|
children: /* @__PURE__ */ jsx("dialog", {
|
|
@@ -78,6 +94,7 @@ const Drawer = React.forwardRef(({ className, isOpen, onClose, modal = true, sid
|
|
|
78
94
|
className: classes,
|
|
79
95
|
"aria-labelledby": headingId,
|
|
80
96
|
onCancel: handleCancel,
|
|
97
|
+
tabIndex: -1,
|
|
81
98
|
...props,
|
|
82
99
|
children
|
|
83
100
|
})
|
|
@@ -111,14 +128,21 @@ DrawerHeader.displayName = "DrawerHeader";
|
|
|
111
128
|
/**
|
|
112
129
|
* DrawerHeading
|
|
113
130
|
* De heading van het zijpaneel. ID wordt automatisch gegenereerd voor aria-labelledby.
|
|
131
|
+
* Krijgt `tabindex="-1"` zodat de Drawer er bij openen de focus op kan zetten.
|
|
114
132
|
*/
|
|
115
133
|
const DrawerHeading = React.forwardRef(({ className, level = 2, children, ...props }, ref) => {
|
|
116
|
-
const { headingId } = React.useContext(DrawerContext);
|
|
134
|
+
const { headingId, headingRef } = React.useContext(DrawerContext);
|
|
117
135
|
const Tag = `h${level}`;
|
|
136
|
+
const setRef = React.useCallback((node) => {
|
|
137
|
+
headingRef.current = node;
|
|
138
|
+
if (typeof ref === "function") ref(node);
|
|
139
|
+
else if (ref) ref.current = node;
|
|
140
|
+
}, [headingRef, ref]);
|
|
118
141
|
return /* @__PURE__ */ jsx(Tag, {
|
|
119
|
-
ref,
|
|
142
|
+
ref: setRef,
|
|
120
143
|
id: headingId,
|
|
121
144
|
className: classNames("dsn-drawer-heading", className),
|
|
145
|
+
tabIndex: -1,
|
|
122
146
|
...props,
|
|
123
147
|
children
|
|
124
148
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Drawer2.mjs","names":[],"sources":["../../src/Drawer/Drawer.tsx"],"sourcesContent":["import React from 'react';\nimport { classNames } from '@dsn-starter-kit/core';\nimport { Button } from '../Button';\nimport { Icon } from '../Icon';\nimport './Drawer.css';\n\n// =============================================================================\n// Context\n// =============================================================================\n\ninterface DrawerContextValue {\n headingId: string;\n onClose?: () => void;\n}\n\nconst DrawerContext = React.createContext<DrawerContextValue>({\n headingId: '',\n});\n\n// =============================================================================\n// Drawer (root)\n// =============================================================================\n\nexport interface DrawerProps extends Omit<\n React.HTMLAttributes<HTMLDialogElement>,\n 'children'\n> {\n /**\n * Bepaalt of het zijpaneel getoond wordt.\n */\n isOpen: boolean;\n\n /**\n * Callback die wordt aangeroepen wanneer het zijpaneel sluit\n * (sluitknop, Escape-toets).\n */\n onClose?: () => void;\n\n /**\n * Modaal of non-modaal gedrag.\n * - `true` (standaard): opent via `.showModal()` — achtergrond geblokkeerd,\n * natieve focus-trap, Escape sluit via `cancel`-event.\n * - `false`: opent via `.show()` — achtergrond blijft interactief,\n * Escape sluit via `keydown`-listener.\n * @default true\n */\n modal?: boolean;\n\n /**\n * De kant van de viewport vanwaar het zijpaneel inschuift.\n * @default \"right\"\n */\n side?: 'right' | 'left';\n\n /**\n * De subcomponenten van het zijpaneel:\n * `DrawerHeader`, `DrawerBody`, `DrawerFooter`\n */\n children?: React.ReactNode;\n}\n\n/**\n * Drawer component\n * Zijpaneel dat vanuit links of rechts de viewport inschuift.\n *\n * Gebruik `modal={true}` (standaard) voor gefocuste taken waarbij de achtergrond\n * geblokkeerd moet worden. Gebruik `modal={false}` als de gebruiker de achtergrondpagina\n * nodig heeft voor context.\n *\n * @example\n * ```tsx\n * <Drawer isOpen={isOpen} onClose={() => setIsOpen(false)}>\n * <DrawerHeader>\n * <DrawerHeading>Filteropties</DrawerHeading>\n * </DrawerHeader>\n * <DrawerBody>\n * <Paragraph>Inhoud van het zijpaneel</Paragraph>\n * </DrawerBody>\n * <DrawerFooter>\n * <ActionGroup>\n * <Button variant=\"strong\" onClick={() => setIsOpen(false)}>Toepassen</Button>\n * <Button variant=\"default\" onClick={() => setIsOpen(false)}>Annuleren</Button>\n * </ActionGroup>\n * </DrawerFooter>\n * </Drawer>\n * ```\n */\nexport const Drawer = React.forwardRef<HTMLDialogElement, DrawerProps>(\n (\n {\n className,\n isOpen,\n onClose,\n modal = true,\n side = 'right',\n children,\n ...props\n },\n ref\n ) => {\n const internalRef = React.useRef<HTMLDialogElement>(null);\n const dialogRef =\n (ref as React.RefObject<HTMLDialogElement>) ?? internalRef;\n const headingId = React.useId();\n\n React.useEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) return;\n\n if (isOpen && !dialog.open) {\n if (modal) {\n dialog.showModal();\n } else {\n dialog.show();\n }\n } else if (!isOpen && dialog.open) {\n dialog.close();\n }\n }, [isOpen, modal, dialogRef]);\n\n // Non-modaal: handmatige Escape-afhandeling via keydown\n React.useEffect(() => {\n if (modal) return;\n const dialog = dialogRef.current;\n if (!dialog || !isOpen) return;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n onClose?.();\n }\n };\n\n dialog.addEventListener('keydown', handleKeyDown);\n return () => dialog.removeEventListener('keydown', handleKeyDown);\n }, [modal, isOpen, onClose, dialogRef]);\n\n // Modaal: native cancel-event (Escape via browser)\n const handleCancel = (event: React.SyntheticEvent<HTMLDialogElement>) => {\n event.preventDefault();\n onClose?.();\n };\n\n const classes = classNames(\n 'dsn-drawer',\n side === 'left' ? 'dsn-drawer--side-left' : 'dsn-drawer--side-right',\n className\n );\n\n return (\n <DrawerContext.Provider value={{ headingId, onClose }}>\n <dialog\n ref={dialogRef}\n className={classes}\n aria-labelledby={headingId}\n onCancel={handleCancel}\n {...props}\n >\n {children}\n </dialog>\n </DrawerContext.Provider>\n );\n }\n);\n\nDrawer.displayName = 'Drawer';\n\n// =============================================================================\n// DrawerHeader\n// =============================================================================\n\nexport interface DrawerHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Inhoud van de header — doorgaans een `DrawerHeading`\n */\n children?: React.ReactNode;\n}\n\n/**\n * DrawerHeader\n * De headerstrook van het zijpaneel met heading en sluitknop.\n */\nexport const DrawerHeader = React.forwardRef<HTMLDivElement, DrawerHeaderProps>(\n ({ className, children, ...props }, ref) => {\n const { onClose } = React.useContext(DrawerContext);\n\n return (\n <div\n ref={ref}\n className={classNames('dsn-drawer__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);\n\nDrawerHeader.displayName = 'DrawerHeader';\n\n// =============================================================================\n// DrawerHeading\n// =============================================================================\n\nexport interface DrawerHeadingProps extends React.HTMLAttributes<HTMLHeadingElement> {\n /**\n * Semantisch heading-niveau. Kies op basis van documenthiërarchie, niet visuele grootte.\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 * DrawerHeading\n * De heading van het zijpaneel. ID wordt automatisch gegenereerd voor aria-labelledby.\n */\nexport const DrawerHeading = React.forwardRef<\n HTMLHeadingElement,\n DrawerHeadingProps\n>(({ className, level = 2, children, ...props }, ref) => {\n const { headingId } = React.useContext(DrawerContext);\n const Tag = `h${level}` as React.ElementType;\n\n return (\n <Tag\n ref={ref}\n id={headingId}\n className={classNames('dsn-drawer-heading', className)}\n {...props}\n >\n {children}\n </Tag>\n );\n});\n\nDrawerHeading.displayName = 'DrawerHeading';\n\n// =============================================================================\n// DrawerBody\n// =============================================================================\n\nexport interface DrawerBodyProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * De hoofdinhoud van het zijpaneel\n */\n children?: React.ReactNode;\n}\n\n/**\n * DrawerBody\n * De scrollbare inhoudssectie van het zijpaneel met scroll-affordance schaduw.\n */\nexport const DrawerBody = React.forwardRef<HTMLDivElement, DrawerBodyProps>(\n ({ className, children, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={classNames('dsn-drawer__body', className)}\n {...props}\n >\n {children}\n </div>\n );\n }\n);\n\nDrawerBody.displayName = 'DrawerBody';\n\n// =============================================================================\n// DrawerFooter\n// =============================================================================\n\nexport interface DrawerFooterProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * De acties van het zijpaneel — doorgaans een `ActionGroup` met knoppen\n */\n children?: React.ReactNode;\n}\n\n/**\n * DrawerFooter\n * De voettekst van het zijpaneel met actieknoppen.\n */\nexport const DrawerFooter = React.forwardRef<HTMLDivElement, DrawerFooterProps>(\n ({ className, children, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={classNames('dsn-drawer__footer', className)}\n {...props}\n >\n {children}\n </div>\n );\n }\n);\n\nDrawerFooter.displayName = 'DrawerFooter';\n"],"mappings":";;;;;;;AAeA,MAAM,gBAAgB,MAAM,cAAkC,EAC5D,WAAW,GACb,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsED,MAAa,SAAS,MAAM,YAExB,EACE,WACA,QACA,SACA,QAAQ,MACR,OAAO,SACP,UACA,GAAG,SAEL,QACG;CACH,MAAM,cAAc,MAAM,OAA0B,IAAI;CACxD,MAAM,YACH,OAA8C;CACjD,MAAM,YAAY,MAAM,MAAM;CAE9B,MAAM,gBAAgB;EACpB,MAAM,SAAS,UAAU;EACzB,IAAI,CAAC,QAAQ;EAEb,IAAI,UAAU,CAAC,OAAO,MAAM;GAC1B,IAAI,OACF,OAAO,UAAU;QAEjB,OAAO,KAAK;EAEhB,OAAO,IAAI,CAAC,UAAU,OAAO,MAC3B,OAAO,MAAM;CAEjB,GAAG;EAAC;EAAQ;EAAO;CAAS,CAAC;CAG7B,MAAM,gBAAgB;EACpB,IAAI,OAAO;EACX,MAAM,SAAS,UAAU;EACzB,IAAI,CAAC,UAAU,CAAC,QAAQ;EAExB,MAAM,iBAAiB,UAAyB;GAC9C,IAAI,MAAM,QAAQ,UAChB,UAAU;EAEd;EAEA,OAAO,iBAAiB,WAAW,aAAa;EAChD,aAAa,OAAO,oBAAoB,WAAW,aAAa;CAClE,GAAG;EAAC;EAAO;EAAQ;EAAS;CAAS,CAAC;CAGtC,MAAM,gBAAgB,UAAmD;EACvE,MAAM,eAAe;EACrB,UAAU;CACZ;CAEA,MAAM,UAAU,WACd,cACA,SAAS,SAAS,0BAA0B,0BAC5C,SACF;CAEA,OACE,oBAAC,cAAc,UAAf;EAAwB,OAAO;GAAE;GAAW;EAAQ;EAClD,UAAA,oBAAC,UAAD;GACE,KAAK;GACL,WAAW;GACX,mBAAiB;GACjB,UAAU;GACV,GAAI;GAEH;EACK,CAAA;CACc,CAAA;AAE5B,CACF;AAEA,OAAO,cAAc;;;;;AAiBrB,MAAa,eAAe,MAAM,YAC/B,EAAE,WAAW,UAAU,GAAG,SAAS,QAAQ;CAC1C,MAAM,EAAE,YAAY,MAAM,WAAW,aAAa;CAElD,OACE,qBAAC,OAAD;EACO;EACL,WAAW,WAAW,sBAAsB,SAAS;EACrD,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,CACF;AAEA,aAAa,cAAc;;;;;AAuB3B,MAAa,gBAAgB,MAAM,YAGhC,EAAE,WAAW,QAAQ,GAAG,UAAU,GAAG,SAAS,QAAQ;CACvD,MAAM,EAAE,cAAc,MAAM,WAAW,aAAa;CACpD,MAAM,MAAM,IAAI;CAEhB,OACE,oBAAC,KAAD;EACO;EACL,IAAI;EACJ,WAAW,WAAW,sBAAsB,SAAS;EACrD,GAAI;EAEH;CACE,CAAA;AAET,CAAC;AAED,cAAc,cAAc;;;;;AAiB5B,MAAa,aAAa,MAAM,YAC7B,EAAE,WAAW,UAAU,GAAG,SAAS,QAAQ;CAC1C,OACE,oBAAC,OAAD;EACO;EACL,WAAW,WAAW,oBAAoB,SAAS;EACnD,GAAI;EAEH;CACE,CAAA;AAET,CACF;AAEA,WAAW,cAAc;;;;;AAiBzB,MAAa,eAAe,MAAM,YAC/B,EAAE,WAAW,UAAU,GAAG,SAAS,QAAQ;CAC1C,OACE,oBAAC,OAAD;EACO;EACL,WAAW,WAAW,sBAAsB,SAAS;EACrD,GAAI;EAEH;CACE,CAAA;AAET,CACF;AAEA,aAAa,cAAc"}
|
|
1
|
+
{"version":3,"file":"Drawer2.mjs","names":[],"sources":["../../src/Drawer/Drawer.tsx"],"sourcesContent":["import React from 'react';\nimport { classNames } from '@dsn-starter-kit/core';\nimport { Button } from '../Button';\nimport { Icon } from '../Icon';\nimport { useFocusTrap } from '../utils/focusTrap';\nimport './Drawer.css';\n\n// =============================================================================\n// Context\n// =============================================================================\n\ninterface DrawerContextValue {\n headingId: string;\n headingRef: React.MutableRefObject<HTMLHeadingElement | null>;\n onClose?: () => void;\n}\n\nconst DrawerContext = React.createContext<DrawerContextValue>({\n headingId: '',\n headingRef: { current: null },\n});\n\n// =============================================================================\n// Drawer (root)\n// =============================================================================\n\nexport interface DrawerProps extends Omit<\n React.HTMLAttributes<HTMLDialogElement>,\n 'children'\n> {\n /**\n * Bepaalt of het zijpaneel getoond wordt.\n */\n isOpen: boolean;\n\n /**\n * Callback die wordt aangeroepen wanneer het zijpaneel sluit\n * (sluitknop, Escape-toets).\n */\n onClose?: () => void;\n\n /**\n * Modaal of non-modaal gedrag.\n * - `true` (standaard): opent via `.showModal()` — achtergrond geblokkeerd,\n * focus blijft binnen het paneel, Escape sluit via `cancel`-event.\n * - `false`: opent via `.show()` — achtergrond blijft interactief en de\n * gebruiker kan er met Tab naartoe, Escape sluit via `keydown`-listener.\n * @default true\n */\n modal?: boolean;\n\n /**\n * Ref naar het element dat het zijpaneel opent. Wanneer je die meegeeft,\n * houdt het zijpaneel `aria-expanded` op dat element synchroon met de\n * open-staat: `false` wanneer het paneel gesloten is, `true` wanneer het\n * open staat.\n */\n triggerRef?: React.RefObject<HTMLElement | null>;\n\n /**\n * De kant van de viewport vanwaar het zijpaneel inschuift.\n * @default \"right\"\n */\n side?: 'right' | 'left';\n\n /**\n * De subcomponenten van het zijpaneel:\n * `DrawerHeader`, `DrawerBody`, `DrawerFooter`\n */\n children?: React.ReactNode;\n}\n\n/**\n * Drawer component\n * Zijpaneel dat vanuit links of rechts de viewport inschuift.\n *\n * Gebruik `modal={true}` (standaard) voor gefocuste taken waarbij de achtergrond\n * geblokkeerd moet worden. Gebruik `modal={false}` als de gebruiker de achtergrondpagina\n * nodig heeft voor context.\n *\n * @example\n * ```tsx\n * <Drawer isOpen={isOpen} onClose={() => setIsOpen(false)}>\n * <DrawerHeader>\n * <DrawerHeading>Filteropties</DrawerHeading>\n * </DrawerHeader>\n * <DrawerBody>\n * <Paragraph>Inhoud van het zijpaneel</Paragraph>\n * </DrawerBody>\n * <DrawerFooter>\n * <ActionGroup>\n * <Button variant=\"strong\" onClick={() => setIsOpen(false)}>Toepassen</Button>\n * <Button variant=\"default\" onClick={() => setIsOpen(false)}>Annuleren</Button>\n * </ActionGroup>\n * </DrawerFooter>\n * </Drawer>\n * ```\n */\nexport const Drawer = React.forwardRef<HTMLDialogElement, DrawerProps>(\n (\n {\n className,\n isOpen,\n onClose,\n modal = true,\n triggerRef,\n side = 'right',\n children,\n ...props\n },\n ref\n ) => {\n const internalRef = React.useRef<HTMLDialogElement>(null);\n const dialogRef =\n (ref as React.RefObject<HTMLDialogElement>) ?? internalRef;\n const headingId = React.useId();\n const headingRef = React.useRef<HTMLHeadingElement | null>(null);\n\n React.useEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) return;\n\n if (isOpen && !dialog.open) {\n if (modal) {\n dialog.showModal();\n } else {\n dialog.show();\n }\n\n /*\n * Zet de focus op de heading, niet op de sluitknop. De browser zet de\n * focus bij het openen zelf op het eerste focusbare element, en dat is\n * de sluitknop. VoiceOver in Safari leest die knop dan wel voor, maar\n * niet de titel uit `aria-labelledby`, en omdat de sluitknop na de\n * titel staat kom je de titel bij verder lezen ook niet meer tegen.\n *\n * Met de focus op de heading (die `tabindex=\"-1\"` heeft) wordt de\n * titel als eerste voorgelezen en loopt de leesvolgorde daarna door\n * naar de sluitknop en de inhoud. Dit is het patroon uit de ARIA\n * Authoring Practices, en het houdt DOM-volgorde en visuele volgorde\n * gelijk.\n */\n (headingRef.current ?? dialog).focus();\n } else if (!isOpen && dialog.open) {\n dialog.close();\n }\n }, [isOpen, modal, dialogRef]);\n\n /*\n * Expliciete focus-trap bovenop de native trap van `.showModal()`.\n * Zie `utils/focusTrap.ts` voor waarom de native trap niet volstaat.\n * Non-modaal krijgt bewust geen trap: daar hoort de gebruiker juist\n * tussen paneel en achtergrondpagina te kunnen tabben.\n */\n useFocusTrap(dialogRef, isOpen && modal);\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 // Non-modaal: handmatige Escape-afhandeling via keydown\n React.useEffect(() => {\n if (modal) return;\n const dialog = dialogRef.current;\n if (!dialog || !isOpen) return;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n onClose?.();\n }\n };\n\n dialog.addEventListener('keydown', handleKeyDown);\n return () => dialog.removeEventListener('keydown', handleKeyDown);\n }, [modal, isOpen, onClose, dialogRef]);\n\n // Modaal: native cancel-event (Escape via browser)\n const handleCancel = (event: React.SyntheticEvent<HTMLDialogElement>) => {\n event.preventDefault();\n onClose?.();\n };\n\n const classes = classNames(\n 'dsn-drawer',\n side === 'left' ? 'dsn-drawer--side-left' : 'dsn-drawer--side-right',\n className\n );\n\n return (\n <DrawerContext.Provider value={{ headingId, headingRef, onClose }}>\n <dialog\n ref={dialogRef}\n className={classes}\n aria-labelledby={headingId}\n onCancel={handleCancel}\n tabIndex={-1}\n {...props}\n >\n {children}\n </dialog>\n </DrawerContext.Provider>\n );\n }\n);\n\nDrawer.displayName = 'Drawer';\n\n// =============================================================================\n// DrawerHeader\n// =============================================================================\n\nexport interface DrawerHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Inhoud van de header — doorgaans een `DrawerHeading`\n */\n children?: React.ReactNode;\n}\n\n/**\n * DrawerHeader\n * De headerstrook van het zijpaneel met heading en sluitknop.\n */\nexport const DrawerHeader = React.forwardRef<HTMLDivElement, DrawerHeaderProps>(\n ({ className, children, ...props }, ref) => {\n const { onClose } = React.useContext(DrawerContext);\n\n return (\n <div\n ref={ref}\n className={classNames('dsn-drawer__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);\n\nDrawerHeader.displayName = 'DrawerHeader';\n\n// =============================================================================\n// DrawerHeading\n// =============================================================================\n\nexport interface DrawerHeadingProps extends React.HTMLAttributes<HTMLHeadingElement> {\n /**\n * Semantisch heading-niveau. Kies op basis van documenthiërarchie, niet visuele grootte.\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 * DrawerHeading\n * De heading van het zijpaneel. ID wordt automatisch gegenereerd voor aria-labelledby.\n * Krijgt `tabindex=\"-1\"` zodat de Drawer er bij openen de focus op kan zetten.\n */\nexport const DrawerHeading = React.forwardRef<\n HTMLHeadingElement,\n DrawerHeadingProps\n>(({ className, level = 2, children, ...props }, ref) => {\n const { headingId, headingRef } = React.useContext(DrawerContext);\n const Tag = `h${level}` as React.ElementType;\n\n // De Drawer 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-drawer-heading', className)}\n tabIndex={-1}\n {...props}\n >\n {children}\n </Tag>\n );\n});\n\nDrawerHeading.displayName = 'DrawerHeading';\n\n// =============================================================================\n// DrawerBody\n// =============================================================================\n\nexport interface DrawerBodyProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * De hoofdinhoud van het zijpaneel\n */\n children?: React.ReactNode;\n}\n\n/**\n * DrawerBody\n * De scrollbare inhoudssectie van het zijpaneel met scroll-affordance schaduw.\n */\nexport const DrawerBody = React.forwardRef<HTMLDivElement, DrawerBodyProps>(\n ({ className, children, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={classNames('dsn-drawer__body', className)}\n {...props}\n >\n {children}\n </div>\n );\n }\n);\n\nDrawerBody.displayName = 'DrawerBody';\n\n// =============================================================================\n// DrawerFooter\n// =============================================================================\n\nexport interface DrawerFooterProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * De acties van het zijpaneel — doorgaans een `ActionGroup` met knoppen\n */\n children?: React.ReactNode;\n}\n\n/**\n * DrawerFooter\n * De voettekst van het zijpaneel met actieknoppen.\n */\nexport const DrawerFooter = React.forwardRef<HTMLDivElement, DrawerFooterProps>(\n ({ className, children, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={classNames('dsn-drawer__footer', className)}\n {...props}\n >\n {children}\n </div>\n );\n }\n);\n\nDrawerFooter.displayName = 'DrawerFooter';\n"],"mappings":"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"}
|
|
@@ -11,6 +11,13 @@ interface ModalDialogProps extends Omit<React.HTMLAttributes<HTMLDialogElement>,
|
|
|
11
11
|
* (sluitknop, Escape-toets of klik buiten het venster).
|
|
12
12
|
*/
|
|
13
13
|
onClose?: () => void;
|
|
14
|
+
/**
|
|
15
|
+
* Ref naar het element dat het dialoogvenster opent. Wanneer je die meegeeft,
|
|
16
|
+
* houdt het dialoogvenster `aria-expanded` op dat element synchroon met de
|
|
17
|
+
* open-staat: `false` wanneer het venster gesloten is, `true` wanneer het
|
|
18
|
+
* open staat.
|
|
19
|
+
*/
|
|
20
|
+
triggerRef?: React.RefObject<HTMLElement | null>;
|
|
14
21
|
/**
|
|
15
22
|
* De subcomponenten van het dialoogvenster:
|
|
16
23
|
* `ModalDialogHeader`, `ModalDialogBody`, `ModalDialogFooter`
|
|
@@ -21,8 +28,10 @@ interface ModalDialogProps extends Omit<React.HTMLAttributes<HTMLDialogElement>,
|
|
|
21
28
|
* ModalDialog component
|
|
22
29
|
* Modaal dialoogvenster gebaseerd op het native `<dialog>` element.
|
|
23
30
|
*
|
|
24
|
-
* Gebruik altijd `.showModal()` (intern afgehandeld via `isOpen` prop)
|
|
25
|
-
* Dit garandeert
|
|
31
|
+
* Gebruik altijd `.showModal()` (intern afgehandeld via `isOpen` prop), nooit `.show()`.
|
|
32
|
+
* Dit garandeert aria-modal semantiek en het inert-attribuut op de achtergrond.
|
|
33
|
+
* De focus blijft binnen het venster via de native focus-trap plus een
|
|
34
|
+
* expliciete trap, en komt bij openen op de heading te staan.
|
|
26
35
|
*
|
|
27
36
|
* @example
|
|
28
37
|
* ```tsx
|
|
@@ -68,6 +77,7 @@ interface ModalDialogHeadingProps extends React.HTMLAttributes<HTMLHeadingElemen
|
|
|
68
77
|
/**
|
|
69
78
|
* ModalDialogHeading
|
|
70
79
|
* De heading van het dialoogvenster. ID wordt automatisch gegenereerd voor aria-labelledby.
|
|
80
|
+
* Krijgt `tabindex="-1"` zodat het ModalDialog er bij openen de focus op kan zetten.
|
|
71
81
|
*/
|
|
72
82
|
declare const ModalDialogHeading: React.ForwardRefExoticComponent<ModalDialogHeadingProps & React.RefAttributes<HTMLHeadingElement>>;
|
|
73
83
|
interface ModalDialogBodyProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ModalDialog.d.mts","names":[],"sources":["../../src/ModalDialog/ModalDialog.tsx"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"ModalDialog.d.mts","names":[],"sources":["../../src/ModalDialog/ModalDialog.tsx"],"mappings":";;UA0BiB,yBAAyB,KACxC,MAAM,eAAe;;;;;EAOrB;;;;;EAMA;;;;;;;EAQA,aAAa,MAAM,UAAU;;;;;EAM7B,WAAW,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA8BN,aAAW,MAAA,0BAAA,mBAAA,MAAA,cAAA;UA+EP,+BAA+B,MAAM,eAAe;;;;EAInE,WAAW,MAAM;;;;;;cAON,mBAAiB,MAAA,0BAAA,yBAAA,MAAA,cAAA;UAgCb,gCAAgC,MAAM,eAAe;;;;;EAKpE;;;;EAKA,WAAW,MAAM;;;;;;;cAQN,oBAAkB,MAAA,0BAAA,0BAAA,MAAA,cAAA;UAyCd,6BAA6B,MAAM,eAAe;;;;EAIjE,WAAW,MAAM;;;;;;cAON,iBAAe,MAAA,0BAAA,uBAAA,MAAA,cAAA;UAqBX,+BAA+B,MAAM,eAAe;;;;EAInE,WAAW,MAAM;;;;;;cAON,mBAAiB,MAAA,0BAAA,yBAAA,MAAA,cAAA"}
|
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
import { Icon } from "../Icon/Icon2.mjs";
|
|
2
2
|
import { Button } from "../Button/Button2.mjs";
|
|
3
|
+
import { useFocusTrap } from "../utils/focusTrap.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/ModalDialog/ModalDialog.tsx
|
|
8
|
-
const ModalDialogContext = React.createContext({
|
|
9
|
+
const ModalDialogContext = React.createContext({
|
|
10
|
+
headingId: "",
|
|
11
|
+
headingRef: { current: null }
|
|
12
|
+
});
|
|
9
13
|
/**
|
|
10
14
|
* ModalDialog component
|
|
11
15
|
* Modaal dialoogvenster gebaseerd op het native `<dialog>` element.
|
|
12
16
|
*
|
|
13
|
-
* Gebruik altijd `.showModal()` (intern afgehandeld via `isOpen` prop)
|
|
14
|
-
* Dit garandeert
|
|
17
|
+
* Gebruik altijd `.showModal()` (intern afgehandeld via `isOpen` prop), nooit `.show()`.
|
|
18
|
+
* Dit garandeert aria-modal semantiek en het inert-attribuut op de achtergrond.
|
|
19
|
+
* De focus blijft binnen het venster via de native focus-trap plus een
|
|
20
|
+
* expliciete trap, en komt bij openen op de heading te staan.
|
|
15
21
|
*
|
|
16
22
|
* @example
|
|
17
23
|
* ```tsx
|
|
@@ -31,16 +37,28 @@ const ModalDialogContext = React.createContext({ headingId: "" });
|
|
|
31
37
|
* </ModalDialog>
|
|
32
38
|
* ```
|
|
33
39
|
*/
|
|
34
|
-
const ModalDialog = React.forwardRef(({ className, isOpen, onClose, children, ...props }, ref) => {
|
|
40
|
+
const ModalDialog = React.forwardRef(({ className, isOpen, onClose, triggerRef, children, ...props }, ref) => {
|
|
35
41
|
const internalRef = React.useRef(null);
|
|
36
42
|
const dialogRef = ref ?? internalRef;
|
|
37
43
|
const headingId = React.useId();
|
|
44
|
+
const headingRef = React.useRef(null);
|
|
38
45
|
React.useEffect(() => {
|
|
39
46
|
const dialog = dialogRef.current;
|
|
40
47
|
if (!dialog) return;
|
|
41
|
-
if (isOpen && !dialog.open)
|
|
42
|
-
|
|
48
|
+
if (isOpen && !dialog.open) {
|
|
49
|
+
dialog.showModal();
|
|
50
|
+
(headingRef.current ?? dialog).focus();
|
|
51
|
+
} else if (!isOpen && dialog.open) dialog.close();
|
|
43
52
|
}, [isOpen, dialogRef]);
|
|
53
|
+
useFocusTrap(dialogRef, isOpen);
|
|
54
|
+
React.useEffect(() => {
|
|
55
|
+
const trigger = triggerRef?.current;
|
|
56
|
+
if (!trigger) return;
|
|
57
|
+
trigger.setAttribute("aria-expanded", isOpen ? "true" : "false");
|
|
58
|
+
return () => {
|
|
59
|
+
trigger.setAttribute("aria-expanded", "false");
|
|
60
|
+
};
|
|
61
|
+
}, [isOpen, triggerRef]);
|
|
44
62
|
const handleCancel = (event) => {
|
|
45
63
|
event.preventDefault();
|
|
46
64
|
onClose?.();
|
|
@@ -49,6 +67,7 @@ const ModalDialog = React.forwardRef(({ className, isOpen, onClose, children, ..
|
|
|
49
67
|
return /* @__PURE__ */ jsx(ModalDialogContext.Provider, {
|
|
50
68
|
value: {
|
|
51
69
|
headingId,
|
|
70
|
+
headingRef,
|
|
52
71
|
onClose
|
|
53
72
|
},
|
|
54
73
|
children: /* @__PURE__ */ jsx("dialog", {
|
|
@@ -56,6 +75,7 @@ const ModalDialog = React.forwardRef(({ className, isOpen, onClose, children, ..
|
|
|
56
75
|
className: classes,
|
|
57
76
|
"aria-labelledby": headingId,
|
|
58
77
|
onCancel: handleCancel,
|
|
78
|
+
tabIndex: -1,
|
|
59
79
|
...props,
|
|
60
80
|
children
|
|
61
81
|
})
|
|
@@ -89,14 +109,21 @@ ModalDialogHeader.displayName = "ModalDialogHeader";
|
|
|
89
109
|
/**
|
|
90
110
|
* ModalDialogHeading
|
|
91
111
|
* De heading van het dialoogvenster. ID wordt automatisch gegenereerd voor aria-labelledby.
|
|
112
|
+
* Krijgt `tabindex="-1"` zodat het ModalDialog er bij openen de focus op kan zetten.
|
|
92
113
|
*/
|
|
93
114
|
const ModalDialogHeading = React.forwardRef(({ className, level = 2, children, ...props }, ref) => {
|
|
94
|
-
const { headingId } = React.useContext(ModalDialogContext);
|
|
115
|
+
const { headingId, headingRef } = React.useContext(ModalDialogContext);
|
|
95
116
|
const Tag = `h${level}`;
|
|
117
|
+
const setRef = React.useCallback((node) => {
|
|
118
|
+
headingRef.current = node;
|
|
119
|
+
if (typeof ref === "function") ref(node);
|
|
120
|
+
else if (ref) ref.current = node;
|
|
121
|
+
}, [headingRef, ref]);
|
|
96
122
|
return /* @__PURE__ */ jsx(Tag, {
|
|
97
|
-
ref,
|
|
123
|
+
ref: setRef,
|
|
98
124
|
id: headingId,
|
|
99
125
|
className: classNames("dsn-modal-dialog-heading", className),
|
|
126
|
+
tabIndex: -1,
|
|
100
127
|
...props,
|
|
101
128
|
children
|
|
102
129
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ModalDialog2.mjs","names":[],"sources":["../../src/ModalDialog/ModalDialog.tsx"],"sourcesContent":["import React from 'react';\nimport { classNames } from '@dsn-starter-kit/core';\nimport { Button } from '../Button';\nimport { Icon } from '../Icon';\nimport './ModalDialog.css';\n\n// =============================================================================\n// Context\n// =============================================================================\n\ninterface ModalDialogContextValue {\n headingId: string;\n onClose?: () => void;\n}\n\nconst ModalDialogContext = React.createContext<ModalDialogContextValue>({\n headingId: '',\n});\n\n// =============================================================================\n// ModalDialog (root)\n// =============================================================================\n\nexport interface ModalDialogProps extends Omit<\n React.HTMLAttributes<HTMLDialogElement>,\n 'children'\n> {\n /**\n * Bepaalt of het dialoogvenster getoond wordt.\n * Het dialoogvenster wordt via `.showModal()` geopend — nooit via `.show()`.\n */\n isOpen: boolean;\n\n /**\n * Callback die wordt aangeroepen wanneer het dialoogvenster sluit\n * (sluitknop, Escape-toets of klik buiten het venster).\n */\n onClose?: () => void;\n\n /**\n * De subcomponenten van het dialoogvenster:\n * `ModalDialogHeader`, `ModalDialogBody`, `ModalDialogFooter`\n */\n children?: React.ReactNode;\n}\n\n/**\n * ModalDialog component\n * Modaal dialoogvenster gebaseerd op het native `<dialog>` element.\n *\n * Gebruik altijd `.showModal()` (intern afgehandeld via `isOpen` prop) — nooit `.show()`.\n * Dit garandeert natieve focus-trap, aria-modal semantiek en inert-attribuut op de achtergrond.\n *\n * @example\n * ```tsx\n * <ModalDialog isOpen={isOpen} onClose={() => setIsOpen(false)}>\n * <ModalDialogHeader>\n * <ModalDialogHeading>Bevestig verwijderen</ModalDialogHeading>\n * </ModalDialogHeader>\n * <ModalDialogBody>\n * <Paragraph>Weet u zeker dat u dit item wilt verwijderen?</Paragraph>\n * </ModalDialogBody>\n * <ModalDialogFooter>\n * <ActionGroup>\n * <Button variant=\"negative\" onClick={() => setIsOpen(false)}>Verwijderen</Button>\n * <Button variant=\"default\" onClick={() => setIsOpen(false)}>Annuleren</Button>\n * </ActionGroup>\n * </ModalDialogFooter>\n * </ModalDialog>\n * ```\n */\nexport const ModalDialog = React.forwardRef<\n HTMLDialogElement,\n ModalDialogProps\n>(({ className, isOpen, onClose, children, ...props }, ref) => {\n const internalRef = React.useRef<HTMLDialogElement>(null);\n const dialogRef = (ref as React.RefObject<HTMLDialogElement>) ?? internalRef;\n const headingId = React.useId();\n\n React.useEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) return;\n\n if (isOpen && !dialog.open) {\n dialog.showModal();\n } else if (!isOpen && dialog.open) {\n dialog.close();\n }\n }, [isOpen, dialogRef]);\n\n const handleCancel = (event: React.SyntheticEvent<HTMLDialogElement>) => {\n event.preventDefault();\n onClose?.();\n };\n\n const classes = classNames('dsn-modal-dialog', className);\n\n return (\n <ModalDialogContext.Provider value={{ headingId, onClose }}>\n <dialog\n ref={dialogRef}\n className={classes}\n aria-labelledby={headingId}\n onCancel={handleCancel}\n {...props}\n >\n {children}\n </dialog>\n </ModalDialogContext.Provider>\n );\n});\n\nModalDialog.displayName = 'ModalDialog';\n\n// =============================================================================\n// ModalDialogHeader\n// =============================================================================\n\nexport interface ModalDialogHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Inhoud van de header — doorgaans een `ModalDialogHeading`\n */\n children?: React.ReactNode;\n}\n\n/**\n * ModalDialogHeader\n * De headerstrook van het dialoogvenster met heading en sluitknop.\n */\nexport const ModalDialogHeader = React.forwardRef<\n HTMLDivElement,\n ModalDialogHeaderProps\n>(({ className, children, ...props }, ref) => {\n const { onClose } = React.useContext(ModalDialogContext);\n\n return (\n <div\n ref={ref}\n className={classNames('dsn-modal-dialog__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\nModalDialogHeader.displayName = 'ModalDialogHeader';\n\n// =============================================================================\n// ModalDialogHeading\n// =============================================================================\n\nexport interface ModalDialogHeadingProps extends React.HTMLAttributes<HTMLHeadingElement> {\n /**\n * Semantisch heading-niveau. Kies op basis van documenthiërarchie, niet visuele grootte.\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 * ModalDialogHeading\n * De heading van het dialoogvenster. ID wordt automatisch gegenereerd voor aria-labelledby.\n */\nexport const ModalDialogHeading = React.forwardRef<\n HTMLHeadingElement,\n ModalDialogHeadingProps\n>(({ className, level = 2, children, ...props }, ref) => {\n const { headingId } = React.useContext(ModalDialogContext);\n const Tag = `h${level}` as React.ElementType;\n\n return (\n <Tag\n ref={ref}\n id={headingId}\n className={classNames('dsn-modal-dialog-heading', className)}\n {...props}\n >\n {children}\n </Tag>\n );\n});\n\nModalDialogHeading.displayName = 'ModalDialogHeading';\n\n// =============================================================================\n// ModalDialogBody\n// =============================================================================\n\nexport interface ModalDialogBodyProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * De hoofdinhoud van het dialoogvenster\n */\n children?: React.ReactNode;\n}\n\n/**\n * ModalDialogBody\n * De scrollbare inhoudssectie van het dialoogvenster met scroll-affordance schaduw.\n */\nexport const ModalDialogBody = React.forwardRef<\n HTMLDivElement,\n ModalDialogBodyProps\n>(({ className, children, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={classNames('dsn-modal-dialog__body', className)}\n {...props}\n >\n {children}\n </div>\n );\n});\n\nModalDialogBody.displayName = 'ModalDialogBody';\n\n// =============================================================================\n// ModalDialogFooter\n// =============================================================================\n\nexport interface ModalDialogFooterProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * De acties van het dialoogvenster — doorgaans een `ActionGroup` met knoppen\n */\n children?: React.ReactNode;\n}\n\n/**\n * ModalDialogFooter\n * De voettekst van het dialoogvenster met actieknoppen.\n */\nexport const ModalDialogFooter = React.forwardRef<\n HTMLDivElement,\n ModalDialogFooterProps\n>(({ className, children, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={classNames('dsn-modal-dialog__footer', className)}\n {...props}\n >\n {children}\n </div>\n );\n});\n\nModalDialogFooter.displayName = 'ModalDialogFooter';\n"],"mappings":";;;;;;;AAeA,MAAM,qBAAqB,MAAM,cAAuC,EACtE,WAAW,GACb,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;AAsDD,MAAa,cAAc,MAAM,YAG9B,EAAE,WAAW,QAAQ,SAAS,UAAU,GAAG,SAAS,QAAQ;CAC7D,MAAM,cAAc,MAAM,OAA0B,IAAI;CACxD,MAAM,YAAa,OAA8C;CACjE,MAAM,YAAY,MAAM,MAAM;CAE9B,MAAM,gBAAgB;EACpB,MAAM,SAAS,UAAU;EACzB,IAAI,CAAC,QAAQ;EAEb,IAAI,UAAU,CAAC,OAAO,MACpB,OAAO,UAAU;OACZ,IAAI,CAAC,UAAU,OAAO,MAC3B,OAAO,MAAM;CAEjB,GAAG,CAAC,QAAQ,SAAS,CAAC;CAEtB,MAAM,gBAAgB,UAAmD;EACvE,MAAM,eAAe;EACrB,UAAU;CACZ;CAEA,MAAM,UAAU,WAAW,oBAAoB,SAAS;CAExD,OACE,oBAAC,mBAAmB,UAApB;EAA6B,OAAO;GAAE;GAAW;EAAQ;EACvD,UAAA,oBAAC,UAAD;GACE,KAAK;GACL,WAAW;GACX,mBAAiB;GACjB,UAAU;GACV,GAAI;GAEH;EACK,CAAA;CACmB,CAAA;AAEjC,CAAC;AAED,YAAY,cAAc;;;;;AAiB1B,MAAa,oBAAoB,MAAM,YAGpC,EAAE,WAAW,UAAU,GAAG,SAAS,QAAQ;CAC5C,MAAM,EAAE,YAAY,MAAM,WAAW,kBAAkB;CAEvD,OACE,qBAAC,OAAD;EACO;EACL,WAAW,WAAW,4BAA4B,SAAS;EAC3D,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,kBAAkB,cAAc;;;;;AAuBhC,MAAa,qBAAqB,MAAM,YAGrC,EAAE,WAAW,QAAQ,GAAG,UAAU,GAAG,SAAS,QAAQ;CACvD,MAAM,EAAE,cAAc,MAAM,WAAW,kBAAkB;CACzD,MAAM,MAAM,IAAI;CAEhB,OACE,oBAAC,KAAD;EACO;EACL,IAAI;EACJ,WAAW,WAAW,4BAA4B,SAAS;EAC3D,GAAI;EAEH;CACE,CAAA;AAET,CAAC;AAED,mBAAmB,cAAc;;;;;AAiBjC,MAAa,kBAAkB,MAAM,YAGlC,EAAE,WAAW,UAAU,GAAG,SAAS,QAAQ;CAC5C,OACE,oBAAC,OAAD;EACO;EACL,WAAW,WAAW,0BAA0B,SAAS;EACzD,GAAI;EAEH;CACE,CAAA;AAET,CAAC;AAED,gBAAgB,cAAc;;;;;AAiB9B,MAAa,oBAAoB,MAAM,YAGpC,EAAE,WAAW,UAAU,GAAG,SAAS,QAAQ;CAC5C,OACE,oBAAC,OAAD;EACO;EACL,WAAW,WAAW,4BAA4B,SAAS;EAC3D,GAAI;EAEH;CACE,CAAA;AAET,CAAC;AAED,kBAAkB,cAAc"}
|
|
1
|
+
{"version":3,"file":"ModalDialog2.mjs","names":[],"sources":["../../src/ModalDialog/ModalDialog.tsx"],"sourcesContent":["import React from 'react';\nimport { classNames } from '@dsn-starter-kit/core';\nimport { Button } from '../Button';\nimport { Icon } from '../Icon';\nimport { useFocusTrap } from '../utils/focusTrap';\nimport './ModalDialog.css';\n\n// =============================================================================\n// Context\n// =============================================================================\n\ninterface ModalDialogContextValue {\n headingId: string;\n headingRef: React.MutableRefObject<HTMLHeadingElement | null>;\n onClose?: () => void;\n}\n\nconst ModalDialogContext = React.createContext<ModalDialogContextValue>({\n headingId: '',\n headingRef: { current: null },\n});\n\n// =============================================================================\n// ModalDialog (root)\n// =============================================================================\n\nexport interface ModalDialogProps extends Omit<\n React.HTMLAttributes<HTMLDialogElement>,\n 'children'\n> {\n /**\n * Bepaalt of het dialoogvenster getoond wordt.\n * Het dialoogvenster wordt via `.showModal()` geopend — nooit via `.show()`.\n */\n isOpen: boolean;\n\n /**\n * Callback die wordt aangeroepen wanneer het dialoogvenster sluit\n * (sluitknop, Escape-toets of klik buiten het venster).\n */\n onClose?: () => void;\n\n /**\n * Ref naar het element dat het dialoogvenster opent. Wanneer je die meegeeft,\n * houdt het dialoogvenster `aria-expanded` op dat element synchroon met de\n * open-staat: `false` wanneer het venster gesloten is, `true` wanneer het\n * open staat.\n */\n triggerRef?: React.RefObject<HTMLElement | null>;\n\n /**\n * De subcomponenten van het dialoogvenster:\n * `ModalDialogHeader`, `ModalDialogBody`, `ModalDialogFooter`\n */\n children?: React.ReactNode;\n}\n\n/**\n * ModalDialog component\n * Modaal dialoogvenster gebaseerd op het native `<dialog>` element.\n *\n * Gebruik altijd `.showModal()` (intern afgehandeld via `isOpen` prop), nooit `.show()`.\n * Dit garandeert aria-modal semantiek en het inert-attribuut op de achtergrond.\n * De focus blijft binnen het venster via de native focus-trap plus een\n * expliciete trap, en komt bij openen op de heading te staan.\n *\n * @example\n * ```tsx\n * <ModalDialog isOpen={isOpen} onClose={() => setIsOpen(false)}>\n * <ModalDialogHeader>\n * <ModalDialogHeading>Bevestig verwijderen</ModalDialogHeading>\n * </ModalDialogHeader>\n * <ModalDialogBody>\n * <Paragraph>Weet u zeker dat u dit item wilt verwijderen?</Paragraph>\n * </ModalDialogBody>\n * <ModalDialogFooter>\n * <ActionGroup>\n * <Button variant=\"negative\" onClick={() => setIsOpen(false)}>Verwijderen</Button>\n * <Button variant=\"default\" onClick={() => setIsOpen(false)}>Annuleren</Button>\n * </ActionGroup>\n * </ModalDialogFooter>\n * </ModalDialog>\n * ```\n */\nexport const ModalDialog = React.forwardRef<\n HTMLDialogElement,\n ModalDialogProps\n>(({ className, isOpen, onClose, triggerRef, children, ...props }, ref) => {\n const internalRef = React.useRef<HTMLDialogElement>(null);\n const dialogRef = (ref as React.RefObject<HTMLDialogElement>) ?? internalRef;\n const headingId = React.useId();\n const headingRef = React.useRef<HTMLHeadingElement | null>(null);\n\n React.useEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) return;\n\n if (isOpen && !dialog.open) {\n dialog.showModal();\n\n /*\n * Zet de focus op de heading, niet op de sluitknop. De browser zet de\n * focus bij het openen zelf op het eerste focusbare element, en dat is\n * de sluitknop. VoiceOver in Safari leest die knop dan wel voor, maar\n * niet de titel uit `aria-labelledby`, en omdat de sluitknop na de\n * titel staat kom je de titel bij verder lezen ook niet meer tegen.\n *\n * Met de focus op de heading (die `tabindex=\"-1\"` heeft) wordt de titel\n * als eerste voorgelezen en loopt de leesvolgorde daarna door naar de\n * sluitknop en de inhoud. Dit is het patroon uit de ARIA Authoring\n * Practices, en het houdt DOM-volgorde en visuele volgorde gelijk.\n */\n (headingRef.current ?? dialog).focus();\n } else if (!isOpen && dialog.open) {\n dialog.close();\n }\n }, [isOpen, dialogRef]);\n\n /*\n * Expliciete focus-trap bovenop de native trap van `.showModal()`.\n * Zie `utils/focusTrap.ts` voor waarom de native trap niet volstaat.\n */\n useFocusTrap(dialogRef, isOpen);\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 const handleCancel = (event: React.SyntheticEvent<HTMLDialogElement>) => {\n event.preventDefault();\n onClose?.();\n };\n\n const classes = classNames('dsn-modal-dialog', className);\n\n return (\n <ModalDialogContext.Provider value={{ headingId, headingRef, onClose }}>\n <dialog\n ref={dialogRef}\n className={classes}\n aria-labelledby={headingId}\n onCancel={handleCancel}\n tabIndex={-1}\n {...props}\n >\n {children}\n </dialog>\n </ModalDialogContext.Provider>\n );\n});\n\nModalDialog.displayName = 'ModalDialog';\n\n// =============================================================================\n// ModalDialogHeader\n// =============================================================================\n\nexport interface ModalDialogHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Inhoud van de header — doorgaans een `ModalDialogHeading`\n */\n children?: React.ReactNode;\n}\n\n/**\n * ModalDialogHeader\n * De headerstrook van het dialoogvenster met heading en sluitknop.\n */\nexport const ModalDialogHeader = React.forwardRef<\n HTMLDivElement,\n ModalDialogHeaderProps\n>(({ className, children, ...props }, ref) => {\n const { onClose } = React.useContext(ModalDialogContext);\n\n return (\n <div\n ref={ref}\n className={classNames('dsn-modal-dialog__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\nModalDialogHeader.displayName = 'ModalDialogHeader';\n\n// =============================================================================\n// ModalDialogHeading\n// =============================================================================\n\nexport interface ModalDialogHeadingProps extends React.HTMLAttributes<HTMLHeadingElement> {\n /**\n * Semantisch heading-niveau. Kies op basis van documenthiërarchie, niet visuele grootte.\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 * ModalDialogHeading\n * De heading van het dialoogvenster. ID wordt automatisch gegenereerd voor aria-labelledby.\n * Krijgt `tabindex=\"-1\"` zodat het ModalDialog er bij openen de focus op kan zetten.\n */\nexport const ModalDialogHeading = React.forwardRef<\n HTMLHeadingElement,\n ModalDialogHeadingProps\n>(({ className, level = 2, children, ...props }, ref) => {\n const { headingId, headingRef } = React.useContext(ModalDialogContext);\n const Tag = `h${level}` as React.ElementType;\n\n // Het ModalDialog heeft de heading nodig om er bij openen de focus op te\n // zetten; 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-modal-dialog-heading', className)}\n tabIndex={-1}\n {...props}\n >\n {children}\n </Tag>\n );\n});\n\nModalDialogHeading.displayName = 'ModalDialogHeading';\n\n// =============================================================================\n// ModalDialogBody\n// =============================================================================\n\nexport interface ModalDialogBodyProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * De hoofdinhoud van het dialoogvenster\n */\n children?: React.ReactNode;\n}\n\n/**\n * ModalDialogBody\n * De scrollbare inhoudssectie van het dialoogvenster met scroll-affordance schaduw.\n */\nexport const ModalDialogBody = React.forwardRef<\n HTMLDivElement,\n ModalDialogBodyProps\n>(({ className, children, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={classNames('dsn-modal-dialog__body', className)}\n {...props}\n >\n {children}\n </div>\n );\n});\n\nModalDialogBody.displayName = 'ModalDialogBody';\n\n// =============================================================================\n// ModalDialogFooter\n// =============================================================================\n\nexport interface ModalDialogFooterProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * De acties van het dialoogvenster — doorgaans een `ActionGroup` met knoppen\n */\n children?: React.ReactNode;\n}\n\n/**\n * ModalDialogFooter\n * De voettekst van het dialoogvenster met actieknoppen.\n */\nexport const ModalDialogFooter = React.forwardRef<\n HTMLDivElement,\n ModalDialogFooterProps\n>(({ className, children, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={classNames('dsn-modal-dialog__footer', className)}\n {...props}\n >\n {children}\n </div>\n );\n});\n\nModalDialogFooter.displayName = 'ModalDialogFooter';\n"],"mappings":"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"}
|
|
@@ -256,6 +256,7 @@ const PageHeader = React.forwardRef(({ className, logoSlot, sticky = "none", lay
|
|
|
256
256
|
isOpen: isMenuOpen,
|
|
257
257
|
onClose: handleMenuClose,
|
|
258
258
|
side: "left",
|
|
259
|
+
triggerRef: menuButtonRef,
|
|
259
260
|
children: [/* @__PURE__ */ jsx(DrawerHeader, { children: /* @__PURE__ */ jsx(DrawerHeading, { children: menuButtonLabel }) }), /* @__PURE__ */ jsx(DrawerBody, { children: /* @__PURE__ */ jsxs(Stack, {
|
|
260
261
|
space: "5xl",
|
|
261
262
|
children: [primaryNavigation && /* @__PURE__ */ jsx("nav", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PageHeader2.mjs","names":[],"sources":["../../src/PageHeader/PageHeader.tsx"],"sourcesContent":["import React from 'react';\nimport { classNames } from '@dsn-starter-kit/core';\nimport { Button } from '../Button';\nimport { Icon } from '../Icon';\nimport { SearchInput } from '../SearchInput';\nimport { Drawer, DrawerBody, DrawerHeader, DrawerHeading } from '../Drawer';\nimport { Stack } from '../Stack';\nimport './PageHeader.css';\n\nexport type PageHeaderSticky = 'none' | 'sticky' | 'auto-hide';\nexport type PageHeaderLayout = 'default' | 'compact';\nexport type PageHeaderColorScheme = 'default' | 'inverse';\n\nexport interface PageHeaderProps extends Omit<\n React.HTMLAttributes<HTMLElement>,\n 'children'\n> {\n /**\n * Logo-inhoud — `<svg>`, `<img>`, of een `<a>` die een logo omhult.\n * De CSS past automatisch `max-block-size` toe op de directe child.\n */\n logoSlot: React.ReactNode;\n\n /**\n * Scrollgedrag van de header.\n * - `none` (standaard): header scrollt mee met de pagina\n * - `sticky`: header blijft bovenaan de viewport vastgeplakt\n * - `auto-hide`: sticky + verbergt bij scroll-down, toont bij scroll-up\n * @default 'none'\n */\n sticky?: PageHeaderSticky;\n\n /**\n * Lay-out van de header op large viewport (≥ 64em).\n * - `default`: twee horizontale banden — Masthead (logo + servicemenu + zoekveld)\n * en Navigatiebalk (primaire navigatie op accent-1 achtergrond)\n * - `compact`: één enkele rij — logo (inline-start), primaire navigatie (gecentreerd),\n * servicemenu + zoekknop (inline-end). Geeft `primaryNavigationLarge` voorrang\n * boven `primaryNavigation` (en idem voor secondary) zodat de Drawer altijd\n * de verticale variant ontvangt.\n * @default 'default'\n */\n layout?: PageHeaderLayout;\n\n /**\n * Kleurschema van de header.\n * - `default`: neutrale achtergrond met accent-1 navbar\n * - `inverse`: sterke accent-1-inverse achtergrond op navbar en compact balk\n * voor prominente branding. Het masthead blijft altijd neutraal.\n * Logo-kleuren passen zich automatisch aan via CSS context overrides.\n * @default 'default'\n */\n colorScheme?: PageHeaderColorScheme;\n\n /**\n * Initiële open-staat van het zoekpaneel (small viewport).\n * Handig voor Storybook en tests — het paneel kan daarna nog steeds\n * worden geopend/gesloten via de knop.\n * @default false\n */\n initialSearchOpen?: boolean;\n\n /**\n * Primaire navigatie-inhoud in de Drawer (small viewport) — doorgaans een verticale `<Menu>` met\n * `<MenuLink>`-items inclusief uitklapmogelijkheden voor sub-niveaus.\n */\n primaryNavigation?: React.ReactNode;\n\n /**\n * Primaire navigatie-inhoud in de Navigatiebalk (large viewport) — doorgaans een horizontale\n * `<Menu>` met alleen Level 1 `<MenuLink>`-items. Als niet meegegeven valt de component\n * terug op `primaryNavigation`.\n */\n primaryNavigationLarge?: React.ReactNode;\n\n /**\n * Servicemenu-inhoud in de Drawer (small viewport) — doorgaans een verticale `<Menu>`.\n */\n secondaryNavigation?: React.ReactNode;\n\n /**\n * Servicemenu-inhoud in de Masthead (large viewport) — doorgaans een horizontale `<Menu>`.\n * Als niet meegegeven valt de component terug op `secondaryNavigation`.\n */\n secondaryNavigationLarge?: React.ReactNode;\n\n /**\n * Inline zoekveld voor de Masthead op large viewport (SearchInput + zoekknop).\n * Optioneel — toont geen zoekveld als weggelaten.\n */\n searchSlot?: React.ReactNode;\n\n /**\n * Visuele badge-indicator bij de menuknop (small viewport), doorgaans een `<DotBadge>`.\n * Gebruik dit om ongelezen meldingen te signaleren. De badge is altijd `aria-hidden` —\n * geef de toegankelijke tekst mee via `menuButtonBadgeLabel`.\n */\n menuButtonBadge?: React.ReactNode;\n\n /**\n * Toegankelijke tekst die wordt toegevoegd aan het label van de menuknop als er een badge\n * aanwezig is, bijvoorbeeld `\"2 nieuwe berichten\"`. Wordt visueel verborgen getoond zodat\n * screenreadergebruikers de melding horen: \"Menu, 2 nieuwe berichten\".\n * Vereist als `menuButtonBadge` is meegegeven.\n */\n menuButtonBadgeLabel?: string;\n\n /**\n * Callback wanneer de navigatielade opent.\n */\n onMenuOpen?: () => void;\n\n /**\n * Callback wanneer de navigatielade sluit.\n */\n onMenuClose?: () => void;\n\n /**\n * Callback wanneer het zoekpaneel opent.\n */\n onSearchOpen?: () => void;\n\n /**\n * Callback wanneer het zoekpaneel sluit.\n */\n onSearchClose?: () => void;\n\n // ── i18n labels ────────────────────────────────────────────────────────────\n\n /**\n * Label van de menuknop en de Drawer-heading.\n * @default 'Menu'\n */\n menuButtonLabel?: string;\n\n /**\n * Label van de zoekknop wanneer het zoekpaneel gesloten is.\n * @default 'Zoeken'\n */\n searchButtonLabel?: string;\n\n /**\n * Label van de zoekknop wanneer het zoekpaneel open is.\n * @default 'Sluiten'\n */\n closeButtonLabel?: string;\n\n /**\n * Placeholder van het zoekveld in het zoekpaneel (small viewport en compact layout).\n * @default 'Zoeken…'\n */\n searchInputPlaceholder?: string;\n\n /**\n * `aria-label` van het zoekveld in het zoekpaneel.\n * @default 'Zoekopdracht'\n */\n searchInputAriaLabel?: string;\n\n /**\n * Label van de zoek-submitknop in het zoekpaneel.\n * @default 'Zoeken'\n */\n searchSubmitLabel?: string;\n\n /**\n * `aria-label` van de primaire navigatie-`<nav>` (large viewport) en\n * visueel verborgen heading in de Drawer.\n * @default 'Hoofd-navigatie'\n */\n primaryNavAriaLabel?: string;\n\n /**\n * `aria-label` van het servicemenu-`<nav>` (large viewport) en\n * visueel verborgen heading in de Drawer.\n * @default 'Service-navigatie'\n */\n secondaryNavAriaLabel?: string;\n\n /**\n * Verbergt de menuknop in de small viewport layout.\n * Gebruik dit wanneer er geen navigatie aanwezig is (bijv. formulierpagina's).\n * @default false\n */\n hideMenuButton?: boolean;\n\n /**\n * Verbergt de zoekknop in zowel de small viewport als compact layout.\n * Gebruik dit wanneer zoeken niet relevant is (bijv. formulierpagina's).\n * @default false\n */\n hideSearchButton?: boolean;\n\n className?: string;\n}\n\n/**\n * PageHeader component\n * Primaire navigatieheader voor een pagina. Mobile-first implementatie.\n *\n * Op mobile toont de header drie elementen:\n * - Menuknop (inline-start) — opent een `Drawer` met primaire en service-navigatie\n * - Logo (gecentreerd, onafhankelijk van knopbreedte via CSS-grid 1fr auto 1fr)\n * - Zoekknop (inline-end) — opent een zoekpaneel direct onder de header\n *\n * @example\n * ```tsx\n * <PageHeader\n * logoSlot={\n * <a href=\"/\">\n * <Logo aria-hidden={true} />\n * <span className=\"dsn-visually-hidden\">Starter Kit — terug naar homepage</span>\n * </a>\n * }\n * primaryNavigation={\n * <Menu orientation=\"vertical\">\n * <MenuLink href=\"/home\" level={1}>Home</MenuLink>\n * <MenuLink href=\"/over\" level={1}>Over ons</MenuLink>\n * </Menu>\n * }\n * secondaryNavigation={\n * <Menu orientation=\"vertical\">\n * <MenuLink href=\"/contact\" level={1}>Contact</MenuLink>\n * </Menu>\n * }\n * />\n * ```\n */\nexport const PageHeader = React.forwardRef<HTMLElement, PageHeaderProps>(\n (\n {\n className,\n logoSlot,\n sticky = 'none',\n layout = 'default',\n colorScheme = 'default',\n initialSearchOpen = false,\n primaryNavigation,\n primaryNavigationLarge,\n secondaryNavigation,\n secondaryNavigationLarge,\n searchSlot,\n menuButtonBadge,\n menuButtonBadgeLabel,\n onMenuOpen,\n onMenuClose,\n onSearchOpen,\n onSearchClose,\n menuButtonLabel = 'Menu',\n searchButtonLabel = 'Zoeken',\n closeButtonLabel = 'Sluiten',\n searchInputPlaceholder = 'Zoeken…',\n searchInputAriaLabel = 'Zoekopdracht',\n searchSubmitLabel = 'Zoeken',\n primaryNavAriaLabel = 'Hoofd-navigatie',\n secondaryNavAriaLabel = 'Service-navigatie',\n hideMenuButton = false,\n hideSearchButton = false,\n ...props\n },\n ref\n ) => {\n const [isMenuOpen, setIsMenuOpen] = React.useState(false);\n const [isSearchOpen, setIsSearchOpen] = React.useState(initialSearchOpen);\n\n const headerRef = React.useRef<HTMLElement>(null);\n const combinedRef = (ref as React.RefObject<HTMLElement>) ?? headerRef;\n\n const menuButtonRef = React.useRef<HTMLButtonElement>(null);\n const searchButtonRef = React.useRef<HTMLButtonElement>(null);\n const searchInputRef = React.useRef<HTMLInputElement>(null);\n const compactSearchButtonRef = React.useRef<HTMLButtonElement>(null);\n const compactSearchInputRef = React.useRef<HTMLInputElement>(null);\n\n const searchPanelId = React.useId();\n const compactSearchPanelId = React.useId();\n\n // Auto-hide: detecteer scrollrichting en toggle data-hidden attribuut\n React.useEffect(() => {\n if (sticky !== 'auto-hide') return;\n\n let lastScrollY = window.scrollY;\n\n const handleScroll = () => {\n const currentScrollY = window.scrollY;\n const isScrollingDown =\n currentScrollY > lastScrollY && currentScrollY > 100;\n combinedRef.current?.setAttribute(\n 'data-hidden',\n String(isScrollingDown)\n );\n lastScrollY = currentScrollY;\n };\n\n window.addEventListener('scroll', handleScroll, { passive: true });\n return () => window.removeEventListener('scroll', handleScroll);\n }, [sticky, combinedRef]);\n\n // Drop-shadow: zet data-scrolled op \"true\" zodra de pagina gescrolled is.\n // Werkt voor zowel sticky als auto-hide — ongeacht scrollrichting.\n React.useEffect(() => {\n if (sticky === 'none') return;\n\n const updateScrolled = () => {\n combinedRef.current?.setAttribute(\n 'data-scrolled',\n String(window.scrollY > 0)\n );\n };\n\n // Direct bij mount instellen (pagina kan al gescrolled zijn)\n updateScrolled();\n\n window.addEventListener('scroll', updateScrolled, { passive: true });\n return () => window.removeEventListener('scroll', updateScrolled);\n }, [sticky, combinedRef]);\n\n // Focus management: bij openen zoekpaneel → focus naar input (klein of compact)\n React.useEffect(() => {\n if (isSearchOpen) {\n searchInputRef.current?.focus();\n compactSearchInputRef.current?.focus();\n }\n }, [isSearchOpen]);\n\n const handleMenuOpen = () => {\n setIsMenuOpen(true);\n onMenuOpen?.();\n };\n\n const handleMenuClose = () => {\n setIsMenuOpen(false);\n onMenuClose?.();\n // Focus terug naar menuknop\n menuButtonRef.current?.focus();\n };\n\n const handleSearchToggle = () => {\n const opening = !isSearchOpen;\n setIsSearchOpen(opening);\n if (opening) {\n onSearchOpen?.();\n } else {\n onSearchClose?.();\n // Focus terug naar zoek/sluit-knop — afhankelijk van actieve layout\n searchButtonRef.current?.focus();\n compactSearchButtonRef.current?.focus();\n }\n };\n\n const classes = classNames(\n 'dsn-page-header',\n sticky === 'sticky' && 'dsn-page-header--sticky',\n sticky === 'auto-hide' && 'dsn-page-header--auto-hide',\n layout === 'compact' && 'dsn-page-header--compact',\n colorScheme === 'inverse' && 'dsn-page-header--inverse',\n className\n );\n\n return (\n <>\n <header\n ref={combinedRef}\n className={classes}\n {...(sticky === 'auto-hide' ? { 'data-hidden': 'false' } : {})}\n {...props}\n >\n {/* ----------------------------------------------------------------\n Small viewport layout (verborgen boven 64em via CSS display:none)\n ---------------------------------------------------------------- */}\n <div className=\"dsn-page-header__small-layout\">\n <div className=\"dsn-page-header__inner\">\n {/* Inline-start: menuknop */}\n <div className=\"dsn-page-header__start\">\n {!hideMenuButton && (\n <Button\n ref={menuButtonRef}\n variant=\"subtle\"\n onClick={handleMenuOpen}\n iconStart={<Icon name=\"menu\" aria-hidden />}\n >\n {menuButtonLabel}\n {menuButtonBadgeLabel && (\n <span className=\"dsn-visually-hidden\">\n , {menuButtonBadgeLabel}\n </span>\n )}\n {menuButtonBadge}\n </Button>\n )}\n </div>\n\n {/* Gecentreerd logo */}\n <div className=\"dsn-page-header__logo\">{logoSlot}</div>\n\n {/* Inline-end: zoekknop / sluitknop */}\n <div className=\"dsn-page-header__end\">\n {!hideSearchButton && (\n <Button\n ref={searchButtonRef}\n variant=\"subtle\"\n aria-expanded={isSearchOpen}\n aria-controls={searchPanelId}\n onClick={handleSearchToggle}\n iconStart={\n <Icon name={isSearchOpen ? 'x' : 'search'} aria-hidden />\n }\n >\n {isSearchOpen ? closeButtonLabel : searchButtonLabel}\n </Button>\n )}\n </div>\n </div>\n\n {/* Zoekpaneel */}\n <div\n id={searchPanelId}\n className=\"dsn-page-header__search-panel\"\n hidden={!isSearchOpen}\n >\n <div className=\"dsn-page-header__search-inner\">\n <SearchInput\n ref={searchInputRef}\n placeholder={searchInputPlaceholder}\n aria-label={searchInputAriaLabel}\n />\n <Button variant=\"strong\">{searchSubmitLabel}</Button>\n </div>\n </div>\n </div>\n\n {/* ----------------------------------------------------------------\n Large viewport layout (zichtbaar boven 64em, alleen bij layout=\"default\")\n ---------------------------------------------------------------- */}\n {layout === 'default' && (\n <div className=\"dsn-page-header__large-layout\">\n {/* Masthead: neutrale achtergrond — logo + servicemenu + zoek */}\n <div className=\"dsn-page-header__masthead\">\n <div className=\"dsn-page-header__masthead-inner\">\n <div className=\"dsn-page-header__logo\">{logoSlot}</div>\n\n <div className=\"dsn-page-header__secondary-nav\">\n {(secondaryNavigationLarge ?? secondaryNavigation) && (\n <nav aria-label={secondaryNavAriaLabel}>\n {secondaryNavigationLarge ?? secondaryNavigation}\n </nav>\n )}\n {searchSlot && (\n <div className=\"dsn-page-header__searchbox\">\n {searchSlot}\n </div>\n )}\n </div>\n </div>\n </div>\n\n {/* Navigatiebalk: accent-1 achtergrond — primaire navigatie (large viewport) */}\n <div className=\"dsn-page-header__navbar\">\n <div className=\"dsn-page-header__navbar-inner\">\n {(primaryNavigationLarge ?? primaryNavigation) && (\n <nav aria-label={primaryNavAriaLabel}>\n {primaryNavigationLarge ?? primaryNavigation}\n </nav>\n )}\n </div>\n </div>\n </div>\n )}\n\n {/* ----------------------------------------------------------------\n Compact layout (zichtbaar boven 64em wanneer layout=\"compact\")\n Eén rij: logo (1fr) | primaire navigatie (auto) | servicemenu + zoek (1fr)\n ---------------------------------------------------------------- */}\n {layout === 'compact' && (\n <div className=\"dsn-page-header__compact-layout\">\n <div className=\"dsn-page-header__compact-inner\">\n {/* Logo — inline-start (eerste gridkolom) */}\n <div className=\"dsn-page-header__logo\">{logoSlot}</div>\n\n {/* Primaire navigatie — gecentreerd (middelste gridkolom)\n Gebruikt primaryNavigationLarge als aanwezig, anders primaryNavigation.\n Dit garandeert dat de Drawer altijd de verticale variant ontvangt. */}\n <div className=\"dsn-page-header__compact-primary-nav\">\n {(primaryNavigationLarge ?? primaryNavigation) && (\n <nav aria-label={primaryNavAriaLabel}>\n {primaryNavigationLarge ?? primaryNavigation}\n </nav>\n )}\n </div>\n\n {/* Servicemenu + zoekknop (icon-only) — inline-end (derde gridkolom) */}\n <div className=\"dsn-page-header__compact-secondary\">\n {(secondaryNavigationLarge ?? secondaryNavigation) && (\n <nav aria-label={secondaryNavAriaLabel}>\n {secondaryNavigationLarge ?? secondaryNavigation}\n </nav>\n )}\n {!hideSearchButton && (\n <Button\n ref={compactSearchButtonRef}\n variant=\"subtle\"\n iconOnly\n aria-expanded={isSearchOpen}\n aria-controls={compactSearchPanelId}\n onClick={handleSearchToggle}\n iconStart={\n <Icon\n name={isSearchOpen ? 'x' : 'search'}\n aria-hidden\n />\n }\n >\n {isSearchOpen ? closeButtonLabel : searchButtonLabel}\n </Button>\n )}\n </div>\n </div>\n\n {/* Zoekpaneel compact layout */}\n <div\n id={compactSearchPanelId}\n className=\"dsn-page-header__search-panel\"\n hidden={!isSearchOpen}\n >\n <div className=\"dsn-page-header__search-inner\">\n <SearchInput\n ref={compactSearchInputRef}\n placeholder={searchInputPlaceholder}\n aria-label={searchInputAriaLabel}\n />\n <Button variant=\"strong\">{searchSubmitLabel}</Button>\n </div>\n </div>\n </div>\n )}\n </header>\n\n {/* Navigatielade (sibling aan PageHeader, altijd in DOM) */}\n <Drawer isOpen={isMenuOpen} onClose={handleMenuClose} side=\"left\">\n <DrawerHeader>\n <DrawerHeading>{menuButtonLabel}</DrawerHeading>\n </DrawerHeader>\n <DrawerBody>\n <Stack space=\"5xl\">\n {primaryNavigation && (\n <nav aria-label={primaryNavAriaLabel}>{primaryNavigation}</nav>\n )}\n {secondaryNavigation && (\n <nav aria-label={secondaryNavAriaLabel}>\n {secondaryNavigation}\n </nav>\n )}\n </Stack>\n </DrawerBody>\n </Drawer>\n </>\n );\n }\n);\n\nPageHeader.displayName = 'PageHeader';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PageHeader2.mjs","names":[],"sources":["../../src/PageHeader/PageHeader.tsx"],"sourcesContent":["import React from 'react';\nimport { classNames } from '@dsn-starter-kit/core';\nimport { Button } from '../Button';\nimport { Icon } from '../Icon';\nimport { SearchInput } from '../SearchInput';\nimport { Drawer, DrawerBody, DrawerHeader, DrawerHeading } from '../Drawer';\nimport { Stack } from '../Stack';\nimport './PageHeader.css';\n\nexport type PageHeaderSticky = 'none' | 'sticky' | 'auto-hide';\nexport type PageHeaderLayout = 'default' | 'compact';\nexport type PageHeaderColorScheme = 'default' | 'inverse';\n\nexport interface PageHeaderProps extends Omit<\n React.HTMLAttributes<HTMLElement>,\n 'children'\n> {\n /**\n * Logo-inhoud — `<svg>`, `<img>`, of een `<a>` die een logo omhult.\n * De CSS past automatisch `max-block-size` toe op de directe child.\n */\n logoSlot: React.ReactNode;\n\n /**\n * Scrollgedrag van de header.\n * - `none` (standaard): header scrollt mee met de pagina\n * - `sticky`: header blijft bovenaan de viewport vastgeplakt\n * - `auto-hide`: sticky + verbergt bij scroll-down, toont bij scroll-up\n * @default 'none'\n */\n sticky?: PageHeaderSticky;\n\n /**\n * Lay-out van de header op large viewport (≥ 64em).\n * - `default`: twee horizontale banden — Masthead (logo + servicemenu + zoekveld)\n * en Navigatiebalk (primaire navigatie op accent-1 achtergrond)\n * - `compact`: één enkele rij — logo (inline-start), primaire navigatie (gecentreerd),\n * servicemenu + zoekknop (inline-end). Geeft `primaryNavigationLarge` voorrang\n * boven `primaryNavigation` (en idem voor secondary) zodat de Drawer altijd\n * de verticale variant ontvangt.\n * @default 'default'\n */\n layout?: PageHeaderLayout;\n\n /**\n * Kleurschema van de header.\n * - `default`: neutrale achtergrond met accent-1 navbar\n * - `inverse`: sterke accent-1-inverse achtergrond op navbar en compact balk\n * voor prominente branding. Het masthead blijft altijd neutraal.\n * Logo-kleuren passen zich automatisch aan via CSS context overrides.\n * @default 'default'\n */\n colorScheme?: PageHeaderColorScheme;\n\n /**\n * Initiële open-staat van het zoekpaneel (small viewport).\n * Handig voor Storybook en tests — het paneel kan daarna nog steeds\n * worden geopend/gesloten via de knop.\n * @default false\n */\n initialSearchOpen?: boolean;\n\n /**\n * Primaire navigatie-inhoud in de Drawer (small viewport) — doorgaans een verticale `<Menu>` met\n * `<MenuLink>`-items inclusief uitklapmogelijkheden voor sub-niveaus.\n */\n primaryNavigation?: React.ReactNode;\n\n /**\n * Primaire navigatie-inhoud in de Navigatiebalk (large viewport) — doorgaans een horizontale\n * `<Menu>` met alleen Level 1 `<MenuLink>`-items. Als niet meegegeven valt de component\n * terug op `primaryNavigation`.\n */\n primaryNavigationLarge?: React.ReactNode;\n\n /**\n * Servicemenu-inhoud in de Drawer (small viewport) — doorgaans een verticale `<Menu>`.\n */\n secondaryNavigation?: React.ReactNode;\n\n /**\n * Servicemenu-inhoud in de Masthead (large viewport) — doorgaans een horizontale `<Menu>`.\n * Als niet meegegeven valt de component terug op `secondaryNavigation`.\n */\n secondaryNavigationLarge?: React.ReactNode;\n\n /**\n * Inline zoekveld voor de Masthead op large viewport (SearchInput + zoekknop).\n * Optioneel — toont geen zoekveld als weggelaten.\n */\n searchSlot?: React.ReactNode;\n\n /**\n * Visuele badge-indicator bij de menuknop (small viewport), doorgaans een `<DotBadge>`.\n * Gebruik dit om ongelezen meldingen te signaleren. De badge is altijd `aria-hidden` —\n * geef de toegankelijke tekst mee via `menuButtonBadgeLabel`.\n */\n menuButtonBadge?: React.ReactNode;\n\n /**\n * Toegankelijke tekst die wordt toegevoegd aan het label van de menuknop als er een badge\n * aanwezig is, bijvoorbeeld `\"2 nieuwe berichten\"`. Wordt visueel verborgen getoond zodat\n * screenreadergebruikers de melding horen: \"Menu, 2 nieuwe berichten\".\n * Vereist als `menuButtonBadge` is meegegeven.\n */\n menuButtonBadgeLabel?: string;\n\n /**\n * Callback wanneer de navigatielade opent.\n */\n onMenuOpen?: () => void;\n\n /**\n * Callback wanneer de navigatielade sluit.\n */\n onMenuClose?: () => void;\n\n /**\n * Callback wanneer het zoekpaneel opent.\n */\n onSearchOpen?: () => void;\n\n /**\n * Callback wanneer het zoekpaneel sluit.\n */\n onSearchClose?: () => void;\n\n // ── i18n labels ────────────────────────────────────────────────────────────\n\n /**\n * Label van de menuknop en de Drawer-heading.\n * @default 'Menu'\n */\n menuButtonLabel?: string;\n\n /**\n * Label van de zoekknop wanneer het zoekpaneel gesloten is.\n * @default 'Zoeken'\n */\n searchButtonLabel?: string;\n\n /**\n * Label van de zoekknop wanneer het zoekpaneel open is.\n * @default 'Sluiten'\n */\n closeButtonLabel?: string;\n\n /**\n * Placeholder van het zoekveld in het zoekpaneel (small viewport en compact layout).\n * @default 'Zoeken…'\n */\n searchInputPlaceholder?: string;\n\n /**\n * `aria-label` van het zoekveld in het zoekpaneel.\n * @default 'Zoekopdracht'\n */\n searchInputAriaLabel?: string;\n\n /**\n * Label van de zoek-submitknop in het zoekpaneel.\n * @default 'Zoeken'\n */\n searchSubmitLabel?: string;\n\n /**\n * `aria-label` van de primaire navigatie-`<nav>` (large viewport) en\n * visueel verborgen heading in de Drawer.\n * @default 'Hoofd-navigatie'\n */\n primaryNavAriaLabel?: string;\n\n /**\n * `aria-label` van het servicemenu-`<nav>` (large viewport) en\n * visueel verborgen heading in de Drawer.\n * @default 'Service-navigatie'\n */\n secondaryNavAriaLabel?: string;\n\n /**\n * Verbergt de menuknop in de small viewport layout.\n * Gebruik dit wanneer er geen navigatie aanwezig is (bijv. formulierpagina's).\n * @default false\n */\n hideMenuButton?: boolean;\n\n /**\n * Verbergt de zoekknop in zowel de small viewport als compact layout.\n * Gebruik dit wanneer zoeken niet relevant is (bijv. formulierpagina's).\n * @default false\n */\n hideSearchButton?: boolean;\n\n className?: string;\n}\n\n/**\n * PageHeader component\n * Primaire navigatieheader voor een pagina. Mobile-first implementatie.\n *\n * Op mobile toont de header drie elementen:\n * - Menuknop (inline-start) — opent een `Drawer` met primaire en service-navigatie\n * - Logo (gecentreerd, onafhankelijk van knopbreedte via CSS-grid 1fr auto 1fr)\n * - Zoekknop (inline-end) — opent een zoekpaneel direct onder de header\n *\n * @example\n * ```tsx\n * <PageHeader\n * logoSlot={\n * <a href=\"/\">\n * <Logo aria-hidden={true} />\n * <span className=\"dsn-visually-hidden\">Starter Kit — terug naar homepage</span>\n * </a>\n * }\n * primaryNavigation={\n * <Menu orientation=\"vertical\">\n * <MenuLink href=\"/home\" level={1}>Home</MenuLink>\n * <MenuLink href=\"/over\" level={1}>Over ons</MenuLink>\n * </Menu>\n * }\n * secondaryNavigation={\n * <Menu orientation=\"vertical\">\n * <MenuLink href=\"/contact\" level={1}>Contact</MenuLink>\n * </Menu>\n * }\n * />\n * ```\n */\nexport const PageHeader = React.forwardRef<HTMLElement, PageHeaderProps>(\n (\n {\n className,\n logoSlot,\n sticky = 'none',\n layout = 'default',\n colorScheme = 'default',\n initialSearchOpen = false,\n primaryNavigation,\n primaryNavigationLarge,\n secondaryNavigation,\n secondaryNavigationLarge,\n searchSlot,\n menuButtonBadge,\n menuButtonBadgeLabel,\n onMenuOpen,\n onMenuClose,\n onSearchOpen,\n onSearchClose,\n menuButtonLabel = 'Menu',\n searchButtonLabel = 'Zoeken',\n closeButtonLabel = 'Sluiten',\n searchInputPlaceholder = 'Zoeken…',\n searchInputAriaLabel = 'Zoekopdracht',\n searchSubmitLabel = 'Zoeken',\n primaryNavAriaLabel = 'Hoofd-navigatie',\n secondaryNavAriaLabel = 'Service-navigatie',\n hideMenuButton = false,\n hideSearchButton = false,\n ...props\n },\n ref\n ) => {\n const [isMenuOpen, setIsMenuOpen] = React.useState(false);\n const [isSearchOpen, setIsSearchOpen] = React.useState(initialSearchOpen);\n\n const headerRef = React.useRef<HTMLElement>(null);\n const combinedRef = (ref as React.RefObject<HTMLElement>) ?? headerRef;\n\n const menuButtonRef = React.useRef<HTMLButtonElement>(null);\n const searchButtonRef = React.useRef<HTMLButtonElement>(null);\n const searchInputRef = React.useRef<HTMLInputElement>(null);\n const compactSearchButtonRef = React.useRef<HTMLButtonElement>(null);\n const compactSearchInputRef = React.useRef<HTMLInputElement>(null);\n\n const searchPanelId = React.useId();\n const compactSearchPanelId = React.useId();\n\n // Auto-hide: detecteer scrollrichting en toggle data-hidden attribuut\n React.useEffect(() => {\n if (sticky !== 'auto-hide') return;\n\n let lastScrollY = window.scrollY;\n\n const handleScroll = () => {\n const currentScrollY = window.scrollY;\n const isScrollingDown =\n currentScrollY > lastScrollY && currentScrollY > 100;\n combinedRef.current?.setAttribute(\n 'data-hidden',\n String(isScrollingDown)\n );\n lastScrollY = currentScrollY;\n };\n\n window.addEventListener('scroll', handleScroll, { passive: true });\n return () => window.removeEventListener('scroll', handleScroll);\n }, [sticky, combinedRef]);\n\n // Drop-shadow: zet data-scrolled op \"true\" zodra de pagina gescrolled is.\n // Werkt voor zowel sticky als auto-hide — ongeacht scrollrichting.\n React.useEffect(() => {\n if (sticky === 'none') return;\n\n const updateScrolled = () => {\n combinedRef.current?.setAttribute(\n 'data-scrolled',\n String(window.scrollY > 0)\n );\n };\n\n // Direct bij mount instellen (pagina kan al gescrolled zijn)\n updateScrolled();\n\n window.addEventListener('scroll', updateScrolled, { passive: true });\n return () => window.removeEventListener('scroll', updateScrolled);\n }, [sticky, combinedRef]);\n\n // Focus management: bij openen zoekpaneel → focus naar input (klein of compact)\n React.useEffect(() => {\n if (isSearchOpen) {\n searchInputRef.current?.focus();\n compactSearchInputRef.current?.focus();\n }\n }, [isSearchOpen]);\n\n const handleMenuOpen = () => {\n setIsMenuOpen(true);\n onMenuOpen?.();\n };\n\n const handleMenuClose = () => {\n setIsMenuOpen(false);\n onMenuClose?.();\n // Focus terug naar menuknop\n menuButtonRef.current?.focus();\n };\n\n const handleSearchToggle = () => {\n const opening = !isSearchOpen;\n setIsSearchOpen(opening);\n if (opening) {\n onSearchOpen?.();\n } else {\n onSearchClose?.();\n // Focus terug naar zoek/sluit-knop — afhankelijk van actieve layout\n searchButtonRef.current?.focus();\n compactSearchButtonRef.current?.focus();\n }\n };\n\n const classes = classNames(\n 'dsn-page-header',\n sticky === 'sticky' && 'dsn-page-header--sticky',\n sticky === 'auto-hide' && 'dsn-page-header--auto-hide',\n layout === 'compact' && 'dsn-page-header--compact',\n colorScheme === 'inverse' && 'dsn-page-header--inverse',\n className\n );\n\n return (\n <>\n <header\n ref={combinedRef}\n className={classes}\n {...(sticky === 'auto-hide' ? { 'data-hidden': 'false' } : {})}\n {...props}\n >\n {/* ----------------------------------------------------------------\n Small viewport layout (verborgen boven 64em via CSS display:none)\n ---------------------------------------------------------------- */}\n <div className=\"dsn-page-header__small-layout\">\n <div className=\"dsn-page-header__inner\">\n {/* Inline-start: menuknop */}\n <div className=\"dsn-page-header__start\">\n {!hideMenuButton && (\n <Button\n ref={menuButtonRef}\n variant=\"subtle\"\n onClick={handleMenuOpen}\n iconStart={<Icon name=\"menu\" aria-hidden />}\n >\n {menuButtonLabel}\n {menuButtonBadgeLabel && (\n <span className=\"dsn-visually-hidden\">\n , {menuButtonBadgeLabel}\n </span>\n )}\n {menuButtonBadge}\n </Button>\n )}\n </div>\n\n {/* Gecentreerd logo */}\n <div className=\"dsn-page-header__logo\">{logoSlot}</div>\n\n {/* Inline-end: zoekknop / sluitknop */}\n <div className=\"dsn-page-header__end\">\n {!hideSearchButton && (\n <Button\n ref={searchButtonRef}\n variant=\"subtle\"\n aria-expanded={isSearchOpen}\n aria-controls={searchPanelId}\n onClick={handleSearchToggle}\n iconStart={\n <Icon name={isSearchOpen ? 'x' : 'search'} aria-hidden />\n }\n >\n {isSearchOpen ? closeButtonLabel : searchButtonLabel}\n </Button>\n )}\n </div>\n </div>\n\n {/* Zoekpaneel */}\n <div\n id={searchPanelId}\n className=\"dsn-page-header__search-panel\"\n hidden={!isSearchOpen}\n >\n <div className=\"dsn-page-header__search-inner\">\n <SearchInput\n ref={searchInputRef}\n placeholder={searchInputPlaceholder}\n aria-label={searchInputAriaLabel}\n />\n <Button variant=\"strong\">{searchSubmitLabel}</Button>\n </div>\n </div>\n </div>\n\n {/* ----------------------------------------------------------------\n Large viewport layout (zichtbaar boven 64em, alleen bij layout=\"default\")\n ---------------------------------------------------------------- */}\n {layout === 'default' && (\n <div className=\"dsn-page-header__large-layout\">\n {/* Masthead: neutrale achtergrond — logo + servicemenu + zoek */}\n <div className=\"dsn-page-header__masthead\">\n <div className=\"dsn-page-header__masthead-inner\">\n <div className=\"dsn-page-header__logo\">{logoSlot}</div>\n\n <div className=\"dsn-page-header__secondary-nav\">\n {(secondaryNavigationLarge ?? secondaryNavigation) && (\n <nav aria-label={secondaryNavAriaLabel}>\n {secondaryNavigationLarge ?? secondaryNavigation}\n </nav>\n )}\n {searchSlot && (\n <div className=\"dsn-page-header__searchbox\">\n {searchSlot}\n </div>\n )}\n </div>\n </div>\n </div>\n\n {/* Navigatiebalk: accent-1 achtergrond — primaire navigatie (large viewport) */}\n <div className=\"dsn-page-header__navbar\">\n <div className=\"dsn-page-header__navbar-inner\">\n {(primaryNavigationLarge ?? primaryNavigation) && (\n <nav aria-label={primaryNavAriaLabel}>\n {primaryNavigationLarge ?? primaryNavigation}\n </nav>\n )}\n </div>\n </div>\n </div>\n )}\n\n {/* ----------------------------------------------------------------\n Compact layout (zichtbaar boven 64em wanneer layout=\"compact\")\n Eén rij: logo (1fr) | primaire navigatie (auto) | servicemenu + zoek (1fr)\n ---------------------------------------------------------------- */}\n {layout === 'compact' && (\n <div className=\"dsn-page-header__compact-layout\">\n <div className=\"dsn-page-header__compact-inner\">\n {/* Logo — inline-start (eerste gridkolom) */}\n <div className=\"dsn-page-header__logo\">{logoSlot}</div>\n\n {/* Primaire navigatie — gecentreerd (middelste gridkolom)\n Gebruikt primaryNavigationLarge als aanwezig, anders primaryNavigation.\n Dit garandeert dat de Drawer altijd de verticale variant ontvangt. */}\n <div className=\"dsn-page-header__compact-primary-nav\">\n {(primaryNavigationLarge ?? primaryNavigation) && (\n <nav aria-label={primaryNavAriaLabel}>\n {primaryNavigationLarge ?? primaryNavigation}\n </nav>\n )}\n </div>\n\n {/* Servicemenu + zoekknop (icon-only) — inline-end (derde gridkolom) */}\n <div className=\"dsn-page-header__compact-secondary\">\n {(secondaryNavigationLarge ?? secondaryNavigation) && (\n <nav aria-label={secondaryNavAriaLabel}>\n {secondaryNavigationLarge ?? secondaryNavigation}\n </nav>\n )}\n {!hideSearchButton && (\n <Button\n ref={compactSearchButtonRef}\n variant=\"subtle\"\n iconOnly\n aria-expanded={isSearchOpen}\n aria-controls={compactSearchPanelId}\n onClick={handleSearchToggle}\n iconStart={\n <Icon\n name={isSearchOpen ? 'x' : 'search'}\n aria-hidden\n />\n }\n >\n {isSearchOpen ? closeButtonLabel : searchButtonLabel}\n </Button>\n )}\n </div>\n </div>\n\n {/* Zoekpaneel compact layout */}\n <div\n id={compactSearchPanelId}\n className=\"dsn-page-header__search-panel\"\n hidden={!isSearchOpen}\n >\n <div className=\"dsn-page-header__search-inner\">\n <SearchInput\n ref={compactSearchInputRef}\n placeholder={searchInputPlaceholder}\n aria-label={searchInputAriaLabel}\n />\n <Button variant=\"strong\">{searchSubmitLabel}</Button>\n </div>\n </div>\n </div>\n )}\n </header>\n\n {/* Navigatielade (sibling aan PageHeader, altijd in DOM) */}\n <Drawer\n isOpen={isMenuOpen}\n onClose={handleMenuClose}\n side=\"left\"\n triggerRef={menuButtonRef}\n >\n <DrawerHeader>\n <DrawerHeading>{menuButtonLabel}</DrawerHeading>\n </DrawerHeader>\n <DrawerBody>\n <Stack space=\"5xl\">\n {primaryNavigation && (\n <nav aria-label={primaryNavAriaLabel}>{primaryNavigation}</nav>\n )}\n {secondaryNavigation && (\n <nav aria-label={secondaryNavAriaLabel}>\n {secondaryNavigation}\n </nav>\n )}\n </Stack>\n </DrawerBody>\n </Drawer>\n </>\n );\n }\n);\n\nPageHeader.displayName = 'PageHeader';\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchInput.d.mts","names":[],"sources":["../../src/SearchInput/SearchInput.tsx"],"mappings":";;;
|
|
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 +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 '
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Select.d.mts","names":[],"sources":["../../src/Select/Select.tsx"],"mappings":";;;
|
|
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"}
|