tempest-react-sdk 0.67.0 → 0.68.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/README.md +97 -93
- package/dist/auth/create-tempest-auth.cjs +1 -1
- package/dist/auth/create-tempest-auth.cjs.map +1 -1
- package/dist/auth/create-tempest-auth.js +41 -40
- package/dist/auth/create-tempest-auth.js.map +1 -1
- package/dist/components/BottomSheet/BottomSheet.cjs +1 -1
- package/dist/components/BottomSheet/BottomSheet.cjs.map +1 -1
- package/dist/components/BottomSheet/BottomSheet.js +34 -30
- package/dist/components/BottomSheet/BottomSheet.js.map +1 -1
- package/dist/components/Card/Card.module.cjs.map +1 -1
- package/dist/components/Card/Card.module.js.map +1 -1
- package/dist/components/Combobox/Combobox.cjs +1 -1
- package/dist/components/Combobox/Combobox.cjs.map +1 -1
- package/dist/components/Combobox/Combobox.js +78 -77
- package/dist/components/Combobox/Combobox.js.map +1 -1
- package/dist/components/Combobox/Combobox.module.cjs.map +1 -1
- package/dist/components/Combobox/Combobox.module.js.map +1 -1
- package/dist/components/Command/Command.cjs +1 -1
- package/dist/components/Command/Command.js +4 -4
- package/dist/components/DataTable/DataTable.cjs +1 -1
- package/dist/components/DataTable/DataTable.cjs.map +1 -1
- package/dist/components/DataTable/DataTable.js +138 -127
- package/dist/components/DataTable/DataTable.js.map +1 -1
- package/dist/components/DataTable/use-dev-warnings.cjs +1 -1
- package/dist/components/DataTable/use-dev-warnings.cjs.map +1 -1
- package/dist/components/DataTable/use-dev-warnings.js +5 -3
- package/dist/components/DataTable/use-dev-warnings.js.map +1 -1
- package/dist/components/Drawer/Drawer.cjs +1 -1
- package/dist/components/Drawer/Drawer.cjs.map +1 -1
- package/dist/components/Drawer/Drawer.js +43 -40
- package/dist/components/Drawer/Drawer.js.map +1 -1
- package/dist/components/FilterPanel/FilterPanel.cjs +2 -0
- package/dist/components/FilterPanel/FilterPanel.cjs.map +1 -0
- package/dist/components/FilterPanel/FilterPanel.js +75 -0
- package/dist/components/FilterPanel/FilterPanel.js.map +1 -0
- package/dist/components/FilterPanel/FilterPanel.module.cjs +3 -0
- package/dist/components/FilterPanel/FilterPanel.module.cjs.map +1 -0
- package/dist/components/FilterPanel/FilterPanel.module.js +12 -0
- package/dist/components/FilterPanel/FilterPanel.module.js.map +1 -0
- package/dist/components/Lightbox/Lightbox.cjs +1 -1
- package/dist/components/Lightbox/Lightbox.js +5 -5
- package/dist/components/Modal/Modal.cjs +1 -1
- package/dist/components/Modal/Modal.cjs.map +1 -1
- package/dist/components/Modal/Modal.js +44 -41
- package/dist/components/Modal/Modal.js.map +1 -1
- package/dist/components/Pagination/Pagination.cjs +1 -1
- package/dist/components/Pagination/Pagination.cjs.map +1 -1
- package/dist/components/Pagination/Pagination.js +31 -30
- package/dist/components/Pagination/Pagination.js.map +1 -1
- package/dist/components/Portal/tab-bridge.cjs +1 -1
- package/dist/components/Portal/tab-bridge.cjs.map +1 -1
- package/dist/components/Portal/tab-bridge.js +21 -23
- package/dist/components/Portal/tab-bridge.js.map +1 -1
- package/dist/components/Select/CaretIcon.cjs +2 -0
- package/dist/components/Select/CaretIcon.cjs.map +1 -0
- package/dist/components/Select/CaretIcon.js +21 -0
- package/dist/components/Select/CaretIcon.js.map +1 -0
- package/dist/components/Select/Select.cjs +1 -1
- package/dist/components/Select/Select.cjs.map +1 -1
- package/dist/components/Select/Select.js +33 -47
- package/dist/components/Select/Select.js.map +1 -1
- package/dist/components/Select/Select.module.cjs +1 -1
- package/dist/components/Select/Select.module.cjs.map +1 -1
- package/dist/components/Select/Select.module.js +4 -3
- package/dist/components/Select/Select.module.js.map +1 -1
- package/dist/components/Select/options.cjs +2 -0
- package/dist/components/Select/options.cjs.map +1 -0
- package/dist/components/Select/options.js +25 -0
- package/dist/components/Select/options.js.map +1 -0
- package/dist/components/Sidebar/Sidebar.cjs +1 -1
- package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
- package/dist/components/Sidebar/Sidebar.js +49 -43
- package/dist/components/Sidebar/Sidebar.js.map +1 -1
- package/dist/components/Sidebar/active-nav-key.cjs +2 -0
- package/dist/components/Sidebar/active-nav-key.cjs.map +1 -0
- package/dist/components/Sidebar/active-nav-key.js +22 -0
- package/dist/components/Sidebar/active-nav-key.js.map +1 -0
- package/dist/hooks/tab-sequence.cjs +2 -0
- package/dist/hooks/tab-sequence.cjs.map +1 -0
- package/dist/hooks/tab-sequence.js +53 -0
- package/dist/hooks/tab-sequence.js.map +1 -0
- package/dist/hooks/use-deep-memo.cjs +1 -1
- package/dist/hooks/use-deep-memo.cjs.map +1 -1
- package/dist/hooks/use-deep-memo.js +1 -1
- package/dist/hooks/use-deep-memo.js.map +1 -1
- package/dist/hooks/use-draft-filters.cjs +2 -0
- package/dist/hooks/use-draft-filters.cjs.map +1 -0
- package/dist/hooks/use-draft-filters.js +49 -0
- package/dist/hooks/use-draft-filters.js.map +1 -0
- package/dist/hooks/use-focus-trap.cjs +1 -1
- package/dist/hooks/use-focus-trap.cjs.map +1 -1
- package/dist/hooks/use-focus-trap.js +31 -34
- package/dist/hooks/use-focus-trap.js.map +1 -1
- package/dist/http/api-client.cjs +1 -1
- package/dist/http/api-client.cjs.map +1 -1
- package/dist/http/api-client.js +92 -80
- package/dist/http/api-client.js.map +1 -1
- package/dist/http/credential-scope.cjs +1 -1
- package/dist/http/credential-scope.cjs.map +1 -1
- package/dist/http/credential-scope.js +5 -3
- package/dist/http/credential-scope.js.map +1 -1
- package/dist/http/csrf.cjs +2 -0
- package/dist/http/csrf.cjs.map +1 -0
- package/dist/http/csrf.js +46 -0
- package/dist/http/csrf.js.map +1 -0
- package/dist/http/resumable-upload.cjs +1 -1
- package/dist/http/resumable-upload.cjs.map +1 -1
- package/dist/http/resumable-upload.js +131 -123
- package/dist/http/resumable-upload.js.map +1 -1
- package/dist/http/upload-with-progress.cjs +1 -1
- package/dist/http/upload-with-progress.cjs.map +1 -1
- package/dist/http/upload-with-progress.js +41 -33
- package/dist/http/upload-with-progress.js.map +1 -1
- package/dist/query/use-paginated-query.cjs +1 -1
- package/dist/query/use-paginated-query.cjs.map +1 -1
- package/dist/query/use-paginated-query.js +47 -30
- package/dist/query/use-paginated-query.js.map +1 -1
- package/dist/styles/FilterPanel.css +2 -0
- package/dist/styles/component/Card.css +1 -1
- package/dist/styles/component/Combobox.css +4 -3
- package/dist/styles/component/FilterPanel.css +5 -0
- package/dist/styles/component/Select.css +13 -6
- package/dist/styles/core.css +4 -4
- package/dist/styles/data.css +4 -0
- package/dist/styles/forms.css +17 -9
- package/dist/styles/identity.css +1 -1
- package/dist/styles/manifest.json +6 -0
- package/dist/styles/tokens.css +4 -4
- package/dist/styles.css +1 -1
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +658 -22
- package/dist/tempest-react-sdk.js +347 -342
- package/dist/vite/tempest-csp.cjs +3 -0
- package/dist/vite/tempest-csp.cjs.map +1 -0
- package/dist/vite/tempest-csp.js +189 -0
- package/dist/vite/tempest-csp.js.map +1 -0
- package/dist/vite.cjs +1 -1
- package/dist/vite.d.ts +145 -0
- package/dist/vite.js +2 -1
- package/package.json +2 -2
- package/template/vite.config.ts +8 -1
- package/template-pwa/vite.config.ts +6 -0
|
@@ -1,79 +1,65 @@
|
|
|
1
1
|
import { cn as e } from "../../utils/cn.js";
|
|
2
|
-
import t from "./
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
2
|
+
import { CaretIcon as t } from "./CaretIcon.js";
|
|
3
|
+
import n from "./Select.module.js";
|
|
4
|
+
import { forwardRef as r, useId as i } from "react";
|
|
5
|
+
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
5
6
|
//#region src/components/Select/Select.tsx
|
|
6
|
-
var
|
|
7
|
-
let { options: c, placeholder: l, wrapperClassName: u, className: d, children: f, id: p, required: m, variant: h = "field", label: g, helperText: _, error: v,
|
|
8
|
-
className: e(
|
|
9
|
-
children: [/* @__PURE__ */
|
|
10
|
-
ref:
|
|
11
|
-
id:
|
|
7
|
+
var s = r(function(r, s) {
|
|
8
|
+
let { options: c, placeholder: l, wrapperClassName: u, className: d, children: f, id: p, required: m, variant: h = "field", label: g, helperText: _, error: v, caretIcon: y, ...b } = r, x = h === "chip", S = h === "bare", C = i(), w = p ?? C, T = /* @__PURE__ */ o("div", {
|
|
9
|
+
className: e(n.field, x && n.chip, S && n.bare, S && u),
|
|
10
|
+
children: [/* @__PURE__ */ o("select", {
|
|
11
|
+
ref: s,
|
|
12
|
+
id: w,
|
|
12
13
|
"aria-invalid": !!v,
|
|
13
14
|
required: m,
|
|
14
|
-
className: e(
|
|
15
|
-
...
|
|
15
|
+
className: e(n.select, d),
|
|
16
|
+
...b,
|
|
16
17
|
children: [
|
|
17
|
-
l && /* @__PURE__ */
|
|
18
|
+
l && /* @__PURE__ */ a("option", {
|
|
18
19
|
value: "",
|
|
19
20
|
disabled: !0,
|
|
20
21
|
hidden: !0,
|
|
21
22
|
children: l
|
|
22
23
|
}),
|
|
23
|
-
c?.map((e) => /* @__PURE__ */
|
|
24
|
+
c?.map((e) => /* @__PURE__ */ a("option", {
|
|
24
25
|
value: e.value,
|
|
25
26
|
disabled: e.disabled,
|
|
26
27
|
children: e.label
|
|
27
28
|
}, e.value)),
|
|
28
29
|
f
|
|
29
30
|
]
|
|
30
|
-
}), /* @__PURE__ */
|
|
31
|
-
className:
|
|
31
|
+
}), /* @__PURE__ */ a("span", {
|
|
32
|
+
className: n.caret,
|
|
32
33
|
"aria-hidden": !0,
|
|
33
|
-
children: /* @__PURE__ */
|
|
34
|
+
children: y ?? /* @__PURE__ */ a(t, {})
|
|
34
35
|
})]
|
|
35
36
|
});
|
|
36
|
-
return
|
|
37
|
-
className: e(
|
|
38
|
-
children:
|
|
39
|
-
}) : /* @__PURE__ */
|
|
40
|
-
className: e(
|
|
37
|
+
return S ? T : x ? /* @__PURE__ */ a("div", {
|
|
38
|
+
className: e(n.chipWrapper, u),
|
|
39
|
+
children: T
|
|
40
|
+
}) : /* @__PURE__ */ o("div", {
|
|
41
|
+
className: e(n.wrapper, v && n.error, u),
|
|
41
42
|
children: [
|
|
42
|
-
g && /* @__PURE__ */
|
|
43
|
-
htmlFor:
|
|
44
|
-
className:
|
|
45
|
-
children: [g, m && /* @__PURE__ */
|
|
46
|
-
className:
|
|
43
|
+
g && /* @__PURE__ */ o("label", {
|
|
44
|
+
htmlFor: w,
|
|
45
|
+
className: n.label,
|
|
46
|
+
children: [g, m && /* @__PURE__ */ a("span", {
|
|
47
|
+
className: n.required,
|
|
47
48
|
children: "*"
|
|
48
49
|
})]
|
|
49
50
|
}),
|
|
50
|
-
|
|
51
|
-
v ? /* @__PURE__ */
|
|
52
|
-
className:
|
|
51
|
+
T,
|
|
52
|
+
v ? /* @__PURE__ */ a("span", {
|
|
53
|
+
className: n.errorText,
|
|
53
54
|
children: v
|
|
54
|
-
}) : _ ? /* @__PURE__ */
|
|
55
|
-
className:
|
|
55
|
+
}) : _ ? /* @__PURE__ */ a("span", {
|
|
56
|
+
className: n.helper,
|
|
56
57
|
children: _
|
|
57
58
|
}) : null
|
|
58
59
|
]
|
|
59
60
|
});
|
|
60
61
|
});
|
|
61
|
-
function s() {
|
|
62
|
-
return /* @__PURE__ */ i("svg", {
|
|
63
|
-
width: "14",
|
|
64
|
-
height: "14",
|
|
65
|
-
viewBox: "0 0 24 24",
|
|
66
|
-
fill: "none",
|
|
67
|
-
children: /* @__PURE__ */ i("path", {
|
|
68
|
-
d: "M6 9l6 6 6-6",
|
|
69
|
-
stroke: "currentColor",
|
|
70
|
-
strokeWidth: "2",
|
|
71
|
-
strokeLinecap: "round",
|
|
72
|
-
strokeLinejoin: "round"
|
|
73
|
-
})
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
62
|
//#endregion
|
|
77
|
-
export {
|
|
63
|
+
export { s as Select };
|
|
78
64
|
|
|
79
65
|
//# sourceMappingURL=Select.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Select.js","names":[],"sources":["../../../src/components/Select/Select.tsx"],"sourcesContent":["import { forwardRef, useId } from \"react\";\nimport type { SelectHTMLAttributes } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Select.module.css\";\n\nexport interface SelectOption {\n value: string | number;\n label: string;\n disabled?: boolean;\n}\n\n/** What
|
|
1
|
+
{"version":3,"file":"Select.js","names":[],"sources":["../../../src/components/Select/Select.tsx"],"sourcesContent":["import { forwardRef, useId } from \"react\";\nimport type { ReactNode, SelectHTMLAttributes } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { CaretIcon } from \"./CaretIcon\";\nimport styles from \"./Select.module.css\";\n\nexport interface SelectOption {\n value: string | number;\n label: string;\n disabled?: boolean;\n}\n\n/** What every variant of {@link Select} accepts. */\nexport interface SelectBaseProps extends SelectHTMLAttributes<HTMLSelectElement> {\n options?: SelectOption[];\n placeholder?: string;\n /**\n * Class for the outermost element. On `\"bare\"` that element is the shell\n * holding the `<select>` and the caret, which is where the app's own look goes.\n */\n wrapperClassName?: string;\n /**\n * Replaces the built-in chevron, following the `icon?: ReactNode` convention\n * the rest of the SDK uses.\n *\n * The icon is drawn in the caret slot, which is positioned by\n * `--tempest-control-caret-offset` and sized by `--tempest-control-caret-size`;\n * an `<svg>` passed here is stretched to that size, so the token — not the\n * icon's own `width`/`height` — decides how big the caret is in every density.\n */\n caretIcon?: ReactNode;\n}\n\n/** The full form field: label, control, helper text and error slot. */\nexport interface SelectFieldProps extends SelectBaseProps {\n variant?: \"field\";\n label?: string;\n helperText?: string;\n error?: string;\n}\n\n/**\n * The bare control, sized to its content.\n *\n * `aria-label` is required rather than optional, because the variant removes the\n * only visible label the component had. A settings row names the control in the\n * row's own text, which the `<select>` has no relationship to — so without this\n * the screen reader announces a combobox with no name at all.\n */\nexport interface SelectChipProps extends SelectBaseProps {\n variant: \"chip\";\n \"aria-label\": string;\n}\n\n/**\n * The mechanism without the look: `appearance: none`, the caret and the focus\n * ring, and no border, background, radius, shadow or height.\n *\n * The `<select>` inherits `background-color` and `color` from the shell (the\n * element `wrapperClassName` lands on). The background is not cosmetic: the\n * native popup paints each `<option>` from the `<select>`'s background, so a\n * transparent control would hand the menu back to the system white.\n *\n * Like `\"chip\"`, the variant has no visible label, so an accessible name is\n * required — through `aria-label` or `aria-labelledby`.\n */\nexport type SelectBareProps = SelectBaseProps & { variant: \"bare\" } & (\n { \"aria-label\": string } | { \"aria-labelledby\": string }\n );\n\nexport type SelectProps = SelectFieldProps | SelectChipProps | SelectBareProps;\n\n/**\n * Native `<select>` wrapper. Either provide `options` for a quick render, or\n * pass `<option>` children directly.\n *\n * Three variants:\n *\n * - `\"field\"` (default) — label, control, helper text and error slot, for a form.\n * - `\"chip\"` — the control alone, sized to its content, for a settings row where\n * the icon and the label are already on the left and the control belongs on the\n * right. Rendering the full field there stacks two labelled fields inside one\n * list item, which is why every app ended up hand-rolling a native `<select>`\n * with `appearance: none` — and hand-rolling it loses the focus ring, the\n * disabled styling and the caret this component already solves.\n * - `\"bare\"` — the same mechanism with none of the look, for an app with its own\n * visual identity: the shell renders `wrapper > select + caret`, and the app\n * dresses the wrapper through `wrapperClassName`. See {@link SelectBareProps}.\n *\n * @example\n * <Select label=\"Estado\" options={states} placeholder=\"Selecione\" />\n *\n * @example\n * <ListTile title=\"Idioma\" trailing={<Select variant=\"chip\" aria-label=\"Idioma\" options={langs} />} />\n *\n * @example\n * <Select variant=\"bare\" wrapperClassName={styles.filtro} aria-label=\"Status\" options={status} />\n */\nexport const Select = forwardRef<HTMLSelectElement, SelectProps>(function Select(props, ref) {\n const {\n options,\n placeholder,\n wrapperClassName,\n className,\n children,\n id,\n required,\n variant = \"field\",\n label,\n helperText,\n error,\n caretIcon,\n ...rest\n } = props as SelectBaseProps & {\n variant?: SelectProps[\"variant\"];\n label?: string;\n helperText?: string;\n error?: string;\n };\n const isChip = variant === \"chip\";\n const isBare = variant === \"bare\";\n const generatedId = useId();\n const selectId = id ?? generatedId;\n\n const control = (\n <div\n className={cn(\n styles.field,\n isChip && styles.chip,\n isBare && styles.bare,\n isBare && wrapperClassName,\n )}\n >\n <select\n ref={ref}\n id={selectId}\n aria-invalid={!!error}\n required={required}\n className={cn(styles.select, className)}\n {...rest}\n >\n {placeholder && (\n <option value=\"\" disabled hidden>\n {placeholder}\n </option>\n )}\n {options?.map((opt) => (\n <option key={opt.value} value={opt.value} disabled={opt.disabled}>\n {opt.label}\n </option>\n ))}\n {children}\n </select>\n <span className={styles.caret} aria-hidden>\n {caretIcon ?? <CaretIcon />}\n </span>\n </div>\n );\n\n if (isBare) {\n return control;\n }\n\n if (isChip) {\n return <div className={cn(styles.chipWrapper, wrapperClassName)}>{control}</div>;\n }\n\n return (\n <div className={cn(styles.wrapper, error && styles.error, wrapperClassName)}>\n {label && (\n <label htmlFor={selectId} className={styles.label}>\n {label}\n {required && <span className={styles.required}>*</span>}\n </label>\n )}\n {control}\n {error ? (\n <span className={styles.errorText}>{error}</span>\n ) : helperText ? (\n <span className={styles.helper}>{helperText}</span>\n ) : null}\n </div>\n );\n});\n"],"mappings":";;;;;;AAkGA,IAAa,IAAS,EAA2C,SAAgB,GAAO,GAAK;CACzF,IAAM,EACF,YACA,gBACA,qBACA,cACA,aACA,OACA,aACA,aAAU,SACV,UACA,eACA,UACA,cACA,GAAG,MACH,GAME,IAAS,MAAY,QACrB,IAAS,MAAY,QACrB,IAAc,EAAM,GACpB,IAAW,KAAM,GAEjB,IACF,kBAAC,OAAD;EACI,WAAW,EACP,EAAO,OACP,KAAU,EAAO,MACjB,KAAU,EAAO,MACjB,KAAU,CACd;EANJ,UAAA,CAQI,kBAAC,UAAD;GACS;GACL,IAAI;GACJ,gBAAc,CAAC,CAAC;GACN;GACV,WAAW,EAAG,EAAO,QAAQ,CAAS;GACtC,GAAI;GANR,UAAA;IAQK,KACG,kBAAC,UAAD;KAAQ,OAAM;KAAG,UAAA;KAAS,QAAA;KACrB,UAAA;IACG,CAAA;IAEX,GAAS,KAAK,MACX,kBAAC,UAAD;KAAwB,OAAO,EAAI;KAAO,UAAU,EAAI;KACnD,UAAA,EAAI;IACD,GAFK,EAAI,KAET,CACX;IACA;GACG;EACR,CAAA,GAAA,kBAAC,QAAD;GAAM,WAAW,EAAO;GAAO,eAAA;GAC1B,UAAA,KAAa,kBAAC,GAAD,CAAY,CAAA;EACxB,CAAA,CACL;;CAWT,OARI,IACO,IAGP,IACO,kBAAC,OAAD;EAAK,WAAW,EAAG,EAAO,aAAa,CAAgB;EAAI,UAAA;CAAa,CAAA,IAI/E,kBAAC,OAAD;EAAK,WAAW,EAAG,EAAO,SAAS,KAAS,EAAO,OAAO,CAAgB;EAA1E,UAAA;GACK,KACG,kBAAC,SAAD;IAAO,SAAS;IAAU,WAAW,EAAO;IAA5C,UAAA,CACK,GACA,KAAY,kBAAC,QAAD;KAAM,WAAW,EAAO;KAAU,UAAA;IAAO,CAAA,CACnD;;GAEV;GACA,IACG,kBAAC,QAAD;IAAM,WAAW,EAAO;IAAY,UAAA;GAAY,CAAA,IAChD,IACA,kBAAC,QAAD;IAAM,WAAW,EAAO;IAAS,UAAA;GAAiB,CAAA,IAClD;EACH;;AAEb,CAAC"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
require("../../styles/component/Select.css");
|
|
2
|
-
var e=`tempest_wrapper_KS2K3`,t=`tempest_label_Lmgos`,n=`tempest_required_PvDVJ`,r=`tempest_field_h-wBy`,i=`tempest_select_cjdcr`,a=`tempest_caret_MdCao`,o=`tempest_error_sw9MU`,s=`tempest_helper_frosK`,c=`tempest_errorText_-zd6i`,l=`tempest_chipWrapper_5keyI`,u=`tempest_chip_r4NVm`,d={wrapper:e,label:t,required:n,field:r,select:i,caret:a,error:o,helper:s,errorText:c,chipWrapper:l,chip:u};exports.caret=a,exports.chip=u,exports.chipWrapper=l,exports.default=
|
|
2
|
+
var e=`tempest_wrapper_KS2K3`,t=`tempest_label_Lmgos`,n=`tempest_required_PvDVJ`,r=`tempest_field_h-wBy`,i=`tempest_select_cjdcr`,a=`tempest_caret_MdCao`,o=`tempest_error_sw9MU`,s=`tempest_helper_frosK`,c=`tempest_errorText_-zd6i`,l=`tempest_chipWrapper_5keyI`,u=`tempest_chip_r4NVm`,d=`tempest_bare_n9BTx`,f={wrapper:e,label:t,required:n,field:r,select:i,caret:a,error:o,helper:s,errorText:c,chipWrapper:l,chip:u,bare:d};exports.bare=d,exports.caret=a,exports.chip=u,exports.chipWrapper=l,exports.default=f,exports.error=o,exports.errorText=c,exports.field=r,exports.helper=s,exports.label=t,exports.required=n,exports.select=i,exports.wrapper=e;
|
|
3
3
|
//# sourceMappingURL=Select.module.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Select.module.cjs","names":[],"sources":["../../../src/components/Select/Select.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n width: 100%;\n}\n\n.label {\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-semibold);\n color: var(--tempest-text);\n line-height: var(--tempest-leading-snug);\n}\n\n.required {\n color: var(--tempest-danger);\n margin-left: 2px;\n}\n\n.field {\n position: relative;\n display: flex;\n align-items: center;\n}\n\n.select {\n width: 100%;\n height: var(--tempest-control-height-md);\n padding: 0
|
|
1
|
+
{"version":3,"file":"Select.module.cjs","names":[],"sources":["../../../src/components/Select/Select.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n width: 100%;\n}\n\n.label {\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-semibold);\n color: var(--tempest-text);\n line-height: var(--tempest-leading-snug);\n}\n\n.required {\n color: var(--tempest-danger);\n margin-left: 2px;\n}\n\n.field {\n --_tempest-caret-offset: var(--tempest-control-caret-offset, 12px);\n --_tempest-caret-size: var(--tempest-control-caret-size, 14px);\n --_tempest-caret-gap: 10px;\n position: relative;\n display: flex;\n align-items: center;\n}\n\n.select {\n width: 100%;\n height: var(--tempest-control-height-md);\n padding: 0\n calc(var(--_tempest-caret-offset) + var(--_tempest-caret-size) + var(--_tempest-caret-gap))\n 0 var(--tempest-control-padding-md);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-control-radius);\n background-color: var(--tempest-bg);\n color: var(--tempest-text);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-control-font-md);\n line-height: var(--tempest-leading-snug);\n appearance: none;\n cursor: pointer;\n box-shadow: var(--tempest-shadow-xs);\n transition: var(--tempest-transition-color), var(--tempest-transition-shadow);\n}\n\n.select:hover:not(:disabled):not(:focus) {\n border-color: var(--tempest-border-strong);\n}\n\n.select:focus {\n outline: none;\n border-color: var(--tempest-primary);\n box-shadow: 0 0 0 var(--tempest-focus-ring-width) var(--tempest-focus-ring-color);\n}\n\n.select:disabled {\n background-color: var(--tempest-surface);\n cursor: not-allowed;\n color: var(--tempest-text-muted);\n box-shadow: none;\n}\n\n.caret {\n position: absolute;\n right: var(--_tempest-caret-offset);\n display: flex;\n align-items: center;\n justify-content: center;\n width: var(--_tempest-caret-size);\n height: var(--_tempest-caret-size);\n pointer-events: none;\n color: var(--tempest-text-muted);\n}\n\n.field > .caret > svg {\n display: block;\n flex: none;\n width: 100%;\n height: 100%;\n margin: 0;\n}\n\n.error .select {\n border-color: var(--tempest-danger);\n}\n\n.error .select:focus {\n box-shadow: 0 0 0 var(--tempest-focus-ring-width)\n color-mix(in srgb, var(--tempest-danger) 25%, transparent);\n}\n\n.helper {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-text-muted);\n line-height: var(--tempest-leading-snug);\n}\n\n.errorText {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-danger);\n line-height: var(--tempest-leading-snug);\n}\n\n.chipWrapper {\n display: inline-flex;\n}\n\n.chip {\n --_tempest-caret-offset: calc(var(--tempest-control-caret-offset, 12px) - 2px);\n --_tempest-caret-gap: 6px;\n width: auto;\n}\n\n.chip .select {\n width: auto;\n height: var(--tempest-control-height-sm);\n padding: 0\n calc(var(--_tempest-caret-offset) + var(--_tempest-caret-size) + var(--_tempest-caret-gap))\n 0 var(--tempest-space-3);\n border-radius: var(--tempest-radius-full);\n background-color: var(--tempest-surface);\n font-size: var(--tempest-text-sm);\n box-shadow: none;\n}\n\n.bare {\n display: inline-flex;\n}\n\n.bare .select {\n flex: 1 1 auto;\n align-self: stretch;\n min-width: 0;\n height: auto;\n margin: 0;\n padding-block: 0;\n padding-inline-start: 0;\n border: 0;\n border-radius: inherit;\n background-color: inherit;\n color: inherit;\n font: inherit;\n letter-spacing: inherit;\n box-shadow: none;\n}\n\n.bare .select:focus {\n box-shadow: none;\n}\n\n.bare:focus-within {\n outline: var(--tempest-focus-ring-width) solid var(--tempest-focus-ring-color);\n outline-offset: 2px;\n}\n\n.bare .select:disabled {\n background-color: inherit;\n color: inherit;\n}\n\n.bare:has(> .select:disabled) {\n opacity: 0.6;\n}\n\n.bare .caret {\n color: inherit;\n}\n"],"mappings":";"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "../../styles/component/Select.css";
|
|
2
2
|
//#region src/components/Select/Select.module.css
|
|
3
|
-
var e = "tempest_wrapper_KS2K3", t = "tempest_label_Lmgos", n = "tempest_required_PvDVJ", r = "tempest_field_h-wBy", i = "tempest_select_cjdcr", a = "tempest_caret_MdCao", o = "tempest_error_sw9MU", s = "tempest_helper_frosK", c = "tempest_errorText_-zd6i", l = "tempest_chipWrapper_5keyI", u = "tempest_chip_r4NVm", d = {
|
|
3
|
+
var e = "tempest_wrapper_KS2K3", t = "tempest_label_Lmgos", n = "tempest_required_PvDVJ", r = "tempest_field_h-wBy", i = "tempest_select_cjdcr", a = "tempest_caret_MdCao", o = "tempest_error_sw9MU", s = "tempest_helper_frosK", c = "tempest_errorText_-zd6i", l = "tempest_chipWrapper_5keyI", u = "tempest_chip_r4NVm", d = "tempest_bare_n9BTx", f = {
|
|
4
4
|
wrapper: e,
|
|
5
5
|
label: t,
|
|
6
6
|
required: n,
|
|
@@ -11,9 +11,10 @@ var e = "tempest_wrapper_KS2K3", t = "tempest_label_Lmgos", n = "tempest_require
|
|
|
11
11
|
helper: s,
|
|
12
12
|
errorText: c,
|
|
13
13
|
chipWrapper: l,
|
|
14
|
-
chip: u
|
|
14
|
+
chip: u,
|
|
15
|
+
bare: d
|
|
15
16
|
};
|
|
16
17
|
//#endregion
|
|
17
|
-
export { a as caret, u as chip, l as chipWrapper,
|
|
18
|
+
export { d as bare, a as caret, u as chip, l as chipWrapper, f as default, o as error, c as errorText, r as field, s as helper, t as label, n as required, i as select, e as wrapper };
|
|
18
19
|
|
|
19
20
|
//# sourceMappingURL=Select.module.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Select.module.js","names":[],"sources":["../../../src/components/Select/Select.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n width: 100%;\n}\n\n.label {\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-semibold);\n color: var(--tempest-text);\n line-height: var(--tempest-leading-snug);\n}\n\n.required {\n color: var(--tempest-danger);\n margin-left: 2px;\n}\n\n.field {\n position: relative;\n display: flex;\n align-items: center;\n}\n\n.select {\n width: 100%;\n height: var(--tempest-control-height-md);\n padding: 0
|
|
1
|
+
{"version":3,"file":"Select.module.js","names":[],"sources":["../../../src/components/Select/Select.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n width: 100%;\n}\n\n.label {\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-semibold);\n color: var(--tempest-text);\n line-height: var(--tempest-leading-snug);\n}\n\n.required {\n color: var(--tempest-danger);\n margin-left: 2px;\n}\n\n.field {\n --_tempest-caret-offset: var(--tempest-control-caret-offset, 12px);\n --_tempest-caret-size: var(--tempest-control-caret-size, 14px);\n --_tempest-caret-gap: 10px;\n position: relative;\n display: flex;\n align-items: center;\n}\n\n.select {\n width: 100%;\n height: var(--tempest-control-height-md);\n padding: 0\n calc(var(--_tempest-caret-offset) + var(--_tempest-caret-size) + var(--_tempest-caret-gap))\n 0 var(--tempest-control-padding-md);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-control-radius);\n background-color: var(--tempest-bg);\n color: var(--tempest-text);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-control-font-md);\n line-height: var(--tempest-leading-snug);\n appearance: none;\n cursor: pointer;\n box-shadow: var(--tempest-shadow-xs);\n transition: var(--tempest-transition-color), var(--tempest-transition-shadow);\n}\n\n.select:hover:not(:disabled):not(:focus) {\n border-color: var(--tempest-border-strong);\n}\n\n.select:focus {\n outline: none;\n border-color: var(--tempest-primary);\n box-shadow: 0 0 0 var(--tempest-focus-ring-width) var(--tempest-focus-ring-color);\n}\n\n.select:disabled {\n background-color: var(--tempest-surface);\n cursor: not-allowed;\n color: var(--tempest-text-muted);\n box-shadow: none;\n}\n\n.caret {\n position: absolute;\n right: var(--_tempest-caret-offset);\n display: flex;\n align-items: center;\n justify-content: center;\n width: var(--_tempest-caret-size);\n height: var(--_tempest-caret-size);\n pointer-events: none;\n color: var(--tempest-text-muted);\n}\n\n.field > .caret > svg {\n display: block;\n flex: none;\n width: 100%;\n height: 100%;\n margin: 0;\n}\n\n.error .select {\n border-color: var(--tempest-danger);\n}\n\n.error .select:focus {\n box-shadow: 0 0 0 var(--tempest-focus-ring-width)\n color-mix(in srgb, var(--tempest-danger) 25%, transparent);\n}\n\n.helper {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-text-muted);\n line-height: var(--tempest-leading-snug);\n}\n\n.errorText {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-danger);\n line-height: var(--tempest-leading-snug);\n}\n\n.chipWrapper {\n display: inline-flex;\n}\n\n.chip {\n --_tempest-caret-offset: calc(var(--tempest-control-caret-offset, 12px) - 2px);\n --_tempest-caret-gap: 6px;\n width: auto;\n}\n\n.chip .select {\n width: auto;\n height: var(--tempest-control-height-sm);\n padding: 0\n calc(var(--_tempest-caret-offset) + var(--_tempest-caret-size) + var(--_tempest-caret-gap))\n 0 var(--tempest-space-3);\n border-radius: var(--tempest-radius-full);\n background-color: var(--tempest-surface);\n font-size: var(--tempest-text-sm);\n box-shadow: none;\n}\n\n.bare {\n display: inline-flex;\n}\n\n.bare .select {\n flex: 1 1 auto;\n align-self: stretch;\n min-width: 0;\n height: auto;\n margin: 0;\n padding-block: 0;\n padding-inline-start: 0;\n border: 0;\n border-radius: inherit;\n background-color: inherit;\n color: inherit;\n font: inherit;\n letter-spacing: inherit;\n box-shadow: none;\n}\n\n.bare .select:focus {\n box-shadow: none;\n}\n\n.bare:focus-within {\n outline: var(--tempest-focus-ring-width) solid var(--tempest-focus-ring-color);\n outline-offset: 2px;\n}\n\n.bare .select:disabled {\n background-color: inherit;\n color: inherit;\n}\n\n.bare:has(> .select:disabled) {\n opacity: 0.6;\n}\n\n.bare .caret {\n color: inherit;\n}\n"],"mappings":";"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e=`all`;function t(e){let t=e instanceof Map?e.entries():Object.entries(e);return Array.from(t,([e,t])=>({value:String(e),label:t}))}function n(e,t=`Todos`){return[{value:`all`,label:t},...e]}function r(e,t=`— Não definido —`){return[{value:``,label:t},...e]}exports.ALL_OPTION_VALUE=e,exports.toOptions=t,exports.withAllOption=n,exports.withEmptyOption=r;
|
|
2
|
+
//# sourceMappingURL=options.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.cjs","names":[],"sources":["../../../src/components/Select/options.ts"],"sourcesContent":["/**\n * The shape every option list in the SDK shares: `Select`, `MultiSelect` and\n * `Combobox` all accept an array of it.\n *\n * `value` is always a `string` here, because that is what a `<select>` hands\n * back in `event.target.value` whatever the option was built from: an option\n * built with the number `0` comes back as the string `\"0\"`, so a list that kept\n * numbers could never be compared with `===` against what the control emits.\n */\nexport interface OptionEntry {\n value: string;\n label: string;\n}\n\n/**\n * The value {@link withAllOption} gives its entry, exported so the filter code\n * compares against a name instead of a magic string.\n *\n * @example\n * const where = status === ALL_OPTION_VALUE ? {} : { status };\n */\nexport const ALL_OPTION_VALUE = \"all\";\n\nconst DEFAULT_ALL_OPTION_LABEL = \"Todos\";\n\nconst DEFAULT_EMPTY_OPTION_LABEL = \"— Não definido —\";\n\n/** The minimum an option needs for the helpers to prepend to its list. */\ntype OptionLike = { value: string | number; label: string };\n\n/**\n * Turn a `value → label` map into the option list `Select`, `MultiSelect` and\n * `Combobox` take.\n *\n * Every key becomes a string `value` (see {@link OptionEntry} for why), so the\n * result is assignable to `SelectOption[]`, `MultiSelectOption[]` and\n * `ComboboxOption[]` alike.\n *\n * **Order.** A `Map` keeps the order it was written in, always. A plain object\n * does too — **except for integer-like keys**, which JavaScript enumerates first\n * and in ascending order whatever order they were written in\n * (`OrdinaryOwnPropertyKeys`, ECMA-262 §10.1.11.1). Measured in Node 24:\n * `Object.keys({ 5: \"Ótimo\", 3: \"Ok\", 1: \"Ruim\" })` is `[\"1\", \"3\", \"5\"]`, and\n * `{ baixa, 10, 2, alta }` enumerates as `[\"2\", \"10\", \"baixa\", \"alta\"]`. When\n * the business order of a numeric scale is not ascending, pass a `Map`.\n *\n * @param source - A record or a `Map` from value to label.\n * @returns A new array, one entry per key, in the order described above.\n *\n * @example\n * const STATUS = { active: \"Ativo\", paused: \"Pausado\" };\n * <Select aria-label=\"Status\" variant=\"chip\" options={toOptions(STATUS)} />\n *\n * @example\n * const HUMOR = new Map([[5, \"Ótimo\"], [3, \"Ok\"], [1, \"Ruim\"]]);\n * toOptions(HUMOR); // 5, 3, 1 — the order written\n */\nexport function toOptions(\n source: Readonly<Record<string, string>> | ReadonlyMap<string | number, string>,\n): OptionEntry[] {\n const entries: Iterable<readonly [string | number, string]> =\n source instanceof Map ? source.entries() : Object.entries(source);\n return Array.from(entries, ([value, label]) => ({ value: String(value), label }));\n}\n\n/**\n * Prepend the \"no filter\" entry to an option list.\n *\n * A filter needs this as a real option, not as the select's placeholder: a\n * placeholder reads as \"nothing chosen yet\", so the screen cannot tell whether\n * the column is filtered. The entry uses {@link ALL_OPTION_VALUE}.\n *\n * @param options - The list to extend. It is not mutated.\n * @param label - The entry's label. Defaults to `\"Todos\"`.\n * @returns A new array with the entry first.\n */\nexport function withAllOption<T extends OptionLike>(\n options: readonly T[],\n label: string = DEFAULT_ALL_OPTION_LABEL,\n): (T | OptionEntry)[] {\n return [{ value: ALL_OPTION_VALUE, label }, ...options];\n}\n\n/**\n * Prepend the empty entry a nullable field needs.\n *\n * Without it a `<select>` has no way to show \"unset\": a controlled\n * `value=\"\"` with no matching option reads back as the **first option** (measured\n * in jsdom: `select.value === \"a\"`), so opening an edit form and saving it\n * writes a value nobody chose. `Select`'s `placeholder` does not cover this — its\n * entry is `disabled hidden`, so once something is picked the field can never be\n * cleared again. The entry's value is `\"\"`, which is what an unanswered nullable field\n * submits.\n *\n * @param options - The list to extend. It is not mutated.\n * @param label - The entry's label. Defaults to `\"— Não definido —\"`.\n * @returns A new array with the entry first.\n */\nexport function withEmptyOption<T extends OptionLike>(\n options: readonly T[],\n label: string = DEFAULT_EMPTY_OPTION_LABEL,\n): (T | OptionEntry)[] {\n return [{ value: \"\", label }, ...options];\n}\n"],"mappings":"AAqBA,IAAa,EAAmB,MAoChC,SAAgB,EACZ,EACa,CACb,IAAM,EACF,aAAkB,IAAM,EAAO,QAAQ,EAAI,OAAO,QAAQ,CAAM,EACpE,OAAO,MAAM,KAAK,GAAU,CAAC,EAAO,MAAY,CAAE,MAAO,OAAO,CAAK,EAAG,OAAM,EAAE,CACpF,CAaA,SAAgB,EACZ,EACA,EAAgB,QACG,CACnB,MAAO,CAAC,CAAE,MAAA,MAAyB,OAAM,EAAG,GAAG,CAAO,CAC1D,CAiBA,SAAgB,EACZ,EACA,EAAgB,mBACG,CACnB,MAAO,CAAC,CAAE,MAAO,GAAI,OAAM,EAAG,GAAG,CAAO,CAC5C"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region src/components/Select/options.ts
|
|
2
|
+
var e = "all";
|
|
3
|
+
function t(e) {
|
|
4
|
+
let t = e instanceof Map ? e.entries() : Object.entries(e);
|
|
5
|
+
return Array.from(t, ([e, t]) => ({
|
|
6
|
+
value: String(e),
|
|
7
|
+
label: t
|
|
8
|
+
}));
|
|
9
|
+
}
|
|
10
|
+
function n(e, t = "Todos") {
|
|
11
|
+
return [{
|
|
12
|
+
value: "all",
|
|
13
|
+
label: t
|
|
14
|
+
}, ...e];
|
|
15
|
+
}
|
|
16
|
+
function r(e, t = "— Não definido —") {
|
|
17
|
+
return [{
|
|
18
|
+
value: "",
|
|
19
|
+
label: t
|
|
20
|
+
}, ...e];
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { e as ALL_OPTION_VALUE, t as toOptions, n as withAllOption, r as withEmptyOption };
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.js","names":[],"sources":["../../../src/components/Select/options.ts"],"sourcesContent":["/**\n * The shape every option list in the SDK shares: `Select`, `MultiSelect` and\n * `Combobox` all accept an array of it.\n *\n * `value` is always a `string` here, because that is what a `<select>` hands\n * back in `event.target.value` whatever the option was built from: an option\n * built with the number `0` comes back as the string `\"0\"`, so a list that kept\n * numbers could never be compared with `===` against what the control emits.\n */\nexport interface OptionEntry {\n value: string;\n label: string;\n}\n\n/**\n * The value {@link withAllOption} gives its entry, exported so the filter code\n * compares against a name instead of a magic string.\n *\n * @example\n * const where = status === ALL_OPTION_VALUE ? {} : { status };\n */\nexport const ALL_OPTION_VALUE = \"all\";\n\nconst DEFAULT_ALL_OPTION_LABEL = \"Todos\";\n\nconst DEFAULT_EMPTY_OPTION_LABEL = \"— Não definido —\";\n\n/** The minimum an option needs for the helpers to prepend to its list. */\ntype OptionLike = { value: string | number; label: string };\n\n/**\n * Turn a `value → label` map into the option list `Select`, `MultiSelect` and\n * `Combobox` take.\n *\n * Every key becomes a string `value` (see {@link OptionEntry} for why), so the\n * result is assignable to `SelectOption[]`, `MultiSelectOption[]` and\n * `ComboboxOption[]` alike.\n *\n * **Order.** A `Map` keeps the order it was written in, always. A plain object\n * does too — **except for integer-like keys**, which JavaScript enumerates first\n * and in ascending order whatever order they were written in\n * (`OrdinaryOwnPropertyKeys`, ECMA-262 §10.1.11.1). Measured in Node 24:\n * `Object.keys({ 5: \"Ótimo\", 3: \"Ok\", 1: \"Ruim\" })` is `[\"1\", \"3\", \"5\"]`, and\n * `{ baixa, 10, 2, alta }` enumerates as `[\"2\", \"10\", \"baixa\", \"alta\"]`. When\n * the business order of a numeric scale is not ascending, pass a `Map`.\n *\n * @param source - A record or a `Map` from value to label.\n * @returns A new array, one entry per key, in the order described above.\n *\n * @example\n * const STATUS = { active: \"Ativo\", paused: \"Pausado\" };\n * <Select aria-label=\"Status\" variant=\"chip\" options={toOptions(STATUS)} />\n *\n * @example\n * const HUMOR = new Map([[5, \"Ótimo\"], [3, \"Ok\"], [1, \"Ruim\"]]);\n * toOptions(HUMOR); // 5, 3, 1 — the order written\n */\nexport function toOptions(\n source: Readonly<Record<string, string>> | ReadonlyMap<string | number, string>,\n): OptionEntry[] {\n const entries: Iterable<readonly [string | number, string]> =\n source instanceof Map ? source.entries() : Object.entries(source);\n return Array.from(entries, ([value, label]) => ({ value: String(value), label }));\n}\n\n/**\n * Prepend the \"no filter\" entry to an option list.\n *\n * A filter needs this as a real option, not as the select's placeholder: a\n * placeholder reads as \"nothing chosen yet\", so the screen cannot tell whether\n * the column is filtered. The entry uses {@link ALL_OPTION_VALUE}.\n *\n * @param options - The list to extend. It is not mutated.\n * @param label - The entry's label. Defaults to `\"Todos\"`.\n * @returns A new array with the entry first.\n */\nexport function withAllOption<T extends OptionLike>(\n options: readonly T[],\n label: string = DEFAULT_ALL_OPTION_LABEL,\n): (T | OptionEntry)[] {\n return [{ value: ALL_OPTION_VALUE, label }, ...options];\n}\n\n/**\n * Prepend the empty entry a nullable field needs.\n *\n * Without it a `<select>` has no way to show \"unset\": a controlled\n * `value=\"\"` with no matching option reads back as the **first option** (measured\n * in jsdom: `select.value === \"a\"`), so opening an edit form and saving it\n * writes a value nobody chose. `Select`'s `placeholder` does not cover this — its\n * entry is `disabled hidden`, so once something is picked the field can never be\n * cleared again. The entry's value is `\"\"`, which is what an unanswered nullable field\n * submits.\n *\n * @param options - The list to extend. It is not mutated.\n * @param label - The entry's label. Defaults to `\"— Não definido —\"`.\n * @returns A new array with the entry first.\n */\nexport function withEmptyOption<T extends OptionLike>(\n options: readonly T[],\n label: string = DEFAULT_EMPTY_OPTION_LABEL,\n): (T | OptionEntry)[] {\n return [{ value: \"\", label }, ...options];\n}\n"],"mappings":";AAqBA,IAAa,IAAmB;AAoChC,SAAgB,EACZ,GACa;CACb,IAAM,IACF,aAAkB,MAAM,EAAO,QAAQ,IAAI,OAAO,QAAQ,CAAM;CACpE,OAAO,MAAM,KAAK,IAAU,CAAC,GAAO,QAAY;EAAE,OAAO,OAAO,CAAK;EAAG;CAAM,EAAE;AACpF;AAaA,SAAgB,EACZ,GACA,IAAgB,SACG;CACnB,OAAO,CAAC;EAAE,OAAA;EAAyB;CAAM,GAAG,GAAG,CAAO;AAC1D;AAiBA,SAAgB,EACZ,GACA,IAAgB,oBACG;CACnB,OAAO,CAAC;EAAE,OAAO;EAAI;CAAM,GAAG,GAAG,CAAO;AAC5C"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../utils/cn.cjs"),t=require("./Sidebar.module.cjs"),
|
|
1
|
+
const e=require("../../utils/cn.cjs"),t=require("./active-nav-key.cjs"),n=require("./Sidebar.module.cjs"),r=require("./SidebarEntry.cjs");let i=require("react"),a=require("react/jsx-runtime");function o(e){let t=[],n;for(let r of e){if(r.type===`separator`){t.push({kind:`separator`,key:r.key}),n=void 0;continue}if(r.type===`section`){n={kind:`items`,key:r.key,section:r,items:[]},t.push(n);continue}n||(n={kind:`items`,key:`loose-${r.key}`,items:[]},t.push(n)),n.items.push(r)}return t}function s(e,n,r){if(r===`key`||n===void 0)return n;let i=e.filter(e=>e.type!==`section`&&e.type!==`separator`);return i[t.activeNavIndex(n,i.map(e=>e.href??e.key))]?.key}function c({header:t,items:c,value:l,onChange:u,match:d=`key`,footer:f,collapsed:p=!1,width:m=240,collapsedWidth:h=64,className:g,style:_,...v}){let y=(0,i.useId)(),b=o(c),x=s(c,l,d),S=typeof(p?h:m)==`number`?`${p?h:m}px`:p?h:m;return(0,a.jsxs)(`aside`,{className:e.cn(n.default.sidebar,p&&n.default.collapsed,g),style:{width:S,..._},...v,children:[t&&(0,a.jsx)(`div`,{className:n.default.header,children:t}),(0,a.jsx)(`nav`,{className:n.default.nav,"aria-label":`Navegação lateral`,children:b.map(t=>{if(t.kind===`separator`)return(0,a.jsx)(`hr`,{className:n.default.separator},t.key);if(!t.section)return(0,a.jsx)(`div`,{className:n.default.group,children:t.items.map(e=>(0,a.jsx)(r.SidebarEntry,{item:e,active:e.key===x,collapsed:p,onSelect:u},e.key))},t.key);let i=`${y}-${t.key}`;return(0,a.jsxs)(`div`,{role:`group`,"aria-labelledby":i,className:n.default.group,children:[(0,a.jsx)(`div`,{id:i,role:`presentation`,className:e.cn(n.default.section,p&&n.default.sectionCollapsed),children:t.section.label}),t.items.map(e=>(0,a.jsx)(r.SidebarEntry,{item:e,active:e.key===x,collapsed:p,onSelect:u},e.key))]},t.key)})}),f&&(0,a.jsx)(`div`,{className:n.default.footer,children:f})]})}exports.Sidebar=c;
|
|
2
2
|
//# sourceMappingURL=Sidebar.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sidebar.cjs","names":[],"sources":["../../../src/components/Sidebar/Sidebar.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — header, items, footer are the three regions,\n * value/onChange the selection, and collapsed/width/collapsedWidth the two layout\n * modes it switches between.\n */\nimport { useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Sidebar.module.css\";\nimport { SidebarEntry } from \"./SidebarEntry\";\n\nexport interface SidebarItem {\n key: string;\n label: ReactNode;\n icon?: ReactNode;\n badge?: ReactNode;\n disabled?: boolean;\n /**\n * Renders the entry as a link to this URL instead of a button.\n *\n * `onChange` still fires on click, so an app that tracks the active key keeps\n * working — the difference is that the entry now behaves like navigation:\n * middle-click, ctrl-click and \"copy link address\" do what the user expects,\n * and a screen reader announces a link rather than a button.\n *\n * A `disabled` entry ignores this and stays a `<button disabled>`: there is no\n * disabled state for an anchor, and dropping the `href` to fake one leaves a\n * link that announces itself as actionable and is not.\n */\n href?: string;\n}\n\n/**\n * A section heading. Every item after it belongs to the section, until the next\n * section or separator.\n */\nexport interface SidebarSection {\n type: \"section\";\n key: string;\n label: ReactNode;\n}\n\n/** A plain divider, for splitting the list without naming the parts. */\nexport interface SidebarSeparator {\n type: \"separator\";\n key: string;\n}\n\n/**\n * One entry of the navigation list.\n *\n * An entry with no `type` is an item, which is what keeps a plain\n * `SidebarItem[]` a valid `SidebarEntry[]`: adding sections to this component\n * needed no change at any existing call site.\n */\nexport type SidebarEntry = ({ type?: \"item\" } & SidebarItem) | SidebarSection | SidebarSeparator;\n\nexport interface SidebarProps extends Omit<HTMLAttributes<HTMLElement>, \"onChange\"> {\n /** Top slot — typically the logo + brand. */\n header?: ReactNode;\n /** Navigation entries. An entry with no `type` is an item. */\n items: SidebarEntry[];\n /** Active item key. */\n value?: string;\n /** Fires when an item is clicked. Receives the item's `key`. */\n onChange?: (key: string) => void;\n /** Bottom slot — typically settings/profile/logout. */\n footer?: ReactNode;\n /** Collapsed mode — only icons visible. Default `false`. */\n collapsed?: boolean;\n /** Width when expanded, in pixels or any CSS length. Default `240px`. */\n width?: number | string;\n /** Width when collapsed, in pixels or any CSS length. Default `64px`. */\n collapsedWidth?: number | string;\n}\n\n/** A run of items, optionally under a section heading. */\ninterface ItemBlock {\n kind: \"items\";\n key: string;\n section?: SidebarSection;\n items: SidebarItem[];\n}\n\n/** A standalone divider between blocks. */\ninterface SeparatorBlock {\n kind: \"separator\";\n key: string;\n}\n\ntype Block = ItemBlock | SeparatorBlock;\n\n/**\n * Fold the flat entry list into the blocks that get rendered.\n *\n * A section opens a block that swallows every following item; a separator closes\n * whatever is open, so items after it are loose again. Items before the first\n * section are loose too — which is the whole existing behaviour, and why a list\n * with no sections comes out of here as one unnamed block.\n *\n * @param entries - The `items` prop, as given.\n * @returns Blocks in render order.\n */\nfunction toBlocks(entries: readonly SidebarEntry[]): Block[] {\n const blocks: Block[] = [];\n let open: ItemBlock | undefined;\n\n for (const entry of entries) {\n if (entry.type === \"separator\") {\n blocks.push({ kind: \"separator\", key: entry.key });\n open = undefined;\n continue;\n }\n if (entry.type === \"section\") {\n open = { kind: \"items\", key: entry.key, section: entry, items: [] };\n blocks.push(open);\n continue;\n }\n if (!open) {\n open = { kind: \"items\", key: `loose-${entry.key}`, items: [] };\n blocks.push(open);\n }\n open.items.push(entry);\n }\n return blocks;\n}\n\n/**\n * Desktop sidebar navigation. Pair with `<Show above=\"md\">` and a `Drawer`\n * for mobile.\n *\n * Sixteen screens in one flat list is a wall, so `items` also takes section\n * headings and separators. A section renders as a labelled `role=\"group\"`, which\n * is what tells a screen reader \"Monitoring, group, 3 items\" — the reason it is\n * not a styled `disabled` item, which would announce an unavailable button and\n * stay in the navigation order.\n *\n * @example\n * const [tab, setTab] = useState(\"home\");\n * <Show above=\"md\">\n * <Sidebar\n * header={<Brand />}\n * items={[{ key: \"home\", label: \"Home\", icon: <Home /> }]}\n * value={tab}\n * onChange={setTab}\n * />\n * </Show>\n *\n * @example\n * <Sidebar\n * items={[\n * { type: \"section\", key: \"monitoring\", label: \"Monitoring\" },\n * { key: \"overview\", label: \"Overview\", href: \"/overview\" },\n * { key: \"activity\", label: \"Activity\", href: \"/activity\" },\n * { type: \"section\", key: \"admin\", label: \"Administration\" },\n * { key: \"settings\", label: \"Settings\", href: \"/settings\" },\n * ]}\n * value={tab}\n * onChange={setTab}\n * />\n */\nexport function Sidebar({\n header,\n items,\n value,\n onChange,\n footer,\n collapsed = false,\n width = 240,\n collapsedWidth = 64,\n className,\n style,\n ...props\n}: SidebarProps) {\n const baseId = useId();\n const blocks = toBlocks(items);\n const finalWidth =\n typeof (collapsed ? collapsedWidth : width) === \"number\"\n ? `${collapsed ? collapsedWidth : width}px`\n : collapsed\n ? collapsedWidth\n : width;\n\n return (\n <aside\n className={cn(styles.sidebar, collapsed && styles.collapsed, className)}\n style={{ width: finalWidth, ...style }}\n {...props}\n >\n {header && <div className={styles.header}>{header}</div>}\n <nav className={styles.nav} aria-label=\"Navegação lateral\">\n {blocks.map((block) => {\n if (block.kind === \"separator\") {\n return <hr key={block.key} className={styles.separator} />;\n }\n if (!block.section) {\n return (\n <div key={block.key} className={styles.group}>\n {block.items.map((item) => (\n <SidebarEntry\n key={item.key}\n item={item}\n active={item.key === value}\n collapsed={collapsed}\n onSelect={onChange}\n />\n ))}\n </div>\n );\n }\n const labelId = `${baseId}-${block.key}`;\n return (\n <div\n key={block.key}\n role=\"group\"\n aria-labelledby={labelId}\n className={styles.group}\n >\n <div\n id={labelId}\n role=\"presentation\"\n className={cn(styles.section, collapsed && styles.sectionCollapsed)}\n >\n {block.section.label}\n </div>\n {block.items.map((item) => (\n <SidebarEntry\n key={item.key}\n item={item}\n active={item.key === value}\n collapsed={collapsed}\n onSelect={onChange}\n />\n ))}\n </div>\n );\n })}\n </nav>\n {footer && <div className={styles.footer}>{footer}</div>}\n </aside>\n );\n}\n"],"mappings":"8JAsGA,SAAS,EAAS,EAA2C,CACzD,IAAM,EAAkB,CAAC,EACrB,EAEJ,IAAK,IAAM,KAAS,EAAS,CACzB,GAAI,EAAM,OAAS,YAAa,CAC5B,EAAO,KAAK,CAAE,KAAM,YAAa,IAAK,EAAM,GAAI,CAAC,EACjD,EAAO,IAAA,GACP,QACJ,CACA,GAAI,EAAM,OAAS,UAAW,CAC1B,EAAO,CAAE,KAAM,QAAS,IAAK,EAAM,IAAK,QAAS,EAAO,MAAO,CAAC,CAAE,EAClE,EAAO,KAAK,CAAI,EAChB,QACJ,CACK,IACD,EAAO,CAAE,KAAM,QAAS,IAAK,SAAS,EAAM,MAAO,MAAO,CAAC,CAAE,EAC7D,EAAO,KAAK,CAAI,GAEpB,EAAK,MAAM,KAAK,CAAK,CACzB,CACA,OAAO,CACX,CAoCA,SAAgB,EAAQ,CACpB,SACA,QACA,QACA,WACA,SACA,YAAY,GACZ,QAAQ,IACR,iBAAiB,GACjB,YACA,QACA,GAAG,GACU,CACb,IAAM,GAAA,EAAS,EAAA,MAAA,CAAM,EACf,EAAS,EAAS,CAAK,EACvB,EACF,OAAQ,EAAY,EAAiB,IAAW,SAC1C,GAAG,EAAY,EAAiB,EAAM,IACtC,EACE,EACA,EAEZ,OACI,EAAA,EAAA,KAAA,CAAC,QAAD,CACI,UAAW,EAAA,GAAG,EAAA,QAAO,QAAS,GAAa,EAAA,QAAO,UAAW,CAAS,EACtE,MAAO,CAAE,MAAO,EAAY,GAAG,CAAM,EACrC,GAAI,EAHR,SAAA,CAKK,IAAU,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,OAAS,SAAA,CAAY,CAAA,GACvD,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,IAAK,aAAW,oBAClC,SAAA,EAAO,IAAK,GAAU,CACnB,GAAI,EAAM,OAAS,YACf,OAAO,EAAA,EAAA,IAAA,CAAC,KAAD,CAAoB,UAAW,EAAA,QAAO,SAAY,EAAzC,EAAM,GAAmC,EAE7D,GAAI,CAAC,EAAM,QACP,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAqB,UAAW,EAAA,QAAO,MAClC,SAAA,EAAM,MAAM,IAAK,IACd,EAAA,EAAA,IAAA,CAAC,EAAA,aAAD,CAEU,OACN,OAAQ,EAAK,MAAQ,EACV,YACX,SAAU,CACb,EALQ,EAAK,GAKb,CACJ,CACA,EAVK,EAAM,GAUX,EAGb,IAAM,EAAU,GAAG,EAAO,GAAG,EAAM,MACnC,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CAEI,KAAK,QACL,kBAAiB,EACjB,UAAW,EAAA,QAAO,MAJtB,SAAA,EAMI,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,GAAI,EACJ,KAAK,eACL,UAAW,EAAA,GAAG,EAAA,QAAO,QAAS,GAAa,EAAA,QAAO,gBAAgB,EAEjE,SAAA,EAAM,QAAQ,KACd,CAAA,EACJ,EAAM,MAAM,IAAK,IACd,EAAA,EAAA,IAAA,CAAC,EAAA,aAAD,CAEU,OACN,OAAQ,EAAK,MAAQ,EACV,YACX,SAAU,CACb,EALQ,EAAK,GAKb,CACJ,CACA,CArBI,EAAA,EAAM,GAqBV,CAEb,CAAC,CACA,CAAA,EACJ,IAAU,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,OAAS,SAAA,CAAY,CAAA,CACpD,GAEf"}
|
|
1
|
+
{"version":3,"file":"Sidebar.cjs","names":[],"sources":["../../../src/components/Sidebar/Sidebar.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — header, items, footer are the three regions,\n * value/onChange/match the selection, and collapsed/width/collapsedWidth the two\n * layout modes it switches between.\n */\nimport { useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { activeNavIndex } from \"./active-nav-key\";\nimport styles from \"./Sidebar.module.css\";\nimport { SidebarEntry } from \"./SidebarEntry\";\n\nexport interface SidebarItem {\n key: string;\n label: ReactNode;\n icon?: ReactNode;\n badge?: ReactNode;\n disabled?: boolean;\n /**\n * Renders the entry as a link to this URL instead of a button.\n *\n * `onChange` still fires on click, so an app that tracks the active key keeps\n * working — the difference is that the entry now behaves like navigation:\n * middle-click, ctrl-click and \"copy link address\" do what the user expects,\n * and a screen reader announces a link rather than a button.\n *\n * A `disabled` entry ignores this and stays a `<button disabled>`: there is no\n * disabled state for an anchor, and dropping the `href` to fake one leaves a\n * link that announces itself as actionable and is not.\n */\n href?: string;\n}\n\n/**\n * A section heading. Every item after it belongs to the section, until the next\n * section or separator.\n */\nexport interface SidebarSection {\n type: \"section\";\n key: string;\n label: ReactNode;\n}\n\n/** A plain divider, for splitting the list without naming the parts. */\nexport interface SidebarSeparator {\n type: \"separator\";\n key: string;\n}\n\n/**\n * One entry of the navigation list.\n *\n * An entry with no `type` is an item, which is what keeps a plain\n * `SidebarItem[]` a valid `SidebarEntry[]`: adding sections to this component\n * needed no change at any existing call site.\n */\nexport type SidebarEntry = ({ type?: \"item\" } & SidebarItem) | SidebarSection | SidebarSeparator;\n\nexport interface SidebarProps extends Omit<HTMLAttributes<HTMLElement>, \"onChange\"> {\n /** Top slot — typically the logo + brand. */\n header?: ReactNode;\n /** Navigation entries. An entry with no `type` is an item. */\n items: SidebarEntry[];\n /**\n * The active item: its `key` by default, or the current route with\n * `match=\"route\"`.\n */\n value?: string;\n /**\n * How `value` picks the active item.\n *\n * - `\"key\"` (default): the item whose `key` equals `value`.\n * - `\"route\"`: `value` is the current pathname, and the active item is the one\n * whose `href` (or `key`, when it has no `href`) is the longest path covering\n * it on a segment boundary — see `activeNavKey`. `/dashboard/tips/42` lights\n * `/dashboard/tips`, not `/dashboard`, and `/users-admin` never lights\n * `/users`. Sections and separators take no part.\n *\n * `\"route\"` is what lets the app pass `useLocation().pathname` and stop\n * computing a key: the component already holds the entries the resolution runs\n * against, so asking the app to repeat them is how the two drift apart. It\n * reads no router itself — the value comes in as a string — so the component\n * keeps working with no router mounted.\n */\n match?: \"key\" | \"route\";\n /** Fires when an item is clicked. Receives the item's `key`. */\n onChange?: (key: string) => void;\n /** Bottom slot — typically settings/profile/logout. */\n footer?: ReactNode;\n /** Collapsed mode — only icons visible. Default `false`. */\n collapsed?: boolean;\n /** Width when expanded, in pixels or any CSS length. Default `240px`. */\n width?: number | string;\n /** Width when collapsed, in pixels or any CSS length. Default `64px`. */\n collapsedWidth?: number | string;\n}\n\n/** A run of items, optionally under a section heading. */\ninterface ItemBlock {\n kind: \"items\";\n key: string;\n section?: SidebarSection;\n items: SidebarItem[];\n}\n\n/** A standalone divider between blocks. */\ninterface SeparatorBlock {\n kind: \"separator\";\n key: string;\n}\n\ntype Block = ItemBlock | SeparatorBlock;\n\n/**\n * Fold the flat entry list into the blocks that get rendered.\n *\n * A section opens a block that swallows every following item; a separator closes\n * whatever is open, so items after it are loose again. Items before the first\n * section are loose too — which is the whole existing behaviour, and why a list\n * with no sections comes out of here as one unnamed block.\n *\n * @param entries - The `items` prop, as given.\n * @returns Blocks in render order.\n */\nfunction toBlocks(entries: readonly SidebarEntry[]): Block[] {\n const blocks: Block[] = [];\n let open: ItemBlock | undefined;\n\n for (const entry of entries) {\n if (entry.type === \"separator\") {\n blocks.push({ kind: \"separator\", key: entry.key });\n open = undefined;\n continue;\n }\n if (entry.type === \"section\") {\n open = { kind: \"items\", key: entry.key, section: entry, items: [] };\n blocks.push(open);\n continue;\n }\n if (!open) {\n open = { kind: \"items\", key: `loose-${entry.key}`, items: [] };\n blocks.push(open);\n }\n open.items.push(entry);\n }\n return blocks;\n}\n\n/**\n * The key of the item `value` selects, under the given `match` mode.\n *\n * @param entries - The `items` prop, as given.\n * @param value - The `value` prop.\n * @param match - The `match` prop.\n * @returns The active item's key, or `undefined` when no item is active.\n */\nfunction resolveActiveKey(\n entries: readonly SidebarEntry[],\n value: string | undefined,\n match: \"key\" | \"route\",\n): string | undefined {\n if (match === \"key\" || value === undefined) {\n return value;\n }\n const items = entries.filter(\n (entry): entry is { type?: \"item\" } & SidebarItem =>\n entry.type !== \"section\" && entry.type !== \"separator\",\n );\n return items[\n activeNavIndex(\n value,\n items.map((item) => item.href ?? item.key),\n )\n ]?.key;\n}\n\n/**\n * Desktop sidebar navigation. Pair with `<Show above=\"md\">` and a `Drawer`\n * for mobile.\n *\n * Sixteen screens in one flat list is a wall, so `items` also takes section\n * headings and separators. A section renders as a labelled `role=\"group\"`, which\n * is what tells a screen reader \"Monitoring, group, 3 items\" — the reason it is\n * not a styled `disabled` item, which would announce an unavailable button and\n * stay in the navigation order.\n *\n * @example\n * const [tab, setTab] = useState(\"home\");\n * <Show above=\"md\">\n * <Sidebar\n * header={<Brand />}\n * items={[{ key: \"home\", label: \"Home\", icon: <Home /> }]}\n * value={tab}\n * onChange={setTab}\n * />\n * </Show>\n *\n * @example\n * const { pathname } = useLocation();\n * <Sidebar\n * match=\"route\"\n * value={pathname}\n * items={[\n * { key: \"overview\", label: \"Overview\", href: \"/dashboard\" },\n * { key: \"tips\", label: \"Tips\", href: \"/dashboard/tips\" },\n * ]}\n * />\n *\n * @example\n * <Sidebar\n * items={[\n * { type: \"section\", key: \"monitoring\", label: \"Monitoring\" },\n * { key: \"overview\", label: \"Overview\", href: \"/overview\" },\n * { key: \"activity\", label: \"Activity\", href: \"/activity\" },\n * { type: \"section\", key: \"admin\", label: \"Administration\" },\n * { key: \"settings\", label: \"Settings\", href: \"/settings\" },\n * ]}\n * value={tab}\n * onChange={setTab}\n * />\n */\nexport function Sidebar({\n header,\n items,\n value,\n onChange,\n match = \"key\",\n footer,\n collapsed = false,\n width = 240,\n collapsedWidth = 64,\n className,\n style,\n ...props\n}: SidebarProps) {\n const baseId = useId();\n const blocks = toBlocks(items);\n const activeKey = resolveActiveKey(items, value, match);\n const finalWidth =\n typeof (collapsed ? collapsedWidth : width) === \"number\"\n ? `${collapsed ? collapsedWidth : width}px`\n : collapsed\n ? collapsedWidth\n : width;\n\n return (\n <aside\n className={cn(styles.sidebar, collapsed && styles.collapsed, className)}\n style={{ width: finalWidth, ...style }}\n {...props}\n >\n {header && <div className={styles.header}>{header}</div>}\n <nav className={styles.nav} aria-label=\"Navegação lateral\">\n {blocks.map((block) => {\n if (block.kind === \"separator\") {\n return <hr key={block.key} className={styles.separator} />;\n }\n if (!block.section) {\n return (\n <div key={block.key} className={styles.group}>\n {block.items.map((item) => (\n <SidebarEntry\n key={item.key}\n item={item}\n active={item.key === activeKey}\n collapsed={collapsed}\n onSelect={onChange}\n />\n ))}\n </div>\n );\n }\n const labelId = `${baseId}-${block.key}`;\n return (\n <div\n key={block.key}\n role=\"group\"\n aria-labelledby={labelId}\n className={styles.group}\n >\n <div\n id={labelId}\n role=\"presentation\"\n className={cn(styles.section, collapsed && styles.sectionCollapsed)}\n >\n {block.section.label}\n </div>\n {block.items.map((item) => (\n <SidebarEntry\n key={item.key}\n item={item}\n active={item.key === activeKey}\n collapsed={collapsed}\n onSelect={onChange}\n />\n ))}\n </div>\n );\n })}\n </nav>\n {footer && <div className={styles.footer}>{footer}</div>}\n </aside>\n );\n}\n"],"mappings":"gMA2HA,SAAS,EAAS,EAA2C,CACzD,IAAM,EAAkB,CAAC,EACrB,EAEJ,IAAK,IAAM,KAAS,EAAS,CACzB,GAAI,EAAM,OAAS,YAAa,CAC5B,EAAO,KAAK,CAAE,KAAM,YAAa,IAAK,EAAM,GAAI,CAAC,EACjD,EAAO,IAAA,GACP,QACJ,CACA,GAAI,EAAM,OAAS,UAAW,CAC1B,EAAO,CAAE,KAAM,QAAS,IAAK,EAAM,IAAK,QAAS,EAAO,MAAO,CAAC,CAAE,EAClE,EAAO,KAAK,CAAI,EAChB,QACJ,CACK,IACD,EAAO,CAAE,KAAM,QAAS,IAAK,SAAS,EAAM,MAAO,MAAO,CAAC,CAAE,EAC7D,EAAO,KAAK,CAAI,GAEpB,EAAK,MAAM,KAAK,CAAK,CACzB,CACA,OAAO,CACX,CAUA,SAAS,EACL,EACA,EACA,EACkB,CAClB,GAAI,IAAU,OAAS,IAAU,IAAA,GAC7B,OAAO,EAEX,IAAM,EAAQ,EAAQ,OACjB,GACG,EAAM,OAAS,WAAa,EAAM,OAAS,WACnD,EACA,OAAO,EACH,EAAA,eACI,EACA,EAAM,IAAK,GAAS,EAAK,MAAQ,EAAK,GAAG,CAC7C,EACH,EAAE,GACP,CA+CA,SAAgB,EAAQ,CACpB,SACA,QACA,QACA,WACA,QAAQ,MACR,SACA,YAAY,GACZ,QAAQ,IACR,iBAAiB,GACjB,YACA,QACA,GAAG,GACU,CACb,IAAM,GAAA,EAAS,EAAA,MAAA,CAAM,EACf,EAAS,EAAS,CAAK,EACvB,EAAY,EAAiB,EAAO,EAAO,CAAK,EAChD,EACF,OAAQ,EAAY,EAAiB,IAAW,SAC1C,GAAG,EAAY,EAAiB,EAAM,IACtC,EACE,EACA,EAEZ,OACI,EAAA,EAAA,KAAA,CAAC,QAAD,CACI,UAAW,EAAA,GAAG,EAAA,QAAO,QAAS,GAAa,EAAA,QAAO,UAAW,CAAS,EACtE,MAAO,CAAE,MAAO,EAAY,GAAG,CAAM,EACrC,GAAI,EAHR,SAAA,CAKK,IAAU,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,OAAS,SAAA,CAAY,CAAA,GACvD,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,IAAK,aAAW,oBAClC,SAAA,EAAO,IAAK,GAAU,CACnB,GAAI,EAAM,OAAS,YACf,OAAO,EAAA,EAAA,IAAA,CAAC,KAAD,CAAoB,UAAW,EAAA,QAAO,SAAY,EAAzC,EAAM,GAAmC,EAE7D,GAAI,CAAC,EAAM,QACP,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAqB,UAAW,EAAA,QAAO,MAClC,SAAA,EAAM,MAAM,IAAK,IACd,EAAA,EAAA,IAAA,CAAC,EAAA,aAAD,CAEU,OACN,OAAQ,EAAK,MAAQ,EACV,YACX,SAAU,CACb,EALQ,EAAK,GAKb,CACJ,CACA,EAVK,EAAM,GAUX,EAGb,IAAM,EAAU,GAAG,EAAO,GAAG,EAAM,MACnC,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CAEI,KAAK,QACL,kBAAiB,EACjB,UAAW,EAAA,QAAO,MAJtB,SAAA,EAMI,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,GAAI,EACJ,KAAK,eACL,UAAW,EAAA,GAAG,EAAA,QAAO,QAAS,GAAa,EAAA,QAAO,gBAAgB,EAEjE,SAAA,EAAM,QAAQ,KACd,CAAA,EACJ,EAAM,MAAM,IAAK,IACd,EAAA,EAAA,IAAA,CAAC,EAAA,aAAD,CAEU,OACN,OAAQ,EAAK,MAAQ,EACV,YACX,SAAU,CACb,EALQ,EAAK,GAKb,CACJ,CACA,CArBI,EAAA,EAAM,GAqBV,CAEb,CAAC,CACA,CAAA,EACJ,IAAU,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,OAAS,SAAA,CAAY,CAAA,CACpD,GAEf"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { cn as e } from "../../utils/cn.js";
|
|
2
|
-
import t from "./
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { activeNavIndex as t } from "./active-nav-key.js";
|
|
3
|
+
import n from "./Sidebar.module.js";
|
|
4
|
+
import { SidebarEntry as r } from "./SidebarEntry.js";
|
|
5
|
+
import { useId as i } from "react";
|
|
6
|
+
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
6
7
|
//#region src/components/Sidebar/Sidebar.tsx
|
|
7
|
-
function
|
|
8
|
+
function s(e) {
|
|
8
9
|
let t = [], n;
|
|
9
10
|
for (let r of e) {
|
|
10
11
|
if (r.type === "separator") {
|
|
@@ -31,61 +32,66 @@ function o(e) {
|
|
|
31
32
|
}
|
|
32
33
|
return t;
|
|
33
34
|
}
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
function c(e, n, r) {
|
|
36
|
+
if (r === "key" || n === void 0) return n;
|
|
37
|
+
let i = e.filter((e) => e.type !== "section" && e.type !== "separator");
|
|
38
|
+
return i[t(n, i.map((e) => e.href ?? e.key))]?.key;
|
|
39
|
+
}
|
|
40
|
+
function l({ header: t, items: l, value: u, onChange: d, match: f = "key", footer: p, collapsed: m = !1, width: h = 240, collapsedWidth: g = 64, className: _, style: v, ...y }) {
|
|
41
|
+
let b = i(), x = s(l), S = c(l, u, f), C = typeof (m ? g : h) == "number" ? `${m ? g : h}px` : m ? g : h;
|
|
42
|
+
return /* @__PURE__ */ o("aside", {
|
|
43
|
+
className: e(n.sidebar, m && n.collapsed, _),
|
|
38
44
|
style: {
|
|
39
|
-
width:
|
|
40
|
-
...
|
|
45
|
+
width: C,
|
|
46
|
+
...v
|
|
41
47
|
},
|
|
42
|
-
...
|
|
48
|
+
...y,
|
|
43
49
|
children: [
|
|
44
|
-
|
|
45
|
-
className:
|
|
46
|
-
children:
|
|
50
|
+
t && /* @__PURE__ */ a("div", {
|
|
51
|
+
className: n.header,
|
|
52
|
+
children: t
|
|
47
53
|
}),
|
|
48
|
-
/* @__PURE__ */
|
|
49
|
-
className:
|
|
54
|
+
/* @__PURE__ */ a("nav", {
|
|
55
|
+
className: n.nav,
|
|
50
56
|
"aria-label": "Navegação lateral",
|
|
51
|
-
children:
|
|
52
|
-
if (
|
|
53
|
-
if (!
|
|
54
|
-
className:
|
|
55
|
-
children:
|
|
57
|
+
children: x.map((t) => {
|
|
58
|
+
if (t.kind === "separator") return /* @__PURE__ */ a("hr", { className: n.separator }, t.key);
|
|
59
|
+
if (!t.section) return /* @__PURE__ */ a("div", {
|
|
60
|
+
className: n.group,
|
|
61
|
+
children: t.items.map((e) => /* @__PURE__ */ a(r, {
|
|
56
62
|
item: e,
|
|
57
|
-
active: e.key ===
|
|
58
|
-
collapsed:
|
|
59
|
-
onSelect:
|
|
63
|
+
active: e.key === S,
|
|
64
|
+
collapsed: m,
|
|
65
|
+
onSelect: d
|
|
60
66
|
}, e.key))
|
|
61
|
-
},
|
|
62
|
-
let
|
|
63
|
-
return /* @__PURE__ */
|
|
67
|
+
}, t.key);
|
|
68
|
+
let i = `${b}-${t.key}`;
|
|
69
|
+
return /* @__PURE__ */ o("div", {
|
|
64
70
|
role: "group",
|
|
65
|
-
"aria-labelledby":
|
|
66
|
-
className:
|
|
67
|
-
children: [/* @__PURE__ */
|
|
68
|
-
id:
|
|
71
|
+
"aria-labelledby": i,
|
|
72
|
+
className: n.group,
|
|
73
|
+
children: [/* @__PURE__ */ a("div", {
|
|
74
|
+
id: i,
|
|
69
75
|
role: "presentation",
|
|
70
|
-
className: e(
|
|
71
|
-
children:
|
|
72
|
-
}),
|
|
76
|
+
className: e(n.section, m && n.sectionCollapsed),
|
|
77
|
+
children: t.section.label
|
|
78
|
+
}), t.items.map((e) => /* @__PURE__ */ a(r, {
|
|
73
79
|
item: e,
|
|
74
|
-
active: e.key ===
|
|
75
|
-
collapsed:
|
|
76
|
-
onSelect:
|
|
80
|
+
active: e.key === S,
|
|
81
|
+
collapsed: m,
|
|
82
|
+
onSelect: d
|
|
77
83
|
}, e.key))]
|
|
78
|
-
},
|
|
84
|
+
}, t.key);
|
|
79
85
|
})
|
|
80
86
|
}),
|
|
81
|
-
|
|
82
|
-
className:
|
|
83
|
-
children:
|
|
87
|
+
p && /* @__PURE__ */ a("div", {
|
|
88
|
+
className: n.footer,
|
|
89
|
+
children: p
|
|
84
90
|
})
|
|
85
91
|
]
|
|
86
92
|
});
|
|
87
93
|
}
|
|
88
94
|
//#endregion
|
|
89
|
-
export {
|
|
95
|
+
export { l as Sidebar };
|
|
90
96
|
|
|
91
97
|
//# sourceMappingURL=Sidebar.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sidebar.js","names":[],"sources":["../../../src/components/Sidebar/Sidebar.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — header, items, footer are the three regions,\n * value/onChange the selection, and collapsed/width/collapsedWidth the two layout\n * modes it switches between.\n */\nimport { useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Sidebar.module.css\";\nimport { SidebarEntry } from \"./SidebarEntry\";\n\nexport interface SidebarItem {\n key: string;\n label: ReactNode;\n icon?: ReactNode;\n badge?: ReactNode;\n disabled?: boolean;\n /**\n * Renders the entry as a link to this URL instead of a button.\n *\n * `onChange` still fires on click, so an app that tracks the active key keeps\n * working — the difference is that the entry now behaves like navigation:\n * middle-click, ctrl-click and \"copy link address\" do what the user expects,\n * and a screen reader announces a link rather than a button.\n *\n * A `disabled` entry ignores this and stays a `<button disabled>`: there is no\n * disabled state for an anchor, and dropping the `href` to fake one leaves a\n * link that announces itself as actionable and is not.\n */\n href?: string;\n}\n\n/**\n * A section heading. Every item after it belongs to the section, until the next\n * section or separator.\n */\nexport interface SidebarSection {\n type: \"section\";\n key: string;\n label: ReactNode;\n}\n\n/** A plain divider, for splitting the list without naming the parts. */\nexport interface SidebarSeparator {\n type: \"separator\";\n key: string;\n}\n\n/**\n * One entry of the navigation list.\n *\n * An entry with no `type` is an item, which is what keeps a plain\n * `SidebarItem[]` a valid `SidebarEntry[]`: adding sections to this component\n * needed no change at any existing call site.\n */\nexport type SidebarEntry = ({ type?: \"item\" } & SidebarItem) | SidebarSection | SidebarSeparator;\n\nexport interface SidebarProps extends Omit<HTMLAttributes<HTMLElement>, \"onChange\"> {\n /** Top slot — typically the logo + brand. */\n header?: ReactNode;\n /** Navigation entries. An entry with no `type` is an item. */\n items: SidebarEntry[];\n /** Active item key. */\n value?: string;\n /** Fires when an item is clicked. Receives the item's `key`. */\n onChange?: (key: string) => void;\n /** Bottom slot — typically settings/profile/logout. */\n footer?: ReactNode;\n /** Collapsed mode — only icons visible. Default `false`. */\n collapsed?: boolean;\n /** Width when expanded, in pixels or any CSS length. Default `240px`. */\n width?: number | string;\n /** Width when collapsed, in pixels or any CSS length. Default `64px`. */\n collapsedWidth?: number | string;\n}\n\n/** A run of items, optionally under a section heading. */\ninterface ItemBlock {\n kind: \"items\";\n key: string;\n section?: SidebarSection;\n items: SidebarItem[];\n}\n\n/** A standalone divider between blocks. */\ninterface SeparatorBlock {\n kind: \"separator\";\n key: string;\n}\n\ntype Block = ItemBlock | SeparatorBlock;\n\n/**\n * Fold the flat entry list into the blocks that get rendered.\n *\n * A section opens a block that swallows every following item; a separator closes\n * whatever is open, so items after it are loose again. Items before the first\n * section are loose too — which is the whole existing behaviour, and why a list\n * with no sections comes out of here as one unnamed block.\n *\n * @param entries - The `items` prop, as given.\n * @returns Blocks in render order.\n */\nfunction toBlocks(entries: readonly SidebarEntry[]): Block[] {\n const blocks: Block[] = [];\n let open: ItemBlock | undefined;\n\n for (const entry of entries) {\n if (entry.type === \"separator\") {\n blocks.push({ kind: \"separator\", key: entry.key });\n open = undefined;\n continue;\n }\n if (entry.type === \"section\") {\n open = { kind: \"items\", key: entry.key, section: entry, items: [] };\n blocks.push(open);\n continue;\n }\n if (!open) {\n open = { kind: \"items\", key: `loose-${entry.key}`, items: [] };\n blocks.push(open);\n }\n open.items.push(entry);\n }\n return blocks;\n}\n\n/**\n * Desktop sidebar navigation. Pair with `<Show above=\"md\">` and a `Drawer`\n * for mobile.\n *\n * Sixteen screens in one flat list is a wall, so `items` also takes section\n * headings and separators. A section renders as a labelled `role=\"group\"`, which\n * is what tells a screen reader \"Monitoring, group, 3 items\" — the reason it is\n * not a styled `disabled` item, which would announce an unavailable button and\n * stay in the navigation order.\n *\n * @example\n * const [tab, setTab] = useState(\"home\");\n * <Show above=\"md\">\n * <Sidebar\n * header={<Brand />}\n * items={[{ key: \"home\", label: \"Home\", icon: <Home /> }]}\n * value={tab}\n * onChange={setTab}\n * />\n * </Show>\n *\n * @example\n * <Sidebar\n * items={[\n * { type: \"section\", key: \"monitoring\", label: \"Monitoring\" },\n * { key: \"overview\", label: \"Overview\", href: \"/overview\" },\n * { key: \"activity\", label: \"Activity\", href: \"/activity\" },\n * { type: \"section\", key: \"admin\", label: \"Administration\" },\n * { key: \"settings\", label: \"Settings\", href: \"/settings\" },\n * ]}\n * value={tab}\n * onChange={setTab}\n * />\n */\nexport function Sidebar({\n header,\n items,\n value,\n onChange,\n footer,\n collapsed = false,\n width = 240,\n collapsedWidth = 64,\n className,\n style,\n ...props\n}: SidebarProps) {\n const baseId = useId();\n const blocks = toBlocks(items);\n const finalWidth =\n typeof (collapsed ? collapsedWidth : width) === \"number\"\n ? `${collapsed ? collapsedWidth : width}px`\n : collapsed\n ? collapsedWidth\n : width;\n\n return (\n <aside\n className={cn(styles.sidebar, collapsed && styles.collapsed, className)}\n style={{ width: finalWidth, ...style }}\n {...props}\n >\n {header && <div className={styles.header}>{header}</div>}\n <nav className={styles.nav} aria-label=\"Navegação lateral\">\n {blocks.map((block) => {\n if (block.kind === \"separator\") {\n return <hr key={block.key} className={styles.separator} />;\n }\n if (!block.section) {\n return (\n <div key={block.key} className={styles.group}>\n {block.items.map((item) => (\n <SidebarEntry\n key={item.key}\n item={item}\n active={item.key === value}\n collapsed={collapsed}\n onSelect={onChange}\n />\n ))}\n </div>\n );\n }\n const labelId = `${baseId}-${block.key}`;\n return (\n <div\n key={block.key}\n role=\"group\"\n aria-labelledby={labelId}\n className={styles.group}\n >\n <div\n id={labelId}\n role=\"presentation\"\n className={cn(styles.section, collapsed && styles.sectionCollapsed)}\n >\n {block.section.label}\n </div>\n {block.items.map((item) => (\n <SidebarEntry\n key={item.key}\n item={item}\n active={item.key === value}\n collapsed={collapsed}\n onSelect={onChange}\n />\n ))}\n </div>\n );\n })}\n </nav>\n {footer && <div className={styles.footer}>{footer}</div>}\n </aside>\n );\n}\n"],"mappings":";;;;;;AAsGA,SAAS,EAAS,GAA2C;CACzD,IAAM,IAAkB,CAAC,GACrB;CAEJ,KAAK,IAAM,KAAS,GAAS;EACzB,IAAI,EAAM,SAAS,aAAa;GAE5B,AADA,EAAO,KAAK;IAAE,MAAM;IAAa,KAAK,EAAM;GAAI,CAAC,GACjD,IAAO,KAAA;GACP;EACJ;EACA,IAAI,EAAM,SAAS,WAAW;GAE1B,AADA,IAAO;IAAE,MAAM;IAAS,KAAK,EAAM;IAAK,SAAS;IAAO,OAAO,CAAC;GAAE,GAClE,EAAO,KAAK,CAAI;GAChB;EACJ;EAKA,AAJK,MACD,IAAO;GAAE,MAAM;GAAS,KAAK,SAAS,EAAM;GAAO,OAAO,CAAC;EAAE,GAC7D,EAAO,KAAK,CAAI,IAEpB,EAAK,MAAM,KAAK,CAAK;CACzB;CACA,OAAO;AACX;AAoCA,SAAgB,EAAQ,EACpB,WACA,UACA,UACA,aACA,WACA,eAAY,IACZ,WAAQ,KACR,oBAAiB,IACjB,cACA,UACA,GAAG,KACU;CACb,IAAM,IAAS,EAAM,GACf,IAAS,EAAS,CAAK,GACvB,IACF,QAAQ,IAAY,IAAiB,MAAW,WAC1C,GAAG,IAAY,IAAiB,EAAM,MACtC,IACE,IACA;CAEZ,OACI,kBAAC,SAAD;EACI,WAAW,EAAG,EAAO,SAAS,KAAa,EAAO,WAAW,CAAS;EACtE,OAAO;GAAE,OAAO;GAAY,GAAG;EAAM;EACrC,GAAI;EAHR,UAAA;GAKK,KAAU,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAS,UAAA;GAAY,CAAA;GACvD,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAK,cAAW;IAClC,UAAA,EAAO,KAAK,MAAU;KACnB,IAAI,EAAM,SAAS,aACf,OAAO,kBAAC,MAAD,EAAoB,WAAW,EAAO,UAAY,GAAzC,EAAM,GAAmC;KAE7D,IAAI,CAAC,EAAM,SACP,OACI,kBAAC,OAAD;MAAqB,WAAW,EAAO;MAClC,UAAA,EAAM,MAAM,KAAK,MACd,kBAAC,GAAD;OAEU;OACN,QAAQ,EAAK,QAAQ;OACV;OACX,UAAU;MACb,GALQ,EAAK,GAKb,CACJ;KACA,GAVK,EAAM,GAUX;KAGb,IAAM,IAAU,GAAG,EAAO,GAAG,EAAM;KACnC,OACI,kBAAC,OAAD;MAEI,MAAK;MACL,mBAAiB;MACjB,WAAW,EAAO;MAJtB,UAAA,CAMI,kBAAC,OAAD;OACI,IAAI;OACJ,MAAK;OACL,WAAW,EAAG,EAAO,SAAS,KAAa,EAAO,gBAAgB;OAEjE,UAAA,EAAM,QAAQ;MACd,CAAA,GACJ,EAAM,MAAM,KAAK,MACd,kBAAC,GAAD;OAEU;OACN,QAAQ,EAAK,QAAQ;OACV;OACX,UAAU;MACb,GALQ,EAAK,GAKb,CACJ,CACA;KArBI,GAAA,EAAM,GAqBV;IAEb,CAAC;GACA,CAAA;GACJ,KAAU,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAS,UAAA;GAAY,CAAA;EACpD;;AAEf"}
|
|
1
|
+
{"version":3,"file":"Sidebar.js","names":[],"sources":["../../../src/components/Sidebar/Sidebar.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — header, items, footer are the three regions,\n * value/onChange/match the selection, and collapsed/width/collapsedWidth the two\n * layout modes it switches between.\n */\nimport { useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { activeNavIndex } from \"./active-nav-key\";\nimport styles from \"./Sidebar.module.css\";\nimport { SidebarEntry } from \"./SidebarEntry\";\n\nexport interface SidebarItem {\n key: string;\n label: ReactNode;\n icon?: ReactNode;\n badge?: ReactNode;\n disabled?: boolean;\n /**\n * Renders the entry as a link to this URL instead of a button.\n *\n * `onChange` still fires on click, so an app that tracks the active key keeps\n * working — the difference is that the entry now behaves like navigation:\n * middle-click, ctrl-click and \"copy link address\" do what the user expects,\n * and a screen reader announces a link rather than a button.\n *\n * A `disabled` entry ignores this and stays a `<button disabled>`: there is no\n * disabled state for an anchor, and dropping the `href` to fake one leaves a\n * link that announces itself as actionable and is not.\n */\n href?: string;\n}\n\n/**\n * A section heading. Every item after it belongs to the section, until the next\n * section or separator.\n */\nexport interface SidebarSection {\n type: \"section\";\n key: string;\n label: ReactNode;\n}\n\n/** A plain divider, for splitting the list without naming the parts. */\nexport interface SidebarSeparator {\n type: \"separator\";\n key: string;\n}\n\n/**\n * One entry of the navigation list.\n *\n * An entry with no `type` is an item, which is what keeps a plain\n * `SidebarItem[]` a valid `SidebarEntry[]`: adding sections to this component\n * needed no change at any existing call site.\n */\nexport type SidebarEntry = ({ type?: \"item\" } & SidebarItem) | SidebarSection | SidebarSeparator;\n\nexport interface SidebarProps extends Omit<HTMLAttributes<HTMLElement>, \"onChange\"> {\n /** Top slot — typically the logo + brand. */\n header?: ReactNode;\n /** Navigation entries. An entry with no `type` is an item. */\n items: SidebarEntry[];\n /**\n * The active item: its `key` by default, or the current route with\n * `match=\"route\"`.\n */\n value?: string;\n /**\n * How `value` picks the active item.\n *\n * - `\"key\"` (default): the item whose `key` equals `value`.\n * - `\"route\"`: `value` is the current pathname, and the active item is the one\n * whose `href` (or `key`, when it has no `href`) is the longest path covering\n * it on a segment boundary — see `activeNavKey`. `/dashboard/tips/42` lights\n * `/dashboard/tips`, not `/dashboard`, and `/users-admin` never lights\n * `/users`. Sections and separators take no part.\n *\n * `\"route\"` is what lets the app pass `useLocation().pathname` and stop\n * computing a key: the component already holds the entries the resolution runs\n * against, so asking the app to repeat them is how the two drift apart. It\n * reads no router itself — the value comes in as a string — so the component\n * keeps working with no router mounted.\n */\n match?: \"key\" | \"route\";\n /** Fires when an item is clicked. Receives the item's `key`. */\n onChange?: (key: string) => void;\n /** Bottom slot — typically settings/profile/logout. */\n footer?: ReactNode;\n /** Collapsed mode — only icons visible. Default `false`. */\n collapsed?: boolean;\n /** Width when expanded, in pixels or any CSS length. Default `240px`. */\n width?: number | string;\n /** Width when collapsed, in pixels or any CSS length. Default `64px`. */\n collapsedWidth?: number | string;\n}\n\n/** A run of items, optionally under a section heading. */\ninterface ItemBlock {\n kind: \"items\";\n key: string;\n section?: SidebarSection;\n items: SidebarItem[];\n}\n\n/** A standalone divider between blocks. */\ninterface SeparatorBlock {\n kind: \"separator\";\n key: string;\n}\n\ntype Block = ItemBlock | SeparatorBlock;\n\n/**\n * Fold the flat entry list into the blocks that get rendered.\n *\n * A section opens a block that swallows every following item; a separator closes\n * whatever is open, so items after it are loose again. Items before the first\n * section are loose too — which is the whole existing behaviour, and why a list\n * with no sections comes out of here as one unnamed block.\n *\n * @param entries - The `items` prop, as given.\n * @returns Blocks in render order.\n */\nfunction toBlocks(entries: readonly SidebarEntry[]): Block[] {\n const blocks: Block[] = [];\n let open: ItemBlock | undefined;\n\n for (const entry of entries) {\n if (entry.type === \"separator\") {\n blocks.push({ kind: \"separator\", key: entry.key });\n open = undefined;\n continue;\n }\n if (entry.type === \"section\") {\n open = { kind: \"items\", key: entry.key, section: entry, items: [] };\n blocks.push(open);\n continue;\n }\n if (!open) {\n open = { kind: \"items\", key: `loose-${entry.key}`, items: [] };\n blocks.push(open);\n }\n open.items.push(entry);\n }\n return blocks;\n}\n\n/**\n * The key of the item `value` selects, under the given `match` mode.\n *\n * @param entries - The `items` prop, as given.\n * @param value - The `value` prop.\n * @param match - The `match` prop.\n * @returns The active item's key, or `undefined` when no item is active.\n */\nfunction resolveActiveKey(\n entries: readonly SidebarEntry[],\n value: string | undefined,\n match: \"key\" | \"route\",\n): string | undefined {\n if (match === \"key\" || value === undefined) {\n return value;\n }\n const items = entries.filter(\n (entry): entry is { type?: \"item\" } & SidebarItem =>\n entry.type !== \"section\" && entry.type !== \"separator\",\n );\n return items[\n activeNavIndex(\n value,\n items.map((item) => item.href ?? item.key),\n )\n ]?.key;\n}\n\n/**\n * Desktop sidebar navigation. Pair with `<Show above=\"md\">` and a `Drawer`\n * for mobile.\n *\n * Sixteen screens in one flat list is a wall, so `items` also takes section\n * headings and separators. A section renders as a labelled `role=\"group\"`, which\n * is what tells a screen reader \"Monitoring, group, 3 items\" — the reason it is\n * not a styled `disabled` item, which would announce an unavailable button and\n * stay in the navigation order.\n *\n * @example\n * const [tab, setTab] = useState(\"home\");\n * <Show above=\"md\">\n * <Sidebar\n * header={<Brand />}\n * items={[{ key: \"home\", label: \"Home\", icon: <Home /> }]}\n * value={tab}\n * onChange={setTab}\n * />\n * </Show>\n *\n * @example\n * const { pathname } = useLocation();\n * <Sidebar\n * match=\"route\"\n * value={pathname}\n * items={[\n * { key: \"overview\", label: \"Overview\", href: \"/dashboard\" },\n * { key: \"tips\", label: \"Tips\", href: \"/dashboard/tips\" },\n * ]}\n * />\n *\n * @example\n * <Sidebar\n * items={[\n * { type: \"section\", key: \"monitoring\", label: \"Monitoring\" },\n * { key: \"overview\", label: \"Overview\", href: \"/overview\" },\n * { key: \"activity\", label: \"Activity\", href: \"/activity\" },\n * { type: \"section\", key: \"admin\", label: \"Administration\" },\n * { key: \"settings\", label: \"Settings\", href: \"/settings\" },\n * ]}\n * value={tab}\n * onChange={setTab}\n * />\n */\nexport function Sidebar({\n header,\n items,\n value,\n onChange,\n match = \"key\",\n footer,\n collapsed = false,\n width = 240,\n collapsedWidth = 64,\n className,\n style,\n ...props\n}: SidebarProps) {\n const baseId = useId();\n const blocks = toBlocks(items);\n const activeKey = resolveActiveKey(items, value, match);\n const finalWidth =\n typeof (collapsed ? collapsedWidth : width) === \"number\"\n ? `${collapsed ? collapsedWidth : width}px`\n : collapsed\n ? collapsedWidth\n : width;\n\n return (\n <aside\n className={cn(styles.sidebar, collapsed && styles.collapsed, className)}\n style={{ width: finalWidth, ...style }}\n {...props}\n >\n {header && <div className={styles.header}>{header}</div>}\n <nav className={styles.nav} aria-label=\"Navegação lateral\">\n {blocks.map((block) => {\n if (block.kind === \"separator\") {\n return <hr key={block.key} className={styles.separator} />;\n }\n if (!block.section) {\n return (\n <div key={block.key} className={styles.group}>\n {block.items.map((item) => (\n <SidebarEntry\n key={item.key}\n item={item}\n active={item.key === activeKey}\n collapsed={collapsed}\n onSelect={onChange}\n />\n ))}\n </div>\n );\n }\n const labelId = `${baseId}-${block.key}`;\n return (\n <div\n key={block.key}\n role=\"group\"\n aria-labelledby={labelId}\n className={styles.group}\n >\n <div\n id={labelId}\n role=\"presentation\"\n className={cn(styles.section, collapsed && styles.sectionCollapsed)}\n >\n {block.section.label}\n </div>\n {block.items.map((item) => (\n <SidebarEntry\n key={item.key}\n item={item}\n active={item.key === activeKey}\n collapsed={collapsed}\n onSelect={onChange}\n />\n ))}\n </div>\n );\n })}\n </nav>\n {footer && <div className={styles.footer}>{footer}</div>}\n </aside>\n );\n}\n"],"mappings":";;;;;;;AA2HA,SAAS,EAAS,GAA2C;CACzD,IAAM,IAAkB,CAAC,GACrB;CAEJ,KAAK,IAAM,KAAS,GAAS;EACzB,IAAI,EAAM,SAAS,aAAa;GAE5B,AADA,EAAO,KAAK;IAAE,MAAM;IAAa,KAAK,EAAM;GAAI,CAAC,GACjD,IAAO,KAAA;GACP;EACJ;EACA,IAAI,EAAM,SAAS,WAAW;GAE1B,AADA,IAAO;IAAE,MAAM;IAAS,KAAK,EAAM;IAAK,SAAS;IAAO,OAAO,CAAC;GAAE,GAClE,EAAO,KAAK,CAAI;GAChB;EACJ;EAKA,AAJK,MACD,IAAO;GAAE,MAAM;GAAS,KAAK,SAAS,EAAM;GAAO,OAAO,CAAC;EAAE,GAC7D,EAAO,KAAK,CAAI,IAEpB,EAAK,MAAM,KAAK,CAAK;CACzB;CACA,OAAO;AACX;AAUA,SAAS,EACL,GACA,GACA,GACkB;CAClB,IAAI,MAAU,SAAS,MAAU,KAAA,GAC7B,OAAO;CAEX,IAAM,IAAQ,EAAQ,QACjB,MACG,EAAM,SAAS,aAAa,EAAM,SAAS,WACnD;CACA,OAAO,EACH,EACI,GACA,EAAM,KAAK,MAAS,EAAK,QAAQ,EAAK,GAAG,CAC7C,EACH,EAAE;AACP;AA+CA,SAAgB,EAAQ,EACpB,WACA,UACA,UACA,aACA,WAAQ,OACR,WACA,eAAY,IACZ,WAAQ,KACR,oBAAiB,IACjB,cACA,UACA,GAAG,KACU;CACb,IAAM,IAAS,EAAM,GACf,IAAS,EAAS,CAAK,GACvB,IAAY,EAAiB,GAAO,GAAO,CAAK,GAChD,IACF,QAAQ,IAAY,IAAiB,MAAW,WAC1C,GAAG,IAAY,IAAiB,EAAM,MACtC,IACE,IACA;CAEZ,OACI,kBAAC,SAAD;EACI,WAAW,EAAG,EAAO,SAAS,KAAa,EAAO,WAAW,CAAS;EACtE,OAAO;GAAE,OAAO;GAAY,GAAG;EAAM;EACrC,GAAI;EAHR,UAAA;GAKK,KAAU,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAS,UAAA;GAAY,CAAA;GACvD,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAK,cAAW;IAClC,UAAA,EAAO,KAAK,MAAU;KACnB,IAAI,EAAM,SAAS,aACf,OAAO,kBAAC,MAAD,EAAoB,WAAW,EAAO,UAAY,GAAzC,EAAM,GAAmC;KAE7D,IAAI,CAAC,EAAM,SACP,OACI,kBAAC,OAAD;MAAqB,WAAW,EAAO;MAClC,UAAA,EAAM,MAAM,KAAK,MACd,kBAAC,GAAD;OAEU;OACN,QAAQ,EAAK,QAAQ;OACV;OACX,UAAU;MACb,GALQ,EAAK,GAKb,CACJ;KACA,GAVK,EAAM,GAUX;KAGb,IAAM,IAAU,GAAG,EAAO,GAAG,EAAM;KACnC,OACI,kBAAC,OAAD;MAEI,MAAK;MACL,mBAAiB;MACjB,WAAW,EAAO;MAJtB,UAAA,CAMI,kBAAC,OAAD;OACI,IAAI;OACJ,MAAK;OACL,WAAW,EAAG,EAAO,SAAS,KAAa,EAAO,gBAAgB;OAEjE,UAAA,EAAM,QAAQ;MACd,CAAA,GACJ,EAAM,MAAM,KAAK,MACd,kBAAC,GAAD;OAEU;OACN,QAAQ,EAAK,QAAQ;OACV;OACX,UAAU;MACb,GALQ,EAAK,GAKb,CACJ,CACA;KArBI,GAAA,EAAM,GAqBV;IAEb,CAAC;GACA,CAAA;GACJ,KAAU,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAS,UAAA;GAAY,CAAA;EACpD;;AAEf"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e){let t=e.search(/[?#]/),n=t===-1?e:e.slice(0,t),r=n.replace(/\/+$/,``);return r===``&&n.startsWith(`/`)?`/`:r}function t(e,t){return t===`/`?e===`/`:e===t||e.startsWith(`${t}/`)}function n(n,r){let i=e(n),a=-1,o=-1;return r.forEach((n,r)=>{let s=e(n);s!==``&&t(i,s)&&s.length>o&&(a=r,o=s.length)}),a}function r(e,t){return t[n(e,t)]??``}exports.activeNavIndex=n,exports.activeNavKey=r;
|
|
2
|
+
//# sourceMappingURL=active-nav-key.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"active-nav-key.cjs","names":[],"sources":["../../../src/components/Sidebar/active-nav-key.ts"],"sourcesContent":["/**\n * Reduce a path or a key to the form two of them are compared in.\n *\n * The query string and the hash are dropped, because `/users?tab=2` is still the\n * `/users` screen, and trailing slashes are dropped, because `/users/` and\n * `/users` are the same route to every router this SDK pairs with. The root keeps\n * its single slash — stripping it would leave an empty string, which is the\n * \"nothing matched\" answer, not a path.\n *\n * @param path - A pathname, an `href`, or a key used as a URL.\n * @returns The comparable form of `path`.\n */\nfunction normalizePath(path: string): string {\n const cut = path.search(/[?#]/);\n const bare = cut === -1 ? path : path.slice(0, cut);\n const trimmed = bare.replace(/\\/+$/, \"\");\n return trimmed === \"\" && bare.startsWith(\"/\") ? \"/\" : trimmed;\n}\n\n/**\n * Whether `candidate` names the screen at `pathname` or one of its ancestors.\n *\n * The match is on **segment boundaries**, not on string prefixes: `/users` covers\n * `/users/3` but not `/users-admin`. The root `/` covers only itself — the same\n * rule react-router's `NavLink` applies — because a root that prefixed every path\n * would light \"Home\" on every screen the menu does not list.\n *\n * @param pathname - The normalized current path.\n * @param candidate - The normalized key or `href`.\n * @returns `true` when the candidate covers the path.\n */\nfunction covers(pathname: string, candidate: string): boolean {\n if (candidate === \"/\") {\n return pathname === \"/\";\n }\n return pathname === candidate || pathname.startsWith(`${candidate}/`);\n}\n\n/**\n * Index of the candidate that owns `pathname`: the longest one covering it.\n *\n * Longest wins so that `/dashboard` does not stay highlighted on\n * `/dashboard/tips/42` — with a first-match rule the order of the menu array would\n * decide, and nobody reading the menu knows it carries meaning. On a tie (`/users`\n * and `/users/` both present), the first one wins.\n *\n * @param pathname - The current route, as the router reports it.\n * @param candidates - One string per navigable entry, in menu order.\n * @returns The winning index, or `-1` when nothing covers the path.\n */\nexport function activeNavIndex(pathname: string, candidates: readonly string[]): number {\n const path = normalizePath(pathname);\n let winner = -1;\n let winnerLength = -1;\n candidates.forEach((raw, index) => {\n const candidate = normalizePath(raw);\n if (candidate !== \"\" && covers(path, candidate) && candidate.length > winnerLength) {\n winner = index;\n winnerLength = candidate.length;\n }\n });\n return winner;\n}\n\n/**\n * Resolve which navigation key is active for the current route.\n *\n * A menu whose keys are URLs almost never has a key equal to the pathname —\n * `/dashboard/tips/42` has to highlight `/dashboard/tips`. This picks the\n * **longest key that covers the path on a segment boundary**, which is what fixes\n * the two ways the one-line `keys.find((k) => pathname.startsWith(k))` goes wrong:\n * `/dashboard` stays lit on every screen of the panel (the array order decides),\n * and `/dashboard/tip` lights `/dashboard/tips`.\n *\n * The rules:\n *\n * - Query string and hash are ignored, on both sides.\n * - A trailing slash, on the route or on the key, changes nothing.\n * - `/` is active only on `/` itself, as with react-router's `NavLink`.\n * - The comparison is case-sensitive, as URL paths are.\n * - No match returns `\"\"` — never `undefined`, never a throw — so the result can\n * go straight into a `value` prop.\n *\n * The keys and the pathname have to live in the same space: with a router\n * `basename`, pass `useLocation().pathname` (which has the basename stripped) and\n * keys without it.\n *\n * Framework-agnostic on purpose: it takes a string, so it works with\n * react-router, with `window.location.pathname`, and for `BottomNavigation`,\n * `NavigationRail` or a `Drawer` menu just as for `Sidebar` — which does the same\n * resolution itself with `match=\"route\"`.\n *\n * @param pathname - The current route, e.g. `useLocation().pathname`.\n * @param keys - The navigation keys, each one a URL path.\n * @returns The active key exactly as given in `keys`, or `\"\"` when none covers\n * the route.\n *\n * @example\n * const { pathname } = useLocation();\n * const value = activeNavKey(pathname, [\"/dashboard\", \"/dashboard/tips\"]);\n * // \"/dashboard/tips/42\" → \"/dashboard/tips\"\n * // \"/dashboard/tip\" → \"/dashboard\"\n */\nexport function activeNavKey(pathname: string, keys: readonly string[]): string {\n return keys[activeNavIndex(pathname, keys)] ?? \"\";\n}\n"],"mappings":"AAYA,SAAS,EAAc,EAAsB,CACzC,IAAM,EAAM,EAAK,OAAO,MAAM,EACxB,EAAO,IAAQ,GAAK,EAAO,EAAK,MAAM,EAAG,CAAG,EAC5C,EAAU,EAAK,QAAQ,OAAQ,EAAE,EACvC,OAAO,IAAY,IAAM,EAAK,WAAW,GAAG,EAAI,IAAM,CAC1D,CAcA,SAAS,EAAO,EAAkB,EAA4B,CAI1D,OAHI,IAAc,IACP,IAAa,IAEjB,IAAa,GAAa,EAAS,WAAW,GAAG,EAAU,EAAE,CACxE,CAcA,SAAgB,EAAe,EAAkB,EAAuC,CACpF,IAAM,EAAO,EAAc,CAAQ,EAC/B,EAAS,GACT,EAAe,GAQnB,OAPA,EAAW,SAAS,EAAK,IAAU,CAC/B,IAAM,EAAY,EAAc,CAAG,EAC/B,IAAc,IAAM,EAAO,EAAM,CAAS,GAAK,EAAU,OAAS,IAClE,EAAS,EACT,EAAe,EAAU,OAEjC,CAAC,EACM,CACX,CAyCA,SAAgB,EAAa,EAAkB,EAAiC,CAC5E,OAAO,EAAK,EAAe,EAAU,CAAI,IAAM,EACnD"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/components/Sidebar/active-nav-key.ts
|
|
2
|
+
function e(e) {
|
|
3
|
+
let t = e.search(/[?#]/), n = t === -1 ? e : e.slice(0, t), r = n.replace(/\/+$/, "");
|
|
4
|
+
return r === "" && n.startsWith("/") ? "/" : r;
|
|
5
|
+
}
|
|
6
|
+
function t(e, t) {
|
|
7
|
+
return t === "/" ? e === "/" : e === t || e.startsWith(`${t}/`);
|
|
8
|
+
}
|
|
9
|
+
function n(n, r) {
|
|
10
|
+
let i = e(n), a = -1, o = -1;
|
|
11
|
+
return r.forEach((n, r) => {
|
|
12
|
+
let s = e(n);
|
|
13
|
+
s !== "" && t(i, s) && s.length > o && (a = r, o = s.length);
|
|
14
|
+
}), a;
|
|
15
|
+
}
|
|
16
|
+
function r(e, t) {
|
|
17
|
+
return t[n(e, t)] ?? "";
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { n as activeNavIndex, r as activeNavKey };
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=active-nav-key.js.map
|