tempest-react-sdk 0.81.0 → 0.82.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 +5 -6
- package/bin/create-tempest-app.e2e.test.mjs +31 -0
- package/bin/create-tempest-app.mjs +11 -1
- package/bin/lib/doctor/doctor.e2e.test.mjs +29 -0
- package/bin/lib/doctor/vitest.mjs +120 -0
- package/bin/lib/doctor/vitest.test.mjs +139 -0
- package/bin/lib/fs/contain.mjs +7 -0
- package/bin/lib/openapi/escape.mjs +68 -4
- package/bin/lib/openapi/generate.hostile.test.mjs +137 -0
- package/bin/lib/openapi/generate.mjs +78 -13
- package/bin/lib/openapi/pattern-risk.mjs +134 -0
- package/bin/lib/openapi/pattern-risk.test.mjs +45 -0
- package/bin/tempest-gen.test.mjs +100 -0
- package/bin/tempest.mjs +24 -8
- package/dist/access/use-can.cjs +1 -1
- package/dist/access/use-can.cjs.map +1 -1
- package/dist/access/use-can.js +25 -28
- package/dist/access/use-can.js.map +1 -1
- 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 +192 -149
- package/dist/auth/create-tempest-auth.js.map +1 -1
- package/dist/auth/session-identity.cjs +2 -0
- package/dist/auth/session-identity.cjs.map +1 -0
- package/dist/auth/session-identity.js +12 -0
- package/dist/auth/session-identity.js.map +1 -0
- package/dist/br/boleto.cjs.map +1 -1
- package/dist/br/boleto.js.map +1 -1
- package/dist/components/AuthForm/AuthLinks.cjs +1 -1
- package/dist/components/AuthForm/AuthLinks.cjs.map +1 -1
- package/dist/components/AuthForm/AuthLinks.js +18 -15
- package/dist/components/AuthForm/AuthLinks.js.map +1 -1
- package/dist/components/AuthPage/AuthPage.cjs +1 -1
- package/dist/components/AuthPage/AuthPage.cjs.map +1 -1
- package/dist/components/AuthPage/AuthPage.js +58 -52
- package/dist/components/AuthPage/AuthPage.js.map +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.cjs +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.cjs.map +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.js +22 -21
- package/dist/components/Breadcrumbs/Breadcrumbs.js.map +1 -1
- package/dist/components/Button/Button.cjs +1 -1
- package/dist/components/Button/Button.cjs.map +1 -1
- package/dist/components/Button/Button.js +41 -40
- package/dist/components/Button/Button.js.map +1 -1
- package/dist/components/Chat/ChatAttachments.cjs +1 -1
- package/dist/components/Chat/ChatAttachments.cjs.map +1 -1
- package/dist/components/Chat/ChatAttachments.js +38 -38
- package/dist/components/Form/Form.cjs +1 -1
- package/dist/components/Form/Form.cjs.map +1 -1
- package/dist/components/Form/Form.js +30 -32
- package/dist/components/Form/Form.js.map +1 -1
- package/dist/components/Layout/Layout.cjs +1 -1
- package/dist/components/Layout/Layout.cjs.map +1 -1
- package/dist/components/Layout/Layout.js +25 -24
- package/dist/components/Layout/Layout.js.map +1 -1
- package/dist/components/Layout/gap.cjs +2 -0
- package/dist/components/Layout/gap.cjs.map +1 -0
- package/dist/components/Layout/gap.js +15 -0
- package/dist/components/Layout/gap.js.map +1 -0
- package/dist/components/NavigationMenu/NavigationMenu.cjs +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.cjs.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.js +71 -70
- package/dist/components/NavigationMenu/NavigationMenu.js.map +1 -1
- package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
- package/dist/components/Sidebar/Sidebar.js.map +1 -1
- package/dist/components/Sidebar/SidebarEntry.cjs +1 -1
- package/dist/components/Sidebar/SidebarEntry.cjs.map +1 -1
- package/dist/components/Sidebar/SidebarEntry.js +30 -29
- package/dist/components/Sidebar/SidebarEntry.js.map +1 -1
- package/dist/data/create-data-provider.cjs +1 -1
- package/dist/data/create-data-provider.cjs.map +1 -1
- package/dist/data/create-data-provider.js +1 -2
- package/dist/data/create-data-provider.js.map +1 -1
- package/dist/forms/validate-form.cjs +1 -1
- package/dist/forms/validate-form.cjs.map +1 -1
- package/dist/forms/validate-form.js +6 -1
- package/dist/forms/validate-form.js.map +1 -1
- package/dist/forms/zod-resolver.cjs +1 -1
- package/dist/forms/zod-resolver.cjs.map +1 -1
- package/dist/forms/zod-resolver.js +8 -3
- package/dist/forms/zod-resolver.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 +90 -72
- 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 +33 -9
- package/dist/http/credential-scope.js.map +1 -1
- package/dist/http/presigned-post.cjs.map +1 -1
- package/dist/http/presigned-post.js.map +1 -1
- package/dist/http/resumable-upload.cjs +1 -1
- package/dist/http/resumable-upload.cjs.map +1 -1
- package/dist/http/resumable-upload.js +90 -84
- 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 +8 -8
- package/dist/http/upload-with-progress.js.map +1 -1
- package/dist/oauth/GoogleSignIn.cjs +1 -1
- package/dist/oauth/GoogleSignIn.cjs.map +1 -1
- package/dist/oauth/GoogleSignIn.js +5 -4
- package/dist/oauth/GoogleSignIn.js.map +1 -1
- package/dist/offline/create-offline-sync.cjs +1 -1
- package/dist/offline/create-offline-sync.cjs.map +1 -1
- package/dist/offline/create-offline-sync.js +87 -69
- package/dist/offline/create-offline-sync.js.map +1 -1
- package/dist/query/offline-persistence.cjs +1 -1
- package/dist/query/offline-persistence.cjs.map +1 -1
- package/dist/query/offline-persistence.js +54 -44
- package/dist/query/offline-persistence.js.map +1 -1
- package/dist/sse/create-event-stream.cjs +1 -1
- package/dist/sse/create-event-stream.cjs.map +1 -1
- package/dist/sse/create-event-stream.js +48 -41
- package/dist/sse/create-event-stream.js.map +1 -1
- package/dist/sse/ticket-event-stream.cjs +1 -1
- package/dist/sse/ticket-event-stream.cjs.map +1 -1
- package/dist/sse/ticket-event-stream.js +48 -19
- package/dist/sse/ticket-event-stream.js.map +1 -1
- package/dist/sse/use-event-stream.cjs +1 -1
- package/dist/sse/use-event-stream.cjs.map +1 -1
- package/dist/sse/use-event-stream.js +19 -14
- package/dist/sse/use-event-stream.js.map +1 -1
- package/dist/sw/background-sync.cjs +1 -1
- package/dist/sw/background-sync.cjs.map +1 -1
- package/dist/sw/background-sync.js +10 -8
- package/dist/sw/background-sync.js.map +1 -1
- package/dist/sw/cache.cjs +1 -1
- package/dist/sw/cache.cjs.map +1 -1
- package/dist/sw/cache.js +36 -30
- package/dist/sw/cache.js.map +1 -1
- package/dist/sw/create-push-handler.cjs +1 -1
- package/dist/sw/create-push-handler.cjs.map +1 -1
- package/dist/sw/create-push-handler.js +20 -10
- package/dist/sw/create-push-handler.js.map +1 -1
- package/dist/sw.d.ts +44 -13
- package/dist/tabular/compact.cjs +1 -1
- package/dist/tabular/compact.cjs.map +1 -1
- package/dist/tabular/compact.js +35 -16
- package/dist/tabular/compact.js.map +1 -1
- package/dist/tabular/manifest.cjs +1 -1
- package/dist/tabular/manifest.cjs.map +1 -1
- package/dist/tabular/manifest.js +33 -15
- package/dist/tabular/manifest.js.map +1 -1
- package/dist/tabular.d.ts +11 -1
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +457 -68
- package/dist/tempest-react-sdk.js +224 -224
- package/dist/theme/initial-theme.cjs +4 -4
- package/dist/theme/initial-theme.cjs.map +1 -1
- package/dist/theme/initial-theme.js +8 -5
- package/dist/theme/initial-theme.js.map +1 -1
- package/dist/utils/compressed-storage.cjs +1 -1
- package/dist/utils/compressed-storage.cjs.map +1 -1
- package/dist/utils/compressed-storage.js +13 -7
- package/dist/utils/compressed-storage.js.map +1 -1
- package/dist/utils/objects.cjs +1 -1
- package/dist/utils/objects.cjs.map +1 -1
- package/dist/utils/objects.js +13 -7
- package/dist/utils/objects.js.map +1 -1
- package/dist/utils/safe-url.cjs +1 -1
- package/dist/utils/safe-url.cjs.map +1 -1
- package/dist/utils/safe-url.js +4 -1
- package/dist/utils/safe-url.js.map +1 -1
- package/dist/webrtc/peer-mesh.cjs +1 -1
- package/dist/webrtc/peer-mesh.cjs.map +1 -1
- package/dist/webrtc/peer-mesh.js +7 -1
- package/dist/webrtc/peer-mesh.js.map +1 -1
- package/dist/ws/create-web-socket.cjs +1 -1
- package/dist/ws/create-web-socket.cjs.map +1 -1
- package/dist/ws/create-web-socket.js +77 -61
- package/dist/ws/create-web-socket.js.map +1 -1
- package/dist/ws/use-web-socket.cjs +1 -1
- package/dist/ws/use-web-socket.cjs.map +1 -1
- package/dist/ws/use-web-socket.js +4 -3
- package/dist/ws/use-web-socket.js.map +1 -1
- package/package.json +1 -1
- package/template-pwa/src/pwa.tsx +42 -0
- package/template-pwa/src/sw.ts +5 -4
|
@@ -1,72 +1,70 @@
|
|
|
1
1
|
import { cn as e } from "../../utils/cn.js";
|
|
2
|
-
import t from "
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
2
|
+
import { resolveGap as t } from "../Layout/gap.js";
|
|
3
|
+
import n from "./Form.module.js";
|
|
4
|
+
import { forwardRef as r } from "react";
|
|
5
|
+
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
5
6
|
//#region src/components/Form/Form.tsx
|
|
6
|
-
function a(e, t) {
|
|
7
|
-
return e === void 0 ? `${t * 4}px` : typeof e == "number" ? `${e * 4}px` : e;
|
|
8
|
-
}
|
|
9
7
|
function o(e) {
|
|
10
8
|
return e === void 0 ? "repeat(2, minmax(0, 1fr))" : typeof e == "number" ? `repeat(${e}, minmax(0, 1fr))` : e;
|
|
11
9
|
}
|
|
12
|
-
var s =
|
|
10
|
+
var s = r(function({ layout: r = "stack", columns: a, gap: s, className: c, style: l, children: u, ...d }, f) {
|
|
13
11
|
let p = {
|
|
14
|
-
gap:
|
|
15
|
-
...
|
|
12
|
+
gap: t("Form", s ?? 4),
|
|
13
|
+
...r === "grid" ? { gridTemplateColumns: o(a) } : null,
|
|
16
14
|
...l
|
|
17
15
|
};
|
|
18
|
-
return /* @__PURE__ */
|
|
16
|
+
return /* @__PURE__ */ i("form", {
|
|
19
17
|
ref: f,
|
|
20
|
-
className: e(
|
|
18
|
+
className: e(n.form, n[r], c),
|
|
21
19
|
style: p,
|
|
22
20
|
...d,
|
|
23
21
|
children: u
|
|
24
22
|
});
|
|
25
23
|
});
|
|
26
|
-
function c({ title:
|
|
24
|
+
function c({ title: r, description: s, layout: c = "stack", columns: l, gap: u, className: d, style: f, children: p, ...m }) {
|
|
27
25
|
let h = {
|
|
28
|
-
gap:
|
|
26
|
+
gap: t("FormSection", u ?? 4),
|
|
29
27
|
...c === "grid" ? { gridTemplateColumns: o(l) } : null
|
|
30
28
|
};
|
|
31
|
-
return /* @__PURE__ */
|
|
32
|
-
className: e(
|
|
29
|
+
return /* @__PURE__ */ a("section", {
|
|
30
|
+
className: e(n.section, d),
|
|
33
31
|
style: f,
|
|
34
32
|
...m,
|
|
35
|
-
children: [(
|
|
36
|
-
className:
|
|
37
|
-
children: [
|
|
38
|
-
className:
|
|
39
|
-
children:
|
|
40
|
-
}), s && /* @__PURE__ */
|
|
41
|
-
className:
|
|
33
|
+
children: [(r || s) && /* @__PURE__ */ a("header", {
|
|
34
|
+
className: n.sectionHeader,
|
|
35
|
+
children: [r && /* @__PURE__ */ i("h3", {
|
|
36
|
+
className: n.sectionTitle,
|
|
37
|
+
children: r
|
|
38
|
+
}), s && /* @__PURE__ */ i("p", {
|
|
39
|
+
className: n.sectionDescription,
|
|
42
40
|
children: s
|
|
43
41
|
})]
|
|
44
|
-
}), /* @__PURE__ */
|
|
45
|
-
className: e(
|
|
42
|
+
}), /* @__PURE__ */ i("div", {
|
|
43
|
+
className: e(n.sectionBody, n[c]),
|
|
46
44
|
style: h,
|
|
47
45
|
children: p
|
|
48
46
|
})]
|
|
49
47
|
});
|
|
50
48
|
}
|
|
51
|
-
function l({ gap:
|
|
49
|
+
function l({ gap: r, className: a, style: o, children: s, ...c }) {
|
|
52
50
|
let l = {
|
|
53
|
-
gap:
|
|
51
|
+
gap: t("FormRow", r ?? 3),
|
|
54
52
|
...o
|
|
55
53
|
};
|
|
56
|
-
return /* @__PURE__ */
|
|
57
|
-
className: e(
|
|
54
|
+
return /* @__PURE__ */ i("div", {
|
|
55
|
+
className: e(n.row, a),
|
|
58
56
|
style: l,
|
|
59
57
|
...c,
|
|
60
58
|
children: s
|
|
61
59
|
});
|
|
62
60
|
}
|
|
63
|
-
function u({ align:
|
|
61
|
+
function u({ align: r = "end", gap: a, className: o, style: s, children: c, ...l }) {
|
|
64
62
|
let u = {
|
|
65
|
-
gap:
|
|
63
|
+
gap: t("FormActions", a ?? 2),
|
|
66
64
|
...s
|
|
67
65
|
};
|
|
68
|
-
return /* @__PURE__ */
|
|
69
|
-
className: e(
|
|
66
|
+
return /* @__PURE__ */ i("div", {
|
|
67
|
+
className: e(n.actions, n[r], o),
|
|
70
68
|
style: u,
|
|
71
69
|
...l,
|
|
72
70
|
children: c
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Form.js","names":["styles","Form_module_default","
|
|
1
|
+
{"version":3,"file":"Form.js","names":["styles","Form_module_default","resolveColumns","columns","Form","forwardRef","props","finalStyle","resolveGap","gap","layout","style","cn","className","FormSection","bodyStyle","title","description","FormRow","FormActions","align"],"sources":["../../../src/components/Form/Form.tsx"],"sourcesContent":["import { forwardRef } from \"react\";\nimport type { CSSProperties, FormHTMLAttributes, HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { resolveGap } from \"../Layout/gap\";\nimport styles from \"./Form.module.css\";\n\nexport type FormLayout = \"stack\" | \"inline\" | \"grid\";\nexport type FormActionsAlign = \"start\" | \"center\" | \"end\" | \"between\";\n\ninterface LayoutProps {\n /** Field arrangement. `stack` stacks fields vertically, `inline` lines them up in a wrapping row, `grid` arranges them in `columns` equal-width tracks. Default `stack`. */\n layout?: FormLayout;\n /** Number of columns when `layout=\"grid\"`, or a custom `grid-template-columns` value. Default `2`. */\n columns?: number | string;\n /** Gap between fields. Numbers map to a multiple of the 4px scale (default `4` → 16px). A number is a step, not pixels (`6` → 24px): above `12` a development build warns once, and a string such as `\"96px\"` passes through untouched. */\n gap?: number | string;\n}\n\nfunction resolveColumns(columns: number | string | undefined): string {\n if (columns === undefined) return \"repeat(2, minmax(0, 1fr))\";\n return typeof columns === \"number\" ? `repeat(${columns}, minmax(0, 1fr))` : columns;\n}\n\nexport interface FormProps extends FormHTMLAttributes<HTMLFormElement>, LayoutProps {}\n\n/**\n * Form wrapper with a built-in layout variant. Replaces ad-hoc `<form>` +\n * `<Stack>` / `<Grid>` boilerplate; pair with [[FormRow]], [[FormSection]],\n * and [[FormActions]] for richer layouts.\n *\n * @example\n * <Form layout=\"grid\" columns={2} gap={4} onSubmit={form.handleSubmit(save)}>\n * <Input label=\"Nome\" {...form.register(\"name\")} />\n * <Input label=\"Email\" {...form.register(\"email\")} />\n * <FormActions align=\"end\">\n * <Button type=\"submit\">Salvar</Button>\n * </FormActions>\n * </Form>\n */\nexport const Form = forwardRef<HTMLFormElement, FormProps>(function Form(\n { layout = \"stack\", columns, gap, className, style, children, ...props },\n ref,\n) {\n const finalStyle: CSSProperties = {\n gap: resolveGap(\"Form\", gap ?? 4),\n ...(layout === \"grid\" ? { gridTemplateColumns: resolveColumns(columns) } : null),\n ...style,\n };\n return (\n <form\n ref={ref}\n className={cn(styles.form, styles[layout], className)}\n style={finalStyle}\n {...props}\n >\n {children}\n </form>\n );\n});\n\nexport interface FormSectionProps extends Omit<HTMLAttributes<HTMLElement>, \"title\">, LayoutProps {\n title?: ReactNode;\n description?: ReactNode;\n}\n\n/**\n * Visually grouped subset of fields with an optional title and description.\n * Has its own `layout`/`columns`/`gap` so subsets can stack while the parent\n * grids (or vice versa).\n *\n * The header is deliberately not a `SectionHeader`: a field group sits one step\n * below a page section, and its title is `text-lg` / semibold / `leading-snug`\n * with `space-3` below, against `text-2xl` / bold / `leading-tight` with\n * `space-5` for `SectionHeader`. Sharing the component would either grow every\n * form group to page-section size or add a size switch to `SectionHeader` that\n * only this caller uses.\n *\n * @example\n * <FormSection title=\"Endereço\" description=\"Usado para entrega\" layout=\"grid\" columns={3}>\n * <Input label=\"CEP\" />\n * <Input label=\"Rua\" />\n * <Input label=\"Número\" />\n * </FormSection>\n */\nexport function FormSection({\n title,\n description,\n layout = \"stack\",\n columns,\n gap,\n className,\n style,\n children,\n ...props\n}: FormSectionProps) {\n const bodyStyle: CSSProperties = {\n gap: resolveGap(\"FormSection\", gap ?? 4),\n ...(layout === \"grid\" ? { gridTemplateColumns: resolveColumns(columns) } : null),\n };\n return (\n <section className={cn(styles.section, className)} style={style} {...props}>\n {(title || description) && (\n <header className={styles.sectionHeader}>\n {title && <h3 className={styles.sectionTitle}>{title}</h3>}\n {description && <p className={styles.sectionDescription}>{description}</p>}\n </header>\n )}\n <div className={cn(styles.sectionBody, styles[layout])} style={bodyStyle}>\n {children}\n </div>\n </section>\n );\n}\n\nexport interface FormRowProps extends HTMLAttributes<HTMLDivElement> {\n /** Gap between row items. Numbers map to a multiple of the 4px scale (default `3` → 12px). A number is a step, not pixels (`6` → 24px): above `12` a development build warns once, and a string such as `\"96px\"` passes through untouched. */\n gap?: number | string;\n}\n\n/**\n * Forces a horizontal row regardless of parent layout — useful for grouping\n * two short fields side-by-side inside an otherwise stacked form (e.g. CEP +\n * city, expiry month + year).\n *\n * @example\n * <Form layout=\"stack\">\n * <FormRow>\n * <Input label=\"CEP\" />\n * <Input label=\"Cidade\" />\n * </FormRow>\n * <Input label=\"Endereço completo\" />\n * </Form>\n */\nexport function FormRow({ gap, className, style, children, ...props }: FormRowProps) {\n const finalStyle: CSSProperties = { gap: resolveGap(\"FormRow\", gap ?? 3), ...style };\n return (\n <div className={cn(styles.row, className)} style={finalStyle} {...props}>\n {children}\n </div>\n );\n}\n\nexport interface FormActionsProps extends HTMLAttributes<HTMLDivElement> {\n /** Horizontal alignment of buttons. Default `end`. */\n align?: FormActionsAlign;\n /** Gap between buttons. Numbers map to a multiple of the 4px scale (default `2` → 8px). A number is a step, not pixels (`6` → 24px): above `12` a development build warns once, and a string such as `\"96px\"` passes through untouched. */\n gap?: number | string;\n}\n\n/**\n * Footer button row for a form. Use inside (or after) [[Form]] / [[FormSection]].\n *\n * @example\n * <FormActions align=\"end\">\n * <Button variant=\"ghost\" onClick={onCancel}>Cancelar</Button>\n * <Button type=\"submit\" loading={form.formState.isSubmitting}>Salvar</Button>\n * </FormActions>\n */\nexport function FormActions({\n align = \"end\",\n gap,\n className,\n style,\n children,\n ...props\n}: FormActionsProps) {\n const finalStyle: CSSProperties = { gap: resolveGap(\"FormActions\", gap ?? 2), ...style };\n return (\n <div className={cn(styles.actions, styles[align], className)} style={finalStyle} {...props}>\n {children}\n </div>\n );\n}\n"],"mappings":";;;;;;AAkBA,SAASE,EAAe,GAA8C;CAElE,OADIC,MAAY,KAAA,IAAkB,8BAC3B,OAAOA,KAAY,WAAW,UAAUA,EAAQ,qBAAqBA;AAChF;AAkBA,IAAaC,IAAOC,EAAuC,SACvD,EAAE,YAAS,SAAS,YAAS,QAAK,cAAW,UAAO,aAAU,GAAGC,KACjE,GACF;CACE,IAAMC,IAA4B;EAC9B,KAAKC,EAAW,QAAQC,KAAO,CAAC;EAChC,GAAIC,MAAW,SAAS,EAAE,qBAAqBR,EAAeC,CAAO,EAAE,IAAI;EAC3E,GAAGQ;CACP;CACA,OACI,kBAAC,QAAD;EACS;EACL,WAAWC,EAAGZ,EAAO,MAAMA,EAAOU,IAASG,CAAS;EACpD,OAAON;EACP,GAAID;EAEH;CACC,CAAA;AAEd,CAAC;AA0BD,SAAgBQ,EAAY,EACxB,UACA,gBACA,YAAS,SACT,YACA,QACA,cACA,UACA,aACA,GAAGR,KACc;CACjB,IAAMS,IAA2B;EAC7B,KAAKP,EAAW,eAAeC,KAAO,CAAC;EACvC,GAAIC,MAAW,SAAS,EAAE,qBAAqBR,EAAeC,CAAO,EAAE,IAAI;CAC/E;CACA,OACI,kBAAC,WAAD;EAAS,WAAWS,EAAGZ,EAAO,SAASa,CAAS;EAAU;EAAO,GAAIP;EAArE,UAAA,EACMU,KAASC,MACP,kBAAC,UAAD;GAAQ,WAAWjB,EAAO;GAA1B,UAAA,CACKgB,KAAS,kBAAC,MAAD;IAAI,WAAWhB,EAAO;IAAe,UAAAgB;GAAU,CAAA,GACxDC,KAAe,kBAAC,KAAD;IAAG,WAAWjB,EAAO;IAAqB,UAAAiB;GAAe,CAAA,CACrE;EAEZ,CAAA,GAAA,kBAAC,OAAD;GAAK,WAAWL,EAAGZ,EAAO,aAAaA,EAAOU,EAAO;GAAG,OAAOK;GAC1D;EACA,CAAA,CACA;;AAEjB;AAqBA,SAAgBG,EAAQ,EAAE,QAAK,cAAW,UAAO,aAAU,GAAGZ,KAAuB;CACjF,IAAMC,IAA4B;EAAE,KAAKC,EAAW,WAAWC,KAAO,CAAC;EAAG,GAAGE;CAAM;CACnF,OACI,kBAAC,OAAD;EAAK,WAAWC,EAAGZ,EAAO,KAAKa,CAAS;EAAG,OAAON;EAAY,GAAID;EAC7D;CACA,CAAA;AAEb;AAkBA,SAAgBa,EAAY,EACxB,WAAQ,OACR,QACA,cACA,UACA,aACA,GAAGb,KACc;CACjB,IAAMC,IAA4B;EAAE,KAAKC,EAAW,eAAeC,KAAO,CAAC;EAAG,GAAGE;CAAM;CACvF,OACI,kBAAC,OAAD;EAAK,WAAWC,EAAGZ,EAAO,SAASA,EAAOoB,IAAQP,CAAS;EAAG,OAAON;EAAY,GAAID;EAChF;CACA,CAAA;AAEb"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../utils/cn.cjs"),t=require("../../hooks/use-breakpoint.cjs"),n=require("./Layout.module.cjs");let
|
|
1
|
+
const e=require("../../utils/cn.cjs"),t=require("../../hooks/use-breakpoint.cjs"),n=require("./gap.cjs"),r=require("./Layout.module.cjs");let i=require("react/jsx-runtime");function a({size:t=`lg`,className:n,children:a,...o}){return(0,i.jsx)(`div`,{className:e.cn(r.default.container,r.default[t],n),...o,children:a})}function o(e){return typeof e==`object`&&!!e&&(`mobile`in e||`tablet`in e||`desktop`in e)}function s(e,t){if(e!==void 0)return o(e)?t===`desktop`?e.desktop??e.tablet??e.mobile:t===`tablet`?e.tablet??e.mobile??e.desktop:e.mobile??e.tablet??e.desktop:e}function c(e,t){return t===void 0?void 0:n.resolveGap(e,t)}function l(e,t){return e?`mobile`:t?`tablet`:`desktop`}function u({direction:n=`vertical`,gap:a=2,align:o,justify:u,wrap:d=!1,className:f,style:p,children:m,...h}){let{isMobile:g,isTablet:_}=t.useBreakpoint(),v=l(g,_),y=s(n,v)??`vertical`,b={gap:c(`Stack`,s(a,v)),...p},x=u===`between`?r.default.justifyBetween:u===`center`?r.default.justifyCenter:u===`end`?r.default.justifyEnd:u===`start`?r.default.justifyStart:void 0;return(0,i.jsx)(`div`,{className:e.cn(r.default.stack,y===`vertical`?r.default.vertical:r.default.horizontal,o&&r.default[o],x,d&&r.default.wrap,f),style:b,...h,children:m})}function d(e){if(e!==void 0)return typeof e==`number`?`repeat(${e}, minmax(0, 1fr))`:e}function f({columns:n=2,gap:a=4,className:o,style:u,children:f,...p}){let{isMobile:m,isTablet:h}=t.useBreakpoint(),g=l(m,h),_=d(s(n,g)),v=c(`Grid`,s(a,g));return(0,i.jsx)(`div`,{className:e.cn(r.default.grid,o),style:{gridTemplateColumns:_,gap:v,...u},...p,children:f})}exports.Container=a,exports.Grid=f,exports.Stack=u;
|
|
2
2
|
//# sourceMappingURL=Layout.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Layout.cjs","names":["cn","styles","useBreakpoint","require_cn","require_use_breakpoint","require_Layout_module","react_jsx_runtime","Container","props","size","className","isResponsiveObject","value","pickResponsive","device","
|
|
1
|
+
{"version":3,"file":"Layout.cjs","names":["cn","styles","resolveGap","useBreakpoint","require_cn","require_use_breakpoint","require_gap","require_Layout_module","react_jsx_runtime","Container","props","size","className","isResponsiveObject","value","pickResponsive","device","resolveOptionalGap","gap","component","deviceFromBreakpoint","isMobile","isTablet","Stack","finalDirection","direction","finalStyle","style","justifyClass","justify","align","wrap","resolveColumns","columns","Grid","templateColumns","finalGap"],"sources":["../../../src/components/Layout/Layout.tsx"],"sourcesContent":["import type { CSSProperties, HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { useBreakpoint } from \"@/hooks/use-breakpoint\";\nimport { resolveGap } from \"./gap\";\nimport styles from \"./Layout.module.css\";\n\nexport type ContainerSize = \"sm\" | \"md\" | \"lg\" | \"xl\" | \"full\";\n\nexport interface ContainerProps extends HTMLAttributes<HTMLDivElement> {\n size?: ContainerSize;\n}\n\n/**\n * Page-level horizontal container with a max-width preset and responsive\n * side padding (`space-4` mobile / `space-6` tablet / `space-8` desktop).\n */\nexport function Container({ size = \"lg\", className, children, ...props }: ContainerProps) {\n return (\n <div className={cn(styles.container, styles[size], className)} {...props}>\n {children}\n </div>\n );\n}\n\n/**\n * Responsive value — either a single value applied at all breakpoints, or\n * an object with `mobile` / `tablet` / `desktop` overrides. Apps can mix\n * any combination; `mobile` is the base, `tablet` / `desktop` cascade up.\n */\nexport type ResponsiveValue<T> =\n | T\n | {\n mobile?: T;\n tablet?: T;\n desktop?: T;\n };\n\nfunction isResponsiveObject<T>(value: ResponsiveValue<T>): value is {\n mobile?: T;\n tablet?: T;\n desktop?: T;\n} {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (\"mobile\" in value || \"tablet\" in value || \"desktop\" in value)\n );\n}\n\nfunction pickResponsive<T>(\n value: ResponsiveValue<T> | undefined,\n device: \"mobile\" | \"tablet\" | \"desktop\",\n): T | undefined {\n if (value === undefined) return undefined;\n if (!isResponsiveObject(value)) return value;\n if (device === \"desktop\") return value.desktop ?? value.tablet ?? value.mobile;\n if (device === \"tablet\") return value.tablet ?? value.mobile ?? value.desktop;\n return value.mobile ?? value.tablet ?? value.desktop;\n}\n\nexport type StackAlign = \"start\" | \"center\" | \"end\" | \"stretch\";\nexport type StackJustify = \"start\" | \"center\" | \"end\" | \"between\";\nexport type StackDirection = \"vertical\" | \"horizontal\";\n\nexport interface StackProps extends HTMLAttributes<HTMLDivElement> {\n /** Direction. Accepts responsive object — e.g. `{ mobile: \"vertical\", desktop: \"horizontal\" }`. */\n direction?: ResponsiveValue<StackDirection>;\n /** Gap as a CSS length. Numbers map to a multiple of the 4px scale. Accepts responsive object. A number is a step, not pixels (`6` → 24px): above `12` a development build warns once, and a string such as `\"96px\"` passes through untouched. */\n gap?: ResponsiveValue<number | string>;\n align?: StackAlign;\n justify?: StackJustify;\n wrap?: boolean;\n children?: ReactNode;\n}\n\nfunction resolveOptionalGap(\n component: string,\n gap: number | string | undefined,\n): string | undefined {\n return gap === undefined ? undefined : resolveGap(component, gap);\n}\n\nfunction deviceFromBreakpoint(\n isMobile: boolean,\n isTablet: boolean,\n): \"mobile\" | \"tablet\" | \"desktop\" {\n if (isMobile) return \"mobile\";\n if (isTablet) return \"tablet\";\n return \"desktop\";\n}\n\n/** Flex-based vertical or horizontal stack with a numeric `gap`. */\nexport function Stack({\n direction = \"vertical\",\n gap = 2,\n align,\n justify,\n wrap = false,\n className,\n style,\n children,\n ...props\n}: StackProps) {\n const { isMobile, isTablet } = useBreakpoint();\n const device = deviceFromBreakpoint(isMobile, isTablet);\n const finalDirection: StackDirection = pickResponsive(direction, device) ?? \"vertical\";\n const finalGap = resolveOptionalGap(\"Stack\", pickResponsive(gap, device));\n const finalStyle: CSSProperties = { gap: finalGap, ...style };\n const justifyClass =\n justify === \"between\"\n ? styles.justifyBetween\n : justify === \"center\"\n ? styles.justifyCenter\n : justify === \"end\"\n ? styles.justifyEnd\n : justify === \"start\"\n ? styles.justifyStart\n : undefined;\n return (\n <div\n className={cn(\n styles.stack,\n finalDirection === \"vertical\" ? styles.vertical : styles.horizontal,\n align && styles[align],\n justifyClass,\n wrap && styles.wrap,\n className,\n )}\n style={finalStyle}\n {...props}\n >\n {children}\n </div>\n );\n}\n\nexport interface GridProps extends HTMLAttributes<HTMLDivElement> {\n /** Number of columns or a custom `grid-template-columns` value. Accepts responsive object. */\n columns?: ResponsiveValue<number | string>;\n /** Gap as a CSS length. Numbers map to a multiple of the 4px scale. Accepts responsive object. A number is a step, not pixels (`6` → 24px): above `12` a development build warns once, and a string such as `\"96px\"` passes through untouched. */\n gap?: ResponsiveValue<number | string>;\n children?: ReactNode;\n}\n\nfunction resolveColumns(columns: number | string | undefined): string | undefined {\n if (columns === undefined) return undefined;\n return typeof columns === \"number\" ? `repeat(${columns}, minmax(0, 1fr))` : columns;\n}\n\n/** Simple CSS-Grid wrapper for equal-width or custom column layouts. */\nexport function Grid({ columns = 2, gap = 4, className, style, children, ...props }: GridProps) {\n const { isMobile, isTablet } = useBreakpoint();\n const device = deviceFromBreakpoint(isMobile, isTablet);\n const templateColumns = resolveColumns(pickResponsive(columns, device));\n const finalGap = resolveOptionalGap(\"Grid\", pickResponsive(gap, device));\n return (\n <div\n className={cn(styles.grid, className)}\n style={{ gridTemplateColumns: templateColumns, gap: finalGap, ...style }}\n {...props}\n >\n {children}\n </div>\n );\n}\n"],"mappings":"A,6KAgBA,SAAgBS,EAAU,CAAE,OAAO,KAAM,YAAW,WAAU,GAAGC,GAAyB,CACtF,OACI,EAAAF,EAAA,IAAA,CAAC,MAAD,CAAK,UAAWR,EAAAA,GAAGC,EAAAA,QAAO,UAAWA,EAAAA,QAAOU,GAAOC,CAAS,EAAG,GAAIF,EAC9D,UACA,CAAA,CAEb,CAeA,SAASG,EAAsB,EAI7B,CACE,OACI,OAAOC,GAAU,YACjBA,IACC,WAAYA,GAAS,WAAYA,GAAS,YAAaA,EAEhE,CAEA,SAASC,EACL,EACA,EACa,CACb,GAAID,IAAU,IAAA,GAId,OAHKD,EAAmBC,CAAK,EACzBE,IAAW,UAAkBF,EAAM,SAAWA,EAAM,QAAUA,EAAM,OACpEE,IAAW,SAAiBF,EAAM,QAAUA,EAAM,QAAUA,EAAM,QAC/DA,EAAM,QAAUA,EAAM,QAAUA,EAAM,QAHNA,CAI3C,CAiBA,SAASG,EACL,EACA,EACkB,CAClB,OAAOC,IAAQ,IAAA,GAAY,IAAA,GAAYhB,EAAAA,WAAWiB,EAAWD,CAAG,CACpE,CAEA,SAASE,EACL,EACA,EAC+B,CAG/B,OAFIC,EAAiB,SACjBC,EAAiB,SACd,SACX,CAGA,SAAgBC,EAAM,CAClB,YAAY,WACZ,MAAM,EACN,QACA,UACA,OAAO,GACP,YACA,QACA,WACA,GAAGb,GACQ,CACX,GAAM,CAAE,WAAU,YAAaP,EAAAA,cAAc,EACvCa,EAASI,EAAqBC,EAAUC,CAAQ,EAChDE,EAAiCT,EAAeU,EAAWT,CAAM,GAAK,WAEtEU,EAA4B,CAAE,IADnBT,EAAmB,QAASF,EAAeG,EAAKF,CAAM,CAC9B,EAAU,GAAGW,CAAM,EACtDC,EACFC,IAAY,UACN5B,EAAAA,QAAO,eACP4B,IAAY,SACV5B,EAAAA,QAAO,cACP4B,IAAY,MACV5B,EAAAA,QAAO,WACP4B,IAAY,QACV5B,EAAAA,QAAO,aACP,IAAA,GAChB,OACI,EAAAO,EAAA,IAAA,CAAC,MAAD,CACI,UAAWR,EAAAA,GACPC,EAAAA,QAAO,MACPuB,IAAmB,WAAavB,EAAAA,QAAO,SAAWA,EAAAA,QAAO,WACzD6B,GAAS7B,EAAAA,QAAO6B,GAChBF,EACAG,GAAQ9B,EAAAA,QAAO,KACfW,CACJ,EACA,MAAOc,EACP,GAAIhB,EAEH,UACA,CAAA,CAEb,CAUA,SAASsB,EAAe,EAA0D,CAC9E,GAAIC,IAAY,IAAA,GAChB,OAAO,OAAOA,GAAY,SAAW,UAAUA,EAAQ,mBAAqBA,CAChF,CAGA,SAAgBC,EAAK,CAAE,UAAU,EAAG,MAAM,EAAG,YAAW,QAAO,WAAU,GAAGxB,GAAoB,CAC5F,GAAM,CAAE,WAAU,YAAaP,EAAAA,cAAc,EACvCa,EAASI,EAAqBC,EAAUC,CAAQ,EAChDa,EAAkBH,EAAejB,EAAekB,EAASjB,CAAM,CAAC,EAChEoB,EAAWnB,EAAmB,OAAQF,EAAeG,EAAKF,CAAM,CAAC,EACvE,OACI,EAAAR,EAAA,IAAA,CAAC,MAAD,CACI,UAAWR,EAAAA,GAAGC,EAAAA,QAAO,KAAMW,CAAS,EACpC,MAAO,CAAE,oBAAqBuB,EAAiB,IAAKC,EAAU,GAAGT,CAAM,EACvE,GAAIjB,EAEH,UACA,CAAA,CAEb,C"}
|
|
@@ -1,56 +1,57 @@
|
|
|
1
1
|
import { cn as e } from "../../utils/cn.js";
|
|
2
2
|
import { useBreakpoint as t } from "../../hooks/use-breakpoint.js";
|
|
3
|
-
import n from "./
|
|
4
|
-
import
|
|
3
|
+
import { resolveGap as n } from "./gap.js";
|
|
4
|
+
import r from "./Layout.module.js";
|
|
5
|
+
import { jsx as i } from "react/jsx-runtime";
|
|
5
6
|
//#region src/components/Layout/Layout.tsx
|
|
6
|
-
function
|
|
7
|
-
return /* @__PURE__ */
|
|
8
|
-
className: e(
|
|
7
|
+
function a({ size: t = "lg", className: n, children: a, ...o }) {
|
|
8
|
+
return /* @__PURE__ */ i("div", {
|
|
9
|
+
className: e(r.container, r[t], n),
|
|
9
10
|
...o,
|
|
10
11
|
children: a
|
|
11
12
|
});
|
|
12
13
|
}
|
|
13
|
-
function
|
|
14
|
+
function o(e) {
|
|
14
15
|
return typeof e == "object" && !!e && ("mobile" in e || "tablet" in e || "desktop" in e);
|
|
15
16
|
}
|
|
16
|
-
function
|
|
17
|
-
if (e !== void 0) return
|
|
18
|
-
}
|
|
19
|
-
function s(e) {
|
|
20
|
-
if (e !== void 0) return typeof e == "number" ? `${e * 4}px` : e;
|
|
17
|
+
function s(e, t) {
|
|
18
|
+
if (e !== void 0) return o(e) ? t === "desktop" ? e.desktop ?? e.tablet ?? e.mobile : t === "tablet" ? e.tablet ?? e.mobile ?? e.desktop : e.mobile ?? e.tablet ?? e.desktop : e;
|
|
21
19
|
}
|
|
22
20
|
function c(e, t) {
|
|
21
|
+
return t === void 0 ? void 0 : n(e, t);
|
|
22
|
+
}
|
|
23
|
+
function l(e, t) {
|
|
23
24
|
return e ? "mobile" : t ? "tablet" : "desktop";
|
|
24
25
|
}
|
|
25
|
-
function
|
|
26
|
-
let { isMobile: g, isTablet: _ } = t(), v =
|
|
27
|
-
gap: s(
|
|
26
|
+
function u({ direction: n = "vertical", gap: a = 2, align: o, justify: u, wrap: d = !1, className: f, style: p, children: m, ...h }) {
|
|
27
|
+
let { isMobile: g, isTablet: _ } = t(), v = l(g, _), y = s(n, v) ?? "vertical", b = {
|
|
28
|
+
gap: c("Stack", s(a, v)),
|
|
28
29
|
...p
|
|
29
|
-
}, x = u === "between" ?
|
|
30
|
-
return /* @__PURE__ */
|
|
31
|
-
className: e(
|
|
30
|
+
}, x = u === "between" ? r.justifyBetween : u === "center" ? r.justifyCenter : u === "end" ? r.justifyEnd : u === "start" ? r.justifyStart : void 0;
|
|
31
|
+
return /* @__PURE__ */ i("div", {
|
|
32
|
+
className: e(r.stack, y === "vertical" ? r.vertical : r.horizontal, o && r[o], x, d && r.wrap, f),
|
|
32
33
|
style: b,
|
|
33
34
|
...h,
|
|
34
35
|
children: m
|
|
35
36
|
});
|
|
36
37
|
}
|
|
37
|
-
function
|
|
38
|
+
function d(e) {
|
|
38
39
|
if (e !== void 0) return typeof e == "number" ? `repeat(${e}, minmax(0, 1fr))` : e;
|
|
39
40
|
}
|
|
40
|
-
function
|
|
41
|
-
let { isMobile: m, isTablet: h } = t(), g =
|
|
42
|
-
return /* @__PURE__ */
|
|
43
|
-
className: e(
|
|
41
|
+
function f({ columns: n = 2, gap: a = 4, className: o, style: u, children: f, ...p }) {
|
|
42
|
+
let { isMobile: m, isTablet: h } = t(), g = l(m, h), _ = d(s(n, g)), v = c("Grid", s(a, g));
|
|
43
|
+
return /* @__PURE__ */ i("div", {
|
|
44
|
+
className: e(r.grid, o),
|
|
44
45
|
style: {
|
|
45
46
|
gridTemplateColumns: _,
|
|
46
47
|
gap: v,
|
|
47
|
-
...
|
|
48
|
+
...u
|
|
48
49
|
},
|
|
49
50
|
...p,
|
|
50
51
|
children: f
|
|
51
52
|
});
|
|
52
53
|
}
|
|
53
54
|
//#endregion
|
|
54
|
-
export {
|
|
55
|
+
export { a as Container, f as Grid, u as Stack };
|
|
55
56
|
|
|
56
57
|
//# sourceMappingURL=Layout.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Layout.js","names":["styles","Layout_module_default","Container","props","cn","size","className","isResponsiveObject","value","pickResponsive","device","
|
|
1
|
+
{"version":3,"file":"Layout.js","names":["styles","Layout_module_default","Container","props","cn","size","className","isResponsiveObject","value","pickResponsive","device","resolveOptionalGap","gap","resolveGap","component","deviceFromBreakpoint","isMobile","isTablet","Stack","useBreakpoint","finalDirection","direction","finalStyle","style","justifyClass","justify","align","wrap","resolveColumns","columns","Grid","templateColumns","finalGap"],"sources":["../../../src/components/Layout/Layout.tsx"],"sourcesContent":["import type { CSSProperties, HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { useBreakpoint } from \"@/hooks/use-breakpoint\";\nimport { resolveGap } from \"./gap\";\nimport styles from \"./Layout.module.css\";\n\nexport type ContainerSize = \"sm\" | \"md\" | \"lg\" | \"xl\" | \"full\";\n\nexport interface ContainerProps extends HTMLAttributes<HTMLDivElement> {\n size?: ContainerSize;\n}\n\n/**\n * Page-level horizontal container with a max-width preset and responsive\n * side padding (`space-4` mobile / `space-6` tablet / `space-8` desktop).\n */\nexport function Container({ size = \"lg\", className, children, ...props }: ContainerProps) {\n return (\n <div className={cn(styles.container, styles[size], className)} {...props}>\n {children}\n </div>\n );\n}\n\n/**\n * Responsive value — either a single value applied at all breakpoints, or\n * an object with `mobile` / `tablet` / `desktop` overrides. Apps can mix\n * any combination; `mobile` is the base, `tablet` / `desktop` cascade up.\n */\nexport type ResponsiveValue<T> =\n | T\n | {\n mobile?: T;\n tablet?: T;\n desktop?: T;\n };\n\nfunction isResponsiveObject<T>(value: ResponsiveValue<T>): value is {\n mobile?: T;\n tablet?: T;\n desktop?: T;\n} {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (\"mobile\" in value || \"tablet\" in value || \"desktop\" in value)\n );\n}\n\nfunction pickResponsive<T>(\n value: ResponsiveValue<T> | undefined,\n device: \"mobile\" | \"tablet\" | \"desktop\",\n): T | undefined {\n if (value === undefined) return undefined;\n if (!isResponsiveObject(value)) return value;\n if (device === \"desktop\") return value.desktop ?? value.tablet ?? value.mobile;\n if (device === \"tablet\") return value.tablet ?? value.mobile ?? value.desktop;\n return value.mobile ?? value.tablet ?? value.desktop;\n}\n\nexport type StackAlign = \"start\" | \"center\" | \"end\" | \"stretch\";\nexport type StackJustify = \"start\" | \"center\" | \"end\" | \"between\";\nexport type StackDirection = \"vertical\" | \"horizontal\";\n\nexport interface StackProps extends HTMLAttributes<HTMLDivElement> {\n /** Direction. Accepts responsive object — e.g. `{ mobile: \"vertical\", desktop: \"horizontal\" }`. */\n direction?: ResponsiveValue<StackDirection>;\n /** Gap as a CSS length. Numbers map to a multiple of the 4px scale. Accepts responsive object. A number is a step, not pixels (`6` → 24px): above `12` a development build warns once, and a string such as `\"96px\"` passes through untouched. */\n gap?: ResponsiveValue<number | string>;\n align?: StackAlign;\n justify?: StackJustify;\n wrap?: boolean;\n children?: ReactNode;\n}\n\nfunction resolveOptionalGap(\n component: string,\n gap: number | string | undefined,\n): string | undefined {\n return gap === undefined ? undefined : resolveGap(component, gap);\n}\n\nfunction deviceFromBreakpoint(\n isMobile: boolean,\n isTablet: boolean,\n): \"mobile\" | \"tablet\" | \"desktop\" {\n if (isMobile) return \"mobile\";\n if (isTablet) return \"tablet\";\n return \"desktop\";\n}\n\n/** Flex-based vertical or horizontal stack with a numeric `gap`. */\nexport function Stack({\n direction = \"vertical\",\n gap = 2,\n align,\n justify,\n wrap = false,\n className,\n style,\n children,\n ...props\n}: StackProps) {\n const { isMobile, isTablet } = useBreakpoint();\n const device = deviceFromBreakpoint(isMobile, isTablet);\n const finalDirection: StackDirection = pickResponsive(direction, device) ?? \"vertical\";\n const finalGap = resolveOptionalGap(\"Stack\", pickResponsive(gap, device));\n const finalStyle: CSSProperties = { gap: finalGap, ...style };\n const justifyClass =\n justify === \"between\"\n ? styles.justifyBetween\n : justify === \"center\"\n ? styles.justifyCenter\n : justify === \"end\"\n ? styles.justifyEnd\n : justify === \"start\"\n ? styles.justifyStart\n : undefined;\n return (\n <div\n className={cn(\n styles.stack,\n finalDirection === \"vertical\" ? styles.vertical : styles.horizontal,\n align && styles[align],\n justifyClass,\n wrap && styles.wrap,\n className,\n )}\n style={finalStyle}\n {...props}\n >\n {children}\n </div>\n );\n}\n\nexport interface GridProps extends HTMLAttributes<HTMLDivElement> {\n /** Number of columns or a custom `grid-template-columns` value. Accepts responsive object. */\n columns?: ResponsiveValue<number | string>;\n /** Gap as a CSS length. Numbers map to a multiple of the 4px scale. Accepts responsive object. A number is a step, not pixels (`6` → 24px): above `12` a development build warns once, and a string such as `\"96px\"` passes through untouched. */\n gap?: ResponsiveValue<number | string>;\n children?: ReactNode;\n}\n\nfunction resolveColumns(columns: number | string | undefined): string | undefined {\n if (columns === undefined) return undefined;\n return typeof columns === \"number\" ? `repeat(${columns}, minmax(0, 1fr))` : columns;\n}\n\n/** Simple CSS-Grid wrapper for equal-width or custom column layouts. */\nexport function Grid({ columns = 2, gap = 4, className, style, children, ...props }: GridProps) {\n const { isMobile, isTablet } = useBreakpoint();\n const device = deviceFromBreakpoint(isMobile, isTablet);\n const templateColumns = resolveColumns(pickResponsive(columns, device));\n const finalGap = resolveOptionalGap(\"Grid\", pickResponsive(gap, device));\n return (\n <div\n className={cn(styles.grid, className)}\n style={{ gridTemplateColumns: templateColumns, gap: finalGap, ...style }}\n {...props}\n >\n {children}\n </div>\n );\n}\n"],"mappings":";;;;;;AAgBA,SAAgBE,EAAU,EAAE,UAAO,MAAM,cAAW,aAAU,GAAGC,KAAyB;CACtF,OACI,kBAAC,OAAD;EAAK,WAAWC,EAAGJ,EAAO,WAAWA,EAAOK,IAAOC,CAAS;EAAG,GAAIH;EAC9D;CACA,CAAA;AAEb;AAeA,SAASI,EAAsB,GAI7B;CACE,OACI,OAAOC,KAAU,cACjBA,MACC,YAAYA,KAAS,YAAYA,KAAS,aAAaA;AAEhE;AAEA,SAASC,EACL,GACA,GACa;CACb,IAAID,MAAU,KAAA,GAId,OAHKD,EAAmBC,CAAK,IACzBE,MAAW,YAAkBF,EAAM,WAAWA,EAAM,UAAUA,EAAM,SACpEE,MAAW,WAAiBF,EAAM,UAAUA,EAAM,UAAUA,EAAM,UAC/DA,EAAM,UAAUA,EAAM,UAAUA,EAAM,UAHNA;AAI3C;AAiBA,SAASG,EACL,GACA,GACkB;CAClB,OAAOC,MAAQ,KAAA,IAAY,KAAA,IAAYC,EAAWC,GAAWF,CAAG;AACpE;AAEA,SAASG,EACL,GACA,GAC+B;CAG/B,OAFIC,IAAiB,WACjBC,IAAiB,WACd;AACX;AAGA,SAAgBC,EAAM,EAClB,eAAY,YACZ,SAAM,GACN,UACA,YACA,UAAO,IACP,cACA,UACA,aACA,GAAGf,KACQ;CACX,IAAM,EAAE,aAAU,gBAAagB,EAAc,GACvCT,IAASK,EAAqBC,GAAUC,CAAQ,GAChDG,IAAiCX,EAAeY,GAAWX,CAAM,KAAK,YAEtEY,IAA4B;EAAE,KADnBX,EAAmB,SAASF,EAAeG,GAAKF,CAAM,CAC9B;EAAU,GAAGa;CAAM,GACtDC,IACFC,MAAY,YACNzB,EAAO,iBACPyB,MAAY,WACVzB,EAAO,gBACPyB,MAAY,QACVzB,EAAO,aACPyB,MAAY,UACVzB,EAAO,eACP,KAAA;CAChB,OACI,kBAAC,OAAD;EACI,WAAWI,EACPJ,EAAO,OACPoB,MAAmB,aAAapB,EAAO,WAAWA,EAAO,YACzD0B,KAAS1B,EAAO0B,IAChBF,GACAG,KAAQ3B,EAAO,MACfM,CACJ;EACA,OAAOgB;EACP,GAAInB;EAEH;CACA,CAAA;AAEb;AAUA,SAASyB,EAAe,GAA0D;CAC9E,IAAIC,MAAY,KAAA,GAChB,OAAO,OAAOA,KAAY,WAAW,UAAUA,EAAQ,qBAAqBA;AAChF;AAGA,SAAgBC,EAAK,EAAE,aAAU,GAAG,SAAM,GAAG,cAAW,UAAO,aAAU,GAAG3B,KAAoB;CAC5F,IAAM,EAAE,aAAU,gBAAagB,EAAc,GACvCT,IAASK,EAAqBC,GAAUC,CAAQ,GAChDc,IAAkBH,EAAenB,EAAeoB,GAASnB,CAAM,CAAC,GAChEsB,IAAWrB,EAAmB,QAAQF,EAAeG,GAAKF,CAAM,CAAC;CACvE,OACI,kBAAC,OAAD;EACI,WAAWN,EAAGJ,EAAO,MAAMM,CAAS;EACpC,OAAO;GAAE,qBAAqByB;GAAiB,KAAKC;GAAU,GAAGT;EAAM;EACvE,GAAIpB;EAEH;CACA,CAAA;AAEb"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../../utils/dev-mode.cjs");var t=new Set;function n(e,n){if(t.has(n))return;t.add(n);let r=n*4,i=n%4==0?` For ${n}px use gap={${n/4}}.`:``;console.warn(`[tempest-react-sdk] <${e} gap={${n}}> renders a ${r}px gap: numeric gap counts 4px steps, not pixels.${i} For ${r}px on purpose, pass gap="${r}px".`)}function r(t,r){return typeof r==`string`?r:(r>12&&e.isDevBuild()&&n(t,r),`${r*4}px`)}exports.resolveGap=r;
|
|
2
|
+
//# sourceMappingURL=gap.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gap.cjs","names":["isDevBuild","require_dev_mode","warnedGaps","warnPixelLikeGap","gap","pixels","scaleHint","component","resolveGap"],"sources":["../../../src/components/Layout/gap.ts"],"sourcesContent":["import { isDevBuild } from \"@/utils/dev-mode\";\n\n/**\n * The largest numeric `gap` accepted without a development warning: `12`, i.e.\n * 48px on the 4px scale.\n *\n * Chosen from the SDK's own spacing, not from the issue's guess (#536 proposed\n * `> 16`). Measured on 2026-11-01:\n *\n * - the `tempest-gap-*` utility classes stop at `12` (`--tempest-space-12`);\n * - no stylesheet in `src/` sets `gap` above `--tempest-space-12`;\n * - every literal numeric `gap` in `src/`, `docs/`, `examples/gallery/src`,\n * `template/` and `template-pwa/` is `8` or lower (`grep -rhoE \"gap=\\{[^}]*\\}+\"`\n * over those trees: 125 uses, highest value `8`).\n *\n * So nothing the SDK ships or documents crosses it, while the pixel-looking\n * values that do — `16`, `20`, `24`, the gaps a CSS reader writes by reflex —\n * are exactly the ones the issue reported (`gap={24}` became 96px). `> 16`\n * would have let `gap={16}`, the most common pixel gap there is, through as a\n * silent 64px.\n */\nexport const GAP_SCALE_MAX = 12;\n\nconst warnedGaps = new Set<number>();\n\n/**\n * Warn once per value that a numeric `gap` above {@link GAP_SCALE_MAX} was\n * probably meant as pixels.\n *\n * Once per value, because layout components re-render constantly and a warning\n * per render would bury the console; the set is never cleared, since a literal\n * in the source does not change within the life of the page. Keyed by value and\n * not by component: the mistake is the number, wherever it is written.\n *\n * @param component - The component whose prop carried the value, for the message.\n * @param gap - The numeric gap as written.\n */\nfunction warnPixelLikeGap(component: string, gap: number): void {\n if (warnedGaps.has(gap)) return;\n warnedGaps.add(gap);\n const pixels = gap * 4;\n const scaleHint = gap % 4 === 0 ? ` For ${gap}px use gap={${gap / 4}}.` : \"\";\n console.warn(\n `[tempest-react-sdk] <${component} gap={${gap}}> renders a ${pixels}px gap: ` +\n `numeric gap counts 4px steps, not pixels.${scaleHint} ` +\n `For ${pixels}px on purpose, pass gap=\"${pixels}px\".`,\n );\n}\n\n/**\n * Turn a `gap` prop into the CSS `gap` value shared by `Stack`, `Grid`, `Form`,\n * `FormSection`, `FormRow` and `FormActions`.\n *\n * A number is a step on the 4px scale (`6` → `24px`); a string passes through as\n * any CSS length. The number reads as pixels to anyone coming from CSS or from\n * React's `style={{ gap: 24 }}`, so in a development build a value above\n * {@link GAP_SCALE_MAX} logs a one-time warning naming both readings. Production\n * builds stay silent: the gate is `isDevBuild()`, the SDK's single answer to\n * that question.\n *\n * @param component - The component name, used only in the warning.\n * @param gap - The prop value, a scale step or a CSS length.\n * @returns The CSS `gap` value.\n */\nexport function resolveGap(component: string, gap: number | string): string {\n if (typeof gap === \"string\") return gap;\n if (gap > GAP_SCALE_MAX && isDevBuild()) warnPixelLikeGap(component, gap);\n return `${gap * 4}px`;\n}\n"],"mappings":"A,4CAuBA,IAAME,EAAa,IAAI,IAcvB,SAASC,EAAiB,EAAmB,EAAmB,CAC5D,GAAID,EAAW,IAAIE,CAAG,EAAG,OACzBF,EAAW,IAAIE,CAAG,EAClB,IAAMC,EAASD,EAAM,EACfE,EAAYF,EAAM,GAAM,EAAI,QAAQA,EAAI,cAAcA,EAAM,EAAE,IAAM,GAC1E,QAAQ,KACJ,wBAAwBG,EAAU,QAAQH,EAAI,eAAeC,EAAO,mDACpBC,EAAU,OAC/CD,EAAO,2BAA2BA,EAAO,KACxD,CACJ,CAiBA,SAAgBG,EAAW,EAAmB,EAA8B,CAGxE,OAFI,OAAOJ,GAAQ,SAAiBA,GAChCA,EAAA,IAAuBJ,EAAAA,WAAW,GAAGG,EAAiBI,EAAWH,CAAG,EACjE,GAAGA,EAAM,EAAE,IACtB,C"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { isDevBuild as e } from "../../utils/dev-mode.js";
|
|
2
|
+
var t = /* @__PURE__ */ new Set();
|
|
3
|
+
function n(e, n) {
|
|
4
|
+
if (t.has(n)) return;
|
|
5
|
+
t.add(n);
|
|
6
|
+
let r = n * 4, i = n % 4 == 0 ? ` For ${n}px use gap={${n / 4}}.` : "";
|
|
7
|
+
console.warn(`[tempest-react-sdk] <${e} gap={${n}}> renders a ${r}px gap: numeric gap counts 4px steps, not pixels.${i} For ${r}px on purpose, pass gap="${r}px".`);
|
|
8
|
+
}
|
|
9
|
+
function r(t, r) {
|
|
10
|
+
return typeof r == "string" ? r : (r > 12 && e() && n(t, r), `${r * 4}px`);
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { r as resolveGap };
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=gap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gap.js","names":["warnedGaps","warnPixelLikeGap","gap","pixels","scaleHint","component","resolveGap","isDevBuild"],"sources":["../../../src/components/Layout/gap.ts"],"sourcesContent":["import { isDevBuild } from \"@/utils/dev-mode\";\n\n/**\n * The largest numeric `gap` accepted without a development warning: `12`, i.e.\n * 48px on the 4px scale.\n *\n * Chosen from the SDK's own spacing, not from the issue's guess (#536 proposed\n * `> 16`). Measured on 2026-11-01:\n *\n * - the `tempest-gap-*` utility classes stop at `12` (`--tempest-space-12`);\n * - no stylesheet in `src/` sets `gap` above `--tempest-space-12`;\n * - every literal numeric `gap` in `src/`, `docs/`, `examples/gallery/src`,\n * `template/` and `template-pwa/` is `8` or lower (`grep -rhoE \"gap=\\{[^}]*\\}+\"`\n * over those trees: 125 uses, highest value `8`).\n *\n * So nothing the SDK ships or documents crosses it, while the pixel-looking\n * values that do — `16`, `20`, `24`, the gaps a CSS reader writes by reflex —\n * are exactly the ones the issue reported (`gap={24}` became 96px). `> 16`\n * would have let `gap={16}`, the most common pixel gap there is, through as a\n * silent 64px.\n */\nexport const GAP_SCALE_MAX = 12;\n\nconst warnedGaps = new Set<number>();\n\n/**\n * Warn once per value that a numeric `gap` above {@link GAP_SCALE_MAX} was\n * probably meant as pixels.\n *\n * Once per value, because layout components re-render constantly and a warning\n * per render would bury the console; the set is never cleared, since a literal\n * in the source does not change within the life of the page. Keyed by value and\n * not by component: the mistake is the number, wherever it is written.\n *\n * @param component - The component whose prop carried the value, for the message.\n * @param gap - The numeric gap as written.\n */\nfunction warnPixelLikeGap(component: string, gap: number): void {\n if (warnedGaps.has(gap)) return;\n warnedGaps.add(gap);\n const pixels = gap * 4;\n const scaleHint = gap % 4 === 0 ? ` For ${gap}px use gap={${gap / 4}}.` : \"\";\n console.warn(\n `[tempest-react-sdk] <${component} gap={${gap}}> renders a ${pixels}px gap: ` +\n `numeric gap counts 4px steps, not pixels.${scaleHint} ` +\n `For ${pixels}px on purpose, pass gap=\"${pixels}px\".`,\n );\n}\n\n/**\n * Turn a `gap` prop into the CSS `gap` value shared by `Stack`, `Grid`, `Form`,\n * `FormSection`, `FormRow` and `FormActions`.\n *\n * A number is a step on the 4px scale (`6` → `24px`); a string passes through as\n * any CSS length. The number reads as pixels to anyone coming from CSS or from\n * React's `style={{ gap: 24 }}`, so in a development build a value above\n * {@link GAP_SCALE_MAX} logs a one-time warning naming both readings. Production\n * builds stay silent: the gate is `isDevBuild()`, the SDK's single answer to\n * that question.\n *\n * @param component - The component name, used only in the warning.\n * @param gap - The prop value, a scale step or a CSS length.\n * @returns The CSS `gap` value.\n */\nexport function resolveGap(component: string, gap: number | string): string {\n if (typeof gap === \"string\") return gap;\n if (gap > GAP_SCALE_MAX && isDevBuild()) warnPixelLikeGap(component, gap);\n return `${gap * 4}px`;\n}\n"],"mappings":";AAuBA,IAAMA,oBAAa,IAAI,IAAY;AAcnC,SAASC,EAAiB,GAAmB,GAAmB;CAC5D,IAAID,EAAW,IAAIE,CAAG,GAAG;CACzB,EAAW,IAAIA,CAAG;CAClB,IAAMC,IAASD,IAAM,GACfE,IAAYF,IAAM,KAAM,IAAI,QAAQA,EAAI,cAAcA,IAAM,EAAE,MAAM;CAC1E,QAAQ,KACJ,wBAAwBG,EAAU,QAAQH,EAAI,eAAeC,EAAO,mDACpBC,EAAU,OAC/CD,EAAO,2BAA2BA,EAAO,KACxD;AACJ;AAiBA,SAAgBG,EAAW,GAAmB,GAA8B;CAGxE,OAFI,OAAOJ,KAAQ,WAAiBA,KAChCA,IAAA,MAAuBK,EAAW,KAAGN,EAAiBI,GAAWH,CAAG,GACjE,GAAGA,IAAM,EAAE;AACtB"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../utils/cn.cjs"),t=require("../Portal/escape-layer.cjs"),
|
|
1
|
+
const e=require("../../utils/cn.cjs"),t=require("../../utils/safe-url.cjs"),n=require("../Portal/escape-layer.cjs"),r=require("../Portal/Portal.cjs"),i=require("../Portal/anchor-position.cjs"),a=require("../Portal/tab-bridge.cjs"),o=require("./NavigationMenu.module.cjs");let s=require("react"),c=require("react/jsx-runtime");var l=4;function u({items:u,portal:d=!0,className:f,...p}){let[m,h]=(0,s.useState)(-1),[g,_]=(0,s.useState)(-1),v=(0,s.useId)(),y=(0,s.useRef)(null),b=(0,s.useCallback)(()=>{h(-1),_(-1)},[]),x=m===-1?g:m,[S,C]=(0,s.useState)(null),[w,T]=(0,s.useState)(null),E=d&&x!==-1,D=i.useAnchorPosition({anchor:S,floating:w,side:`bottom`,align:`start`,offset:l,enabled:E});a.usePortalTabOrder({enabled:E,anchor:S,panel:w}),n.useEscapeLayer(x!==-1,b),(0,s.useEffect)(()=>{if(x===-1)return;let e=e=>{let t=e.target;y.current?.contains(t)||w?.contains(t)||b()};return window.addEventListener(`mousedown`,e),()=>{window.removeEventListener(`mousedown`,e)}},[x,b,w]);let O=(e,n)=>{let r=()=>{e.onSelect?.(),b()},i=t.vettedHref(e.href);return i?(0,c.jsx)(`a`,{href:i,role:n===`menuitem`?`menuitem`:void 0,className:o.default.link,onClick:r,children:e.label}):(0,c.jsx)(`button`,{type:`button`,role:n===`menuitem`?`menuitem`:void 0,className:o.default.link,onClick:r,children:e.label})};return(0,c.jsx)(`nav`,{ref:y,className:e.cn(o.default.root,f),...p,children:(0,c.jsx)(`ul`,{className:o.default.list,children:u.map((t,n)=>{if(!(t.children&&t.children.length>0))return(0,c.jsx)(`li`,{className:o.default.item,children:O(t,`none`)},n);let i=x===n,a=`${v}-panel-${n}`,s=(0,c.jsx)(`ul`,{ref:T,id:a,role:`menu`,className:e.cn(o.default.panel,d&&o.default.portalled),style:D,children:t.children.map((e,t)=>(0,c.jsx)(`li`,{role:`none`,className:o.default.subItem,children:O(e,`menuitem`)},t))});return(0,c.jsxs)(`li`,{ref:i?C:void 0,className:o.default.item,onMouseEnter:()=>_(n),onMouseLeave:()=>_(e=>e===n?-1:e),children:[(0,c.jsx)(`button`,{type:`button`,className:o.default.trigger,"aria-haspopup":`menu`,"aria-expanded":i,"aria-controls":a,onClick:()=>h(e=>e===n?-1:n),onFocus:()=>_(n),children:t.label}),i&&(d?(0,c.jsx)(r.Portal,{children:s}):s)]},n)})})})}exports.NavigationMenu=u;
|
|
2
2
|
//# sourceMappingURL=NavigationMenu.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NavigationMenu.cjs","names":["useState","useId","useRef","useCallback","useAnchorPosition","styles","cn","Portal","require_cn","require_escape_layer","require_Portal","require_anchor_position","require_tab_bridge","require_NavigationMenu_module","react","react_jsx_runtime","PANEL_OFFSET","NavigationMenu","props","clickedIndex","setClickedIndex","hovered","setHovered","baseId","rootRef","close","openIndex","anchorNode","setAnchorNode","panelNode","setPanelNode","floating","portal","floatingStyle","onDown","target","event","renderLeaf","handleClick","item","role","className","items","index","open","panelId","panel","child","childIndex","prev"],"sources":["../../../src/components/NavigationMenu/NavigationMenu.tsx"],"sourcesContent":["/**\n * @tempest-limits function-lines — the body resolves the active item from the\n * current path, owns the open submenu and the hover-intent delay, all of which\n * decide the same highlighted row.\n */\nimport { useCallback, useEffect, useId, useRef, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { Portal } from \"@/components/Portal\";\nimport { useAnchorPosition } from \"@/components/Portal/anchor-position\";\nimport { useEscapeLayer } from \"@/components/Portal/escape-layer\";\nimport { usePortalTabOrder } from \"@/components/Portal/tab-bridge\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./NavigationMenu.module.css\";\n\nexport interface NavigationMenuItem {\n /** Visible label for the entry. */\n label: ReactNode;\n /** When set, the entry renders as an anchor. */\n href?: string;\n /** Invoked when the entry is activated (click / Enter). */\n onSelect?: () => void;\n /** Nested entries — presence turns the item into a submenu trigger. */\n children?: NavigationMenuItem[];\n}\n\nexport interface NavigationMenuProps extends HTMLAttributes<HTMLElement> {\n /** Top-level navigation entries. */\n items: NavigationMenuItem[];\n /**\n * Render the open panel in a portal, positioned under its trigger. Default\n * `true`.\n *\n * In flow, an ancestor with `overflow` other than `visible` clips the panel,\n * and the SDK's own `Navbar` is one: its `nav` slot scrolls horizontally, and\n * `overflow-x: auto` forces `overflow-y` to `auto` too — a submenu showed\n * 0% of its panel there (measured in Chrome at 1440 px). In a portal the\n * panel escapes the clip, flips at the viewport edge, stays in the tab order\n * right after its trigger, and follows it on scroll and resize.\n */\n portal?: boolean;\n}\n\n/** Gap between trigger and panel, in px — the in-flow CSS uses the same 4 px. */\nconst PANEL_OFFSET = 4;\n\n/**\n * Horizontal navigation menu with hover/click/focus dropdown submenus.\n *\n * - Top-level items render in a `<nav><ul>`.\n * - Items with `children` open a submenu panel (`role=\"menu\"`) on hover, focus,\n * or click. Only one panel is open at a time.\n * - Closes on outside click, Escape, or selecting a leaf entry.\n *\n * @example\n * <NavigationMenu\n * items={[\n * { label: \"Home\", href: \"/\" },\n * {\n * label: \"Products\",\n * children: [\n * { label: \"Analytics\", href: \"/analytics\" },\n * { label: \"Billing\", onSelect: () => openBilling() },\n * ],\n * },\n * ]}\n * />\n */\nexport function NavigationMenu({ items, portal = true, className, ...props }: NavigationMenuProps) {\n const [clickedIndex, setClickedIndex] = useState<number>(-1);\n const [hovered, setHovered] = useState<number>(-1);\n const baseId = useId();\n const rootRef = useRef<HTMLElement>(null);\n\n const close = useCallback((): void => {\n setClickedIndex(-1);\n setHovered(-1);\n }, []);\n\n const openIndex = clickedIndex !== -1 ? clickedIndex : hovered;\n\n const [anchorNode, setAnchorNode] = useState<HTMLLIElement | null>(null);\n const [panelNode, setPanelNode] = useState<HTMLUListElement | null>(null);\n const floating = portal && openIndex !== -1;\n const floatingStyle = useAnchorPosition({\n anchor: anchorNode,\n floating: panelNode,\n side: \"bottom\",\n align: \"start\",\n offset: PANEL_OFFSET,\n enabled: floating,\n });\n usePortalTabOrder({ enabled: floating, anchor: anchorNode, panel: panelNode });\n\n useEscapeLayer(openIndex !== -1, close);\n\n /**\n * Close on a press outside both the menu and the open panel.\n *\n * The panel is checked on its own because in a portal it is not inside the\n * menu's root: a press on one of its entries would otherwise count as\n * outside, close the panel on `mousedown`, and swallow the entry's click.\n */\n useEffect(() => {\n if (openIndex === -1) return;\n const onDown = (event: MouseEvent): void => {\n const target = event.target as Node;\n if (rootRef.current?.contains(target) || panelNode?.contains(target)) return;\n close();\n };\n window.addEventListener(\"mousedown\", onDown);\n return () => {\n window.removeEventListener(\"mousedown\", onDown);\n };\n }, [openIndex, close, panelNode]);\n\n const renderLeaf = (item: NavigationMenuItem, role: \"none\" | \"menuitem\"): ReactNode => {\n const handleClick = (): void => {\n item.onSelect?.();\n close();\n };\n if (item.href) {\n return (\n <a\n href={item.href}\n role={role === \"menuitem\" ? \"menuitem\" : undefined}\n className={styles.link}\n onClick={handleClick}\n >\n {item.label}\n </a>\n );\n }\n return (\n <button\n type=\"button\"\n role={role === \"menuitem\" ? \"menuitem\" : undefined}\n className={styles.link}\n onClick={handleClick}\n >\n {item.label}\n </button>\n );\n };\n\n return (\n <nav ref={rootRef} className={cn(styles.root, className)} {...props}>\n <ul className={styles.list}>\n {items.map((item, index) => {\n const hasChildren = !!item.children && item.children.length > 0;\n if (!hasChildren) {\n return (\n <li key={index} className={styles.item}>\n {renderLeaf(item, \"none\")}\n </li>\n );\n }\n const open = openIndex === index;\n const panelId = `${baseId}-panel-${index}`;\n const panel = (\n <ul\n ref={setPanelNode}\n id={panelId}\n role=\"menu\"\n className={cn(styles.panel, portal && styles.portalled)}\n style={floatingStyle}\n >\n {item.children!.map((child, childIndex) => (\n <li key={childIndex} role=\"none\" className={styles.subItem}>\n {renderLeaf(child, \"menuitem\")}\n </li>\n ))}\n </ul>\n );\n return (\n <li\n key={index}\n ref={open ? setAnchorNode : undefined}\n className={styles.item}\n onMouseEnter={() => setHovered(index)}\n onMouseLeave={() => setHovered((prev) => (prev === index ? -1 : prev))}\n >\n <button\n type=\"button\"\n className={styles.trigger}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n aria-controls={panelId}\n onClick={() =>\n setClickedIndex((prev) => (prev === index ? -1 : index))\n }\n onFocus={() => setHovered(index)}\n >\n {item.label}\n </button>\n {open && (portal ? <Portal>{panel}</Portal> : panel)}\n </li>\n );\n })}\n </ul>\n </nav>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"NavigationMenu.cjs","names":["useState","useId","useRef","useCallback","useAnchorPosition","vettedHref","styles","cn","Portal","require_cn","require_safe_url","require_escape_layer","require_Portal","require_anchor_position","require_tab_bridge","require_NavigationMenu_module","react","react_jsx_runtime","PANEL_OFFSET","NavigationMenu","props","clickedIndex","setClickedIndex","hovered","setHovered","baseId","rootRef","close","openIndex","anchorNode","setAnchorNode","panelNode","setPanelNode","floating","portal","floatingStyle","onDown","target","event","renderLeaf","handleClick","item","href","role","className","items","index","open","panelId","panel","child","childIndex","prev"],"sources":["../../../src/components/NavigationMenu/NavigationMenu.tsx"],"sourcesContent":["/**\n * @tempest-limits function-lines — the body resolves the active item from the\n * current path, owns the open submenu and the hover-intent delay, all of which\n * decide the same highlighted row.\n */\nimport { useCallback, useEffect, useId, useRef, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { Portal } from \"@/components/Portal\";\nimport { useAnchorPosition } from \"@/components/Portal/anchor-position\";\nimport { useEscapeLayer } from \"@/components/Portal/escape-layer\";\nimport { usePortalTabOrder } from \"@/components/Portal/tab-bridge\";\nimport { cn } from \"@/utils/cn\";\nimport { vettedHref } from \"@/utils/safe-url\";\nimport styles from \"./NavigationMenu.module.css\";\n\nexport interface NavigationMenuItem {\n /** Visible label for the entry. */\n label: ReactNode;\n /**\n * When set, the entry renders as an anchor. The URL is checked against\n * `safeLinkUrl`'s allowlist: a scheme outside `http:`/`https:`/`mailto:`/`tel:`/`sms:`\n * renders the entry as a button instead, so a menu built from API data cannot\n * put a clickable `javascript:` link on the page under React 18.\n */\n href?: string;\n /** Invoked when the entry is activated (click / Enter). */\n onSelect?: () => void;\n /** Nested entries — presence turns the item into a submenu trigger. */\n children?: NavigationMenuItem[];\n}\n\nexport interface NavigationMenuProps extends HTMLAttributes<HTMLElement> {\n /** Top-level navigation entries. */\n items: NavigationMenuItem[];\n /**\n * Render the open panel in a portal, positioned under its trigger. Default\n * `true`.\n *\n * In flow, an ancestor with `overflow` other than `visible` clips the panel,\n * and the SDK's own `Navbar` is one: its `nav` slot scrolls horizontally, and\n * `overflow-x: auto` forces `overflow-y` to `auto` too — a submenu showed\n * 0% of its panel there (measured in Chrome at 1440 px). In a portal the\n * panel escapes the clip, flips at the viewport edge, stays in the tab order\n * right after its trigger, and follows it on scroll and resize.\n */\n portal?: boolean;\n}\n\n/** Gap between trigger and panel, in px — the in-flow CSS uses the same 4 px. */\nconst PANEL_OFFSET = 4;\n\n/**\n * Horizontal navigation menu with hover/click/focus dropdown submenus.\n *\n * - Top-level items render in a `<nav><ul>`.\n * - Items with `children` open a submenu panel (`role=\"menu\"`) on hover, focus,\n * or click. Only one panel is open at a time.\n * - Closes on outside click, Escape, or selecting a leaf entry.\n *\n * @example\n * <NavigationMenu\n * items={[\n * { label: \"Home\", href: \"/\" },\n * {\n * label: \"Products\",\n * children: [\n * { label: \"Analytics\", href: \"/analytics\" },\n * { label: \"Billing\", onSelect: () => openBilling() },\n * ],\n * },\n * ]}\n * />\n */\nexport function NavigationMenu({ items, portal = true, className, ...props }: NavigationMenuProps) {\n const [clickedIndex, setClickedIndex] = useState<number>(-1);\n const [hovered, setHovered] = useState<number>(-1);\n const baseId = useId();\n const rootRef = useRef<HTMLElement>(null);\n\n const close = useCallback((): void => {\n setClickedIndex(-1);\n setHovered(-1);\n }, []);\n\n const openIndex = clickedIndex !== -1 ? clickedIndex : hovered;\n\n const [anchorNode, setAnchorNode] = useState<HTMLLIElement | null>(null);\n const [panelNode, setPanelNode] = useState<HTMLUListElement | null>(null);\n const floating = portal && openIndex !== -1;\n const floatingStyle = useAnchorPosition({\n anchor: anchorNode,\n floating: panelNode,\n side: \"bottom\",\n align: \"start\",\n offset: PANEL_OFFSET,\n enabled: floating,\n });\n usePortalTabOrder({ enabled: floating, anchor: anchorNode, panel: panelNode });\n\n useEscapeLayer(openIndex !== -1, close);\n\n /**\n * Close on a press outside both the menu and the open panel.\n *\n * The panel is checked on its own because in a portal it is not inside the\n * menu's root: a press on one of its entries would otherwise count as\n * outside, close the panel on `mousedown`, and swallow the entry's click.\n */\n useEffect(() => {\n if (openIndex === -1) return;\n const onDown = (event: MouseEvent): void => {\n const target = event.target as Node;\n if (rootRef.current?.contains(target) || panelNode?.contains(target)) return;\n close();\n };\n window.addEventListener(\"mousedown\", onDown);\n return () => {\n window.removeEventListener(\"mousedown\", onDown);\n };\n }, [openIndex, close, panelNode]);\n\n const renderLeaf = (item: NavigationMenuItem, role: \"none\" | \"menuitem\"): ReactNode => {\n const handleClick = (): void => {\n item.onSelect?.();\n close();\n };\n const href = vettedHref(item.href);\n if (href) {\n return (\n <a\n href={href}\n role={role === \"menuitem\" ? \"menuitem\" : undefined}\n className={styles.link}\n onClick={handleClick}\n >\n {item.label}\n </a>\n );\n }\n return (\n <button\n type=\"button\"\n role={role === \"menuitem\" ? \"menuitem\" : undefined}\n className={styles.link}\n onClick={handleClick}\n >\n {item.label}\n </button>\n );\n };\n\n return (\n <nav ref={rootRef} className={cn(styles.root, className)} {...props}>\n <ul className={styles.list}>\n {items.map((item, index) => {\n const hasChildren = !!item.children && item.children.length > 0;\n if (!hasChildren) {\n return (\n <li key={index} className={styles.item}>\n {renderLeaf(item, \"none\")}\n </li>\n );\n }\n const open = openIndex === index;\n const panelId = `${baseId}-panel-${index}`;\n const panel = (\n <ul\n ref={setPanelNode}\n id={panelId}\n role=\"menu\"\n className={cn(styles.panel, portal && styles.portalled)}\n style={floatingStyle}\n >\n {item.children!.map((child, childIndex) => (\n <li key={childIndex} role=\"none\" className={styles.subItem}>\n {renderLeaf(child, \"menuitem\")}\n </li>\n ))}\n </ul>\n );\n return (\n <li\n key={index}\n ref={open ? setAnchorNode : undefined}\n className={styles.item}\n onMouseEnter={() => setHovered(index)}\n onMouseLeave={() => setHovered((prev) => (prev === index ? -1 : prev))}\n >\n <button\n type=\"button\"\n className={styles.trigger}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n aria-controls={panelId}\n onClick={() =>\n setClickedIndex((prev) => (prev === index ? -1 : index))\n }\n onFocus={() => setHovered(index)}\n >\n {item.label}\n </button>\n {open && (portal ? <Portal>{panel}</Portal> : panel)}\n </li>\n );\n })}\n </ul>\n </nav>\n );\n}\n"],"mappings":"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"}
|