@kodffe/ui 0.0.0-stage → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_virtual/_rolldown/runtime.js +11 -0
- package/dist/accordion/accordion.d.ts +44 -0
- package/dist/accordion/accordion.js +84 -0
- package/dist/accordion/accordion.js.map +1 -0
- package/dist/alert/alert.d.ts +17 -0
- package/dist/alert/alert.js +59 -0
- package/dist/alert/alert.js.map +1 -0
- package/dist/app-shell/app-footer.d.ts +29 -0
- package/dist/app-shell/app-footer.js +39 -0
- package/dist/app-shell/app-footer.js.map +1 -0
- package/dist/app-shell/app-shell-context.d.ts +10 -0
- package/dist/app-shell/app-shell-context.js +12 -0
- package/dist/app-shell/app-shell-context.js.map +1 -0
- package/dist/app-shell/app-shell.d.ts +42 -0
- package/dist/app-shell/app-shell.js +191 -0
- package/dist/app-shell/app-shell.js.map +1 -0
- package/dist/app-shell/floating-actions.d.ts +19 -0
- package/dist/app-shell/floating-actions.js +59 -0
- package/dist/app-shell/floating-actions.js.map +1 -0
- package/dist/app-shell/workspace-switcher.d.ts +25 -0
- package/dist/app-shell/workspace-switcher.js +82 -0
- package/dist/app-shell/workspace-switcher.js.map +1 -0
- package/dist/badge/badge.d.ts +19 -0
- package/dist/badge/badge.js +59 -0
- package/dist/badge/badge.js.map +1 -0
- package/dist/banner/banner.d.ts +22 -0
- package/dist/banner/banner.js +80 -0
- package/dist/banner/banner.js.map +1 -0
- package/dist/breadcrumbs/breadcrumbs.d.ts +18 -0
- package/dist/breadcrumbs/breadcrumbs.js +45 -0
- package/dist/breadcrumbs/breadcrumbs.js.map +1 -0
- package/dist/button/button.d.ts +38 -0
- package/dist/button/button.js +83 -0
- package/dist/button/button.js.map +1 -0
- package/dist/card/card.d.ts +159 -0
- package/dist/card/card.js +390 -0
- package/dist/card/card.js.map +1 -0
- package/dist/card/insight-card.d.ts +19 -0
- package/dist/card/insight-card.js +51 -0
- package/dist/card/insight-card.js.map +1 -0
- package/dist/card/integration-card.d.ts +19 -0
- package/dist/card/integration-card.js +70 -0
- package/dist/card/integration-card.js.map +1 -0
- package/dist/charts/charts.d.ts +75 -0
- package/dist/charts/charts.js +453 -0
- package/dist/charts/charts.js.map +1 -0
- package/dist/choice/choice.d.ts +39 -0
- package/dist/choice/choice.js +143 -0
- package/dist/choice/choice.js.map +1 -0
- package/dist/combobox/combobox.d.ts +46 -0
- package/dist/combobox/combobox.js +196 -0
- package/dist/combobox/combobox.js.map +1 -0
- package/dist/command-palette/command-palette.d.ts +35 -0
- package/dist/command-palette/command-palette.js +164 -0
- package/dist/command-palette/command-palette.js.map +1 -0
- package/dist/confirm-dialog/confirm-dialog.d.ts +37 -0
- package/dist/confirm-dialog/confirm-dialog.js +106 -0
- package/dist/confirm-dialog/confirm-dialog.js.map +1 -0
- package/dist/content/assistant.d.ts +86 -0
- package/dist/content/assistant.js +213 -0
- package/dist/content/assistant.js.map +1 -0
- package/dist/content/content.d.ts +45 -0
- package/dist/content/content.js +136 -0
- package/dist/content/content.js.map +1 -0
- package/dist/data-display/avatar-sizes.d.ts +8 -0
- package/dist/data-display/avatar-sizes.js +12 -0
- package/dist/data-display/avatar-sizes.js.map +1 -0
- package/dist/data-display/avatar.d.ts +21 -0
- package/dist/data-display/avatar.js +53 -0
- package/dist/data-display/avatar.js.map +1 -0
- package/dist/data-display/chip.d.ts +20 -0
- package/dist/data-display/chip.js +64 -0
- package/dist/data-display/chip.js.map +1 -0
- package/dist/data-display/code-block.d.ts +14 -0
- package/dist/data-display/code-block.js +50 -0
- package/dist/data-display/code-block.js.map +1 -0
- package/dist/data-display/data-display.d.ts +73 -0
- package/dist/data-display/data-display.js +227 -0
- package/dist/data-display/data-display.js.map +1 -0
- package/dist/data-display/insights.d.ts +120 -0
- package/dist/data-display/insights.js +220 -0
- package/dist/data-display/insights.js.map +1 -0
- package/dist/data-table/cells.d.ts +86 -0
- package/dist/data-table/cells.js +163 -0
- package/dist/data-table/cells.js.map +1 -0
- package/dist/data-table/data-table.d.ts +112 -0
- package/dist/data-table/data-table.js +558 -0
- package/dist/data-table/data-table.js.map +1 -0
- package/dist/date-picker/date-picker.d.ts +61 -0
- package/dist/date-picker/date-picker.js +309 -0
- package/dist/date-picker/date-picker.js.map +1 -0
- package/dist/drawer/drawer.d.ts +32 -0
- package/dist/drawer/drawer.js +72 -0
- package/dist/drawer/drawer.js.map +1 -0
- package/dist/empty-state/empty-state.d.ts +25 -0
- package/dist/empty-state/empty-state.js +78 -0
- package/dist/empty-state/empty-state.js.map +1 -0
- package/dist/error-state/error-state.d.ts +19 -0
- package/dist/error-state/error-state.js +56 -0
- package/dist/error-state/error-state.js.map +1 -0
- package/dist/event-calendar/calendar-logic.test.d.ts +1 -0
- package/dist/event-calendar/dates.d.ts +35 -0
- package/dist/event-calendar/dates.js +55 -0
- package/dist/event-calendar/dates.js.map +1 -0
- package/dist/event-calendar/event-calendar.d.ts +125 -0
- package/dist/event-calendar/event-calendar.js +795 -0
- package/dist/event-calendar/event-calendar.js.map +1 -0
- package/dist/event-calendar/event-details.d.ts +50 -0
- package/dist/event-calendar/event-details.js +124 -0
- package/dist/event-calendar/event-details.js.map +1 -0
- package/dist/event-calendar/layout.d.ts +48 -0
- package/dist/event-calendar/layout.js +55 -0
- package/dist/event-calendar/layout.js.map +1 -0
- package/dist/file-upload/file-upload.d.ts +70 -0
- package/dist/file-upload/file-upload.js +307 -0
- package/dist/file-upload/file-upload.js.map +1 -0
- package/dist/filters/filters.d.ts +48 -0
- package/dist/filters/filters.js +103 -0
- package/dist/filters/filters.js.map +1 -0
- package/dist/form-field/form-field.d.ts +39 -0
- package/dist/form-field/form-field.js +88 -0
- package/dist/form-field/form-field.js.map +1 -0
- package/dist/hover-card/hover-card.d.ts +15 -0
- package/dist/hover-card/hover-card.js +27 -0
- package/dist/hover-card/hover-card.js.map +1 -0
- package/dist/hover-card/person-hover-card.d.ts +26 -0
- package/dist/hover-card/person-hover-card.js +85 -0
- package/dist/hover-card/person-hover-card.js.map +1 -0
- package/dist/icon/icon-tile.d.ts +20 -0
- package/dist/icon/icon-tile.js +55 -0
- package/dist/icon/icon-tile.js.map +1 -0
- package/dist/icon/icon.d.ts +14 -0
- package/dist/icon/icon.js +28 -0
- package/dist/icon/icon.js.map +1 -0
- package/dist/icon/icons.generated.d.ts +358 -0
- package/dist/icon/icons.generated.js +362 -0
- package/dist/icon/icons.generated.js.map +1 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.js +88 -0
- package/dist/input/extra-inputs.d.ts +122 -0
- package/dist/input/extra-inputs.js +351 -0
- package/dist/input/extra-inputs.js.map +1 -0
- package/dist/input/field-styles.d.ts +5 -0
- package/dist/input/field-styles.js +14 -0
- package/dist/input/field-styles.js.map +1 -0
- package/dist/input/input.d.ts +50 -0
- package/dist/input/input.js +107 -0
- package/dist/input/input.js.map +1 -0
- package/dist/layout/layout.d.ts +41 -0
- package/dist/layout/layout.js +90 -0
- package/dist/layout/layout.js.map +1 -0
- package/dist/lib/control-styles.d.ts +9 -0
- package/dist/lib/control-styles.js +16 -0
- package/dist/lib/control-styles.js.map +1 -0
- package/dist/lib/cx.d.ts +2 -0
- package/dist/lib/cx.js +8 -0
- package/dist/lib/cx.js.map +1 -0
- package/dist/lib/glyphs.d.ts +13 -0
- package/dist/lib/glyphs.js +67 -0
- package/dist/lib/glyphs.js.map +1 -0
- package/dist/lib/segmented-styles.d.ts +8 -0
- package/dist/lib/segmented-styles.js +12 -0
- package/dist/lib/segmented-styles.js.map +1 -0
- package/dist/lib/select-on-arrow.d.ts +7 -0
- package/dist/lib/select-on-arrow.js +21 -0
- package/dist/lib/select-on-arrow.js.map +1 -0
- package/dist/lib/skip-link.d.ts +5 -0
- package/dist/lib/skip-link.js +13 -0
- package/dist/lib/skip-link.js.map +1 -0
- package/dist/lib/use-media-query.d.ts +4 -0
- package/dist/lib/use-media-query.js +15 -0
- package/dist/lib/use-media-query.js.map +1 -0
- package/dist/lists/lists.d.ts +76 -0
- package/dist/lists/lists.js +133 -0
- package/dist/lists/lists.js.map +1 -0
- package/dist/locale-switch/locale-switch.d.ts +29 -0
- package/dist/locale-switch/locale-switch.js +62 -0
- package/dist/locale-switch/locale-switch.js.map +1 -0
- package/dist/logo/logo.d.ts +31 -0
- package/dist/logo/logo.js +157 -0
- package/dist/logo/logo.js.map +1 -0
- package/dist/logo/product-logo.d.ts +19 -0
- package/dist/logo/product-logo.js +46 -0
- package/dist/logo/product-logo.js.map +1 -0
- package/dist/marketing/marketing.d.ts +176 -0
- package/dist/marketing/marketing.js +451 -0
- package/dist/marketing/marketing.js.map +1 -0
- package/dist/marketing/newsletter-form.d.ts +21 -0
- package/dist/marketing/newsletter-form.js +100 -0
- package/dist/marketing/newsletter-form.js.map +1 -0
- package/dist/mascot/mascot.d.ts +28 -0
- package/dist/mascot/mascot.js +28 -0
- package/dist/mascot/mascot.js.map +1 -0
- package/dist/mascot/mascots.generated.d.ts +284 -0
- package/dist/mascot/mascots.generated.js +287 -0
- package/dist/mascot/mascots.generated.js.map +1 -0
- package/dist/master-detail/master-detail.d.ts +30 -0
- package/dist/master-detail/master-detail.js +71 -0
- package/dist/master-detail/master-detail.js.map +1 -0
- package/dist/menu/account-menu.d.ts +44 -0
- package/dist/menu/account-menu.js +78 -0
- package/dist/menu/account-menu.js.map +1 -0
- package/dist/menu/context-menu.d.ts +31 -0
- package/dist/menu/context-menu.js +111 -0
- package/dist/menu/context-menu.js.map +1 -0
- package/dist/menu/dropdown-menu.d.ts +49 -0
- package/dist/menu/dropdown-menu.js +152 -0
- package/dist/menu/dropdown-menu.js.map +1 -0
- package/dist/menu/menu-styles.d.ts +8 -0
- package/dist/menu/menu-styles.js +6 -0
- package/dist/menu/menu-styles.js.map +1 -0
- package/dist/menu/menubar.d.ts +31 -0
- package/dist/menu/menubar.js +125 -0
- package/dist/menu/menubar.js.map +1 -0
- package/dist/modal/modal.d.ts +34 -0
- package/dist/modal/modal.js +58 -0
- package/dist/modal/modal.js.map +1 -0
- package/dist/notification-center/notification-center.d.ts +51 -0
- package/dist/notification-center/notification-center.js +244 -0
- package/dist/notification-center/notification-center.js.map +1 -0
- package/dist/otp-input/otp-input.d.ts +23 -0
- package/dist/otp-input/otp-input.js +61 -0
- package/dist/otp-input/otp-input.js.map +1 -0
- package/dist/overlay/overlay.d.ts +53 -0
- package/dist/overlay/overlay.js +103 -0
- package/dist/overlay/overlay.js.map +1 -0
- package/dist/page-header/page-header.d.ts +55 -0
- package/dist/page-header/page-header.js +114 -0
- package/dist/page-header/page-header.js.map +1 -0
- package/dist/page-header/record-headers.d.ts +51 -0
- package/dist/page-header/record-headers.js +134 -0
- package/dist/page-header/record-headers.js.map +1 -0
- package/dist/page-header/section-header.d.ts +21 -0
- package/dist/page-header/section-header.js +44 -0
- package/dist/page-header/section-header.js.map +1 -0
- package/dist/page-navigation/page-navigation.d.ts +59 -0
- package/dist/page-navigation/page-navigation.js +136 -0
- package/dist/page-navigation/page-navigation.js.map +1 -0
- package/dist/pagination/pagination.d.ts +28 -0
- package/dist/pagination/pagination.js +102 -0
- package/dist/pagination/pagination.js.map +1 -0
- package/dist/phone-input/phone-input.d.ts +34 -0
- package/dist/phone-input/phone-input.js +171 -0
- package/dist/phone-input/phone-input.js.map +1 -0
- package/dist/popover/popover.d.ts +23 -0
- package/dist/popover/popover.js +40 -0
- package/dist/popover/popover.js.map +1 -0
- package/dist/progress/progress.d.ts +21 -0
- package/dist/progress/progress.js +49 -0
- package/dist/progress/progress.js.map +1 -0
- package/dist/segmented-control/segmented-control.d.ts +32 -0
- package/dist/segmented-control/segmented-control.js +29 -0
- package/dist/segmented-control/segmented-control.js.map +1 -0
- package/dist/select/select.d.ts +48 -0
- package/dist/select/select.js +136 -0
- package/dist/select/select.js.map +1 -0
- package/dist/settings/permission-matrix.d.ts +29 -0
- package/dist/settings/permission-matrix.js +101 -0
- package/dist/settings/permission-matrix.js.map +1 -0
- package/dist/settings/settings-row.d.ts +32 -0
- package/dist/settings/settings-row.js +63 -0
- package/dist/settings/settings-row.js.map +1 -0
- package/dist/side-nav/side-nav.d.ts +36 -0
- package/dist/side-nav/side-nav.js +123 -0
- package/dist/side-nav/side-nav.js.map +1 -0
- package/dist/site-header/site-header.d.ts +32 -0
- package/dist/site-header/site-header.js +99 -0
- package/dist/site-header/site-header.js.map +1 -0
- package/dist/skeleton/skeleton.d.ts +16 -0
- package/dist/skeleton/skeleton.js +19 -0
- package/dist/skeleton/skeleton.js.map +1 -0
- package/dist/slider/slider.d.ts +15 -0
- package/dist/slider/slider.js +48 -0
- package/dist/slider/slider.js.map +1 -0
- package/dist/spinner/spinner.d.ts +15 -0
- package/dist/spinner/spinner.js +42 -0
- package/dist/spinner/spinner.js.map +1 -0
- package/dist/stepper/step-dots.d.ts +16 -0
- package/dist/stepper/step-dots.js +26 -0
- package/dist/stepper/step-dots.js.map +1 -0
- package/dist/stepper/stepper.d.ts +26 -0
- package/dist/stepper/stepper.js +113 -0
- package/dist/stepper/stepper.js.map +1 -0
- package/dist/tabs/tabs.d.ts +31 -0
- package/dist/tabs/tabs.js +66 -0
- package/dist/tabs/tabs.js.map +1 -0
- package/dist/templates/app-templates.d.ts +126 -0
- package/dist/templates/app-templates.js +153 -0
- package/dist/templates/app-templates.js.map +1 -0
- package/dist/templates/site-templates.d.ts +49 -0
- package/dist/templates/site-templates.js +110 -0
- package/dist/templates/site-templates.js.map +1 -0
- package/dist/toast/toast-store.d.ts +54 -0
- package/dist/toast/toast-store.js +49 -0
- package/dist/toast/toast-store.js.map +1 -0
- package/dist/toast/toaster.d.ts +15 -0
- package/dist/toast/toaster.js +124 -0
- package/dist/toast/toaster.js.map +1 -0
- package/dist/tooltip/tooltip.d.ts +29 -0
- package/dist/tooltip/tooltip.js +54 -0
- package/dist/tooltip/tooltip.js.map +1 -0
- package/package.json +88 -3
- package/styles.css +521 -0
- package/README.md +0 -3
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface PermissionColumn {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
/** Risky permission (delete, billing): its header is red. */
|
|
5
|
+
danger?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface PermissionRow {
|
|
8
|
+
id: string;
|
|
9
|
+
label: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
}
|
|
12
|
+
/** `'all'` when the row's master switch is on; otherwise the column ids granted. */
|
|
13
|
+
export type PermissionValue = Record<string, 'all' | string[]>;
|
|
14
|
+
export interface PermissionMatrixProps {
|
|
15
|
+
rows: PermissionRow[];
|
|
16
|
+
columns: PermissionColumn[];
|
|
17
|
+
value: PermissionValue;
|
|
18
|
+
onChange: (value: PermissionValue) => void;
|
|
19
|
+
caption: string;
|
|
20
|
+
labels?: {
|
|
21
|
+
all?: string;
|
|
22
|
+
};
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Roles or people × areas: a master switch per row grants everything (cells show checked and disabled), otherwise
|
|
27
|
+
* one checkbox per cell. Scrolls sideways on narrow screens with the first column fixed.
|
|
28
|
+
*/
|
|
29
|
+
export declare function PermissionMatrix({ rows, columns, value, onChange, caption, labels, className, }: PermissionMatrixProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cx as e } from "../lib/cx.js";
|
|
3
|
+
import { Glyph as t } from "../lib/glyphs.js";
|
|
4
|
+
import { checkboxBox as n, focusRing as r } from "../lib/control-styles.js";
|
|
5
|
+
import { CellSwitch as i } from "../data-table/cells.js";
|
|
6
|
+
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
7
|
+
import * as s from "@radix-ui/react-checkbox";
|
|
8
|
+
//#region src/settings/permission-matrix.tsx
|
|
9
|
+
function c({ rows: c, columns: l, value: u, onChange: d, caption: f, labels: p = {}, className: m }) {
|
|
10
|
+
let h = p.all ?? "All", g = (e, t) => d({
|
|
11
|
+
...u,
|
|
12
|
+
[e]: t
|
|
13
|
+
});
|
|
14
|
+
return /* @__PURE__ */ a("div", {
|
|
15
|
+
className: e("overflow-x-auto rounded-lg border border-border bg-surface font-sans", m),
|
|
16
|
+
children: /* @__PURE__ */ o("table", {
|
|
17
|
+
className: "w-full border-collapse text-sm",
|
|
18
|
+
children: [
|
|
19
|
+
/* @__PURE__ */ a("caption", {
|
|
20
|
+
className: "sr-only",
|
|
21
|
+
children: f
|
|
22
|
+
}),
|
|
23
|
+
/* @__PURE__ */ a("thead", {
|
|
24
|
+
className: "bg-surface-muted",
|
|
25
|
+
children: /* @__PURE__ */ o("tr", { children: [
|
|
26
|
+
/* @__PURE__ */ a("th", {
|
|
27
|
+
scope: "col",
|
|
28
|
+
className: "sticky left-0 bg-surface-muted px-4 py-3 text-left font-semibold text-fg-muted",
|
|
29
|
+
children: /* @__PURE__ */ a("span", {
|
|
30
|
+
className: "sr-only",
|
|
31
|
+
children: f
|
|
32
|
+
})
|
|
33
|
+
}),
|
|
34
|
+
/* @__PURE__ */ a("th", {
|
|
35
|
+
scope: "col",
|
|
36
|
+
className: "px-3 py-3 text-center font-semibold text-fg-muted",
|
|
37
|
+
children: h
|
|
38
|
+
}),
|
|
39
|
+
l.map((t) => /* @__PURE__ */ a("th", {
|
|
40
|
+
scope: "col",
|
|
41
|
+
className: e("px-3 py-3 text-center font-semibold whitespace-nowrap", t.danger ? "text-danger-fg" : "text-fg-muted"),
|
|
42
|
+
children: t.label
|
|
43
|
+
}, t.id))
|
|
44
|
+
] })
|
|
45
|
+
}),
|
|
46
|
+
/* @__PURE__ */ a("tbody", { children: c.map((c) => {
|
|
47
|
+
let d = u[c.id] ?? [], f = d === "all";
|
|
48
|
+
return /* @__PURE__ */ o("tr", {
|
|
49
|
+
className: "border-t border-border",
|
|
50
|
+
children: [
|
|
51
|
+
/* @__PURE__ */ o("th", {
|
|
52
|
+
scope: "row",
|
|
53
|
+
className: "sticky left-0 bg-surface px-4 py-3 text-left font-normal",
|
|
54
|
+
children: [/* @__PURE__ */ a("span", {
|
|
55
|
+
className: "block font-semibold text-fg",
|
|
56
|
+
children: c.label
|
|
57
|
+
}), c.description && /* @__PURE__ */ a("span", {
|
|
58
|
+
className: "block text-xs text-fg-muted",
|
|
59
|
+
children: c.description
|
|
60
|
+
})]
|
|
61
|
+
}),
|
|
62
|
+
/* @__PURE__ */ a("td", {
|
|
63
|
+
className: "px-3 py-3 text-center",
|
|
64
|
+
children: /* @__PURE__ */ a(i, {
|
|
65
|
+
checked: f,
|
|
66
|
+
onCheckedChange: (e) => g(c.id, e ? "all" : []),
|
|
67
|
+
label: `${c.label}: ${h}`
|
|
68
|
+
})
|
|
69
|
+
}),
|
|
70
|
+
l.map((i) => {
|
|
71
|
+
let o = f || d.includes(i.id);
|
|
72
|
+
return /* @__PURE__ */ a("td", {
|
|
73
|
+
className: "px-3 py-3 text-center",
|
|
74
|
+
children: /* @__PURE__ */ a(s.Root, {
|
|
75
|
+
checked: o,
|
|
76
|
+
disabled: f,
|
|
77
|
+
onCheckedChange: (e) => {
|
|
78
|
+
let t = d;
|
|
79
|
+
g(c.id, e === !0 ? [...t, i.id] : t.filter((e) => e !== i.id));
|
|
80
|
+
},
|
|
81
|
+
"aria-label": `${c.label}: ${i.label}`,
|
|
82
|
+
className: e(n({ tone: i.danger ? "danger" : "primary" }), r, "mx-auto"),
|
|
83
|
+
children: /* @__PURE__ */ a(s.Indicator, { children: /* @__PURE__ */ a(t, {
|
|
84
|
+
name: "check",
|
|
85
|
+
size: 12,
|
|
86
|
+
strokeWidth: 3
|
|
87
|
+
}) })
|
|
88
|
+
})
|
|
89
|
+
}, i.id);
|
|
90
|
+
})
|
|
91
|
+
]
|
|
92
|
+
}, c.id);
|
|
93
|
+
}) })
|
|
94
|
+
]
|
|
95
|
+
})
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
export { c as PermissionMatrix };
|
|
100
|
+
|
|
101
|
+
//# sourceMappingURL=permission-matrix.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission-matrix.js","names":["RadixCheckbox","PermissionMatrix","allLabel","labels","set","onChange","value","rowId","next","cx","className","caption","columns","c","rows","v","r","all","CellSwitch","on","checked","list","x","checkboxBox","focusRing","Glyph"],"sources":["../../src/settings/permission-matrix.tsx"],"sourcesContent":["'use client';\nimport * as RadixCheckbox from '@radix-ui/react-checkbox';\nimport { cx } from '../lib/cx';\nimport { checkboxBox, focusRing } from '../lib/control-styles';\nimport { Glyph } from '../lib/glyphs';\nimport { CellSwitch } from '../data-table/cells';\n\n/* ---------------- PermissionMatrix ---------------- */\n\nexport interface PermissionColumn {\n id: string;\n label: string;\n /** Risky permission (delete, billing): its header is red. */\n danger?: boolean;\n}\n\nexport interface PermissionRow {\n id: string;\n label: string;\n description?: string;\n}\n\n/** `'all'` when the row's master switch is on; otherwise the column ids granted. */\nexport type PermissionValue = Record<string, 'all' | string[]>;\n\nexport interface PermissionMatrixProps {\n rows: PermissionRow[];\n columns: PermissionColumn[];\n value: PermissionValue;\n onChange: (value: PermissionValue) => void;\n caption: string;\n labels?: { all?: string };\n className?: string;\n}\n\n/**\n * Roles or people × areas: a master switch per row grants everything (cells show checked and disabled), otherwise\n * one checkbox per cell. Scrolls sideways on narrow screens with the first column fixed.\n */\nexport function PermissionMatrix({\n rows,\n columns,\n value,\n onChange,\n caption,\n labels = {},\n className,\n}: PermissionMatrixProps) {\n const allLabel = labels.all ?? 'All';\n const set = (rowId: string, next: 'all' | string[]) => onChange({ ...value, [rowId]: next });\n return (\n <div className={cx('overflow-x-auto rounded-lg border border-border bg-surface font-sans', className)}>\n <table className=\"w-full border-collapse text-sm\">\n <caption className=\"sr-only\">{caption}</caption>\n <thead className=\"bg-surface-muted\">\n <tr>\n <th scope=\"col\" className=\"sticky left-0 bg-surface-muted px-4 py-3 text-left font-semibold text-fg-muted\">\n <span className=\"sr-only\">{caption}</span>\n </th>\n <th scope=\"col\" className=\"px-3 py-3 text-center font-semibold text-fg-muted\">\n {allLabel}\n </th>\n {columns.map((c) => (\n <th\n key={c.id}\n scope=\"col\"\n className={cx(\n 'px-3 py-3 text-center font-semibold whitespace-nowrap',\n c.danger ? 'text-danger-fg' : 'text-fg-muted',\n )}\n >\n {c.label}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {rows.map((r) => {\n const v = value[r.id] ?? [];\n const all = v === 'all';\n return (\n <tr key={r.id} className=\"border-t border-border\">\n <th scope=\"row\" className=\"sticky left-0 bg-surface px-4 py-3 text-left font-normal\">\n <span className=\"block font-semibold text-fg\">{r.label}</span>\n {r.description && <span className=\"block text-xs text-fg-muted\">{r.description}</span>}\n </th>\n <td className=\"px-3 py-3 text-center\">\n <CellSwitch\n checked={all}\n onCheckedChange={(on) => set(r.id, on ? 'all' : [])}\n label={`${r.label}: ${allLabel}`}\n />\n </td>\n {columns.map((c) => {\n const checked = all || (v as string[]).includes(c.id);\n return (\n <td key={c.id} className=\"px-3 py-3 text-center\">\n <RadixCheckbox.Root\n checked={checked}\n disabled={all}\n onCheckedChange={(on) => {\n const list = v as string[];\n set(r.id, on === true ? [...list, c.id] : list.filter((x) => x !== c.id));\n }}\n aria-label={`${r.label}: ${c.label}`}\n // Centered in the cell: the box is a grid, so the cell centers it with mx-auto.\n className={cx(checkboxBox({ tone: c.danger ? 'danger' : 'primary' }), focusRing, 'mx-auto')}\n >\n <RadixCheckbox.Indicator>\n <Glyph name=\"check\" size={12} strokeWidth={3} />\n </RadixCheckbox.Indicator>\n </RadixCheckbox.Root>\n </td>\n );\n })}\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n );\n}\n"],"mappings":";;;;;;;;AAuCA,SAAgBC,EAAiB,EAC/B,SACA,YACA,UACA,aACA,YACA,YAAS,CAAC,GACV,gBACwB;CACxB,IAAMC,IAAWC,EAAO,OAAO,OACzBC,KAAO,GAAe,MAA2BC,EAAS;EAAE,GAAGC;GAAQC,IAAQC;CAAK,CAAC;CAC3F,OACE,kBAAC,OAAD;EAAK,WAAWC,EAAG,wEAAwEC,CAAS;EAClG,UAAA,kBAAC,SAAD;GAAO,WAAU;GAAjB,UAAA;IACE,kBAAC,WAAD;KAAS,WAAU;KAAW,UAAAC;IAAiB,CAAA;IAC/C,kBAAC,SAAD;KAAO,WAAU;KACf,UAAA,kBAAC,MAAD,EAAA,UAAA;MACE,kBAAC,MAAD;OAAI,OAAM;OAAM,WAAU;OACxB,UAAA,kBAAC,QAAD;QAAM,WAAU;QAAW,UAAAA;OAAc,CAAA;MACvC,CAAA;MACJ,kBAAC,MAAD;OAAI,OAAM;OAAM,WAAU;OACvB,UAAAT;MACC,CAAA;MACHU,EAAQ,KAAK,MACZ,kBAAC,MAAD;OAEE,OAAM;OACN,WAAWH,EACT,yDACAI,EAAE,SAAS,mBAAmB,eAChC;OAEC,UAAAA,EAAE;MACD,GARGA,EAAE,EAQL,CACL;KACC,EAAA,CAAA;IACC,CAAA;IACP,kBAAC,SAAD,EAAA,UACGC,EAAK,KAAK,MAAM;KACf,IAAMC,IAAIT,EAAMU,EAAE,OAAO,CAAC,GACpBC,IAAMF,MAAM;KAClB,OACE,kBAAC,MAAD;MAAe,WAAU;MAAzB,UAAA;OACE,kBAAC,MAAD;QAAI,OAAM;QAAM,WAAU;QAA1B,UAAA,CACE,kBAAC,QAAD;SAAM,WAAU;SAA+B,UAAAC,EAAE;QAAY,CAAA,GAC5DA,EAAE,eAAe,kBAAC,QAAD;SAAM,WAAU;SAA+B,UAAAA,EAAE;QAAkB,CAAA,CACnF;;OACJ,kBAAC,MAAD;QAAI,WAAU;QACZ,UAAA,kBAACE,GAAD;SACE,SAASD;SACT,kBAAkB,MAAOb,EAAIY,EAAE,IAAIG,IAAK,QAAQ,CAAC,CAAC;SAClD,OAAO,GAAGH,EAAE,MAAM,IAAId;QACvB,CAAA;OACC,CAAA;OACHU,EAAQ,KAAK,MAAM;QAClB,IAAMQ,IAAUH,KAAQF,EAAe,SAASF,EAAE,EAAE;QACpD,OACE,kBAAC,MAAD;SAAe,WAAU;SACvB,UAAA,kBAACb,EAAc,MAAf;UACW;UACT,UAAUiB;UACV,kBAAkB,MAAO;WACvB,IAAMI,IAAON;WACb,EAAIC,EAAE,IAAIG,MAAO,KAAO,CAAC,GAAGE,GAAMR,EAAE,EAAE,IAAIQ,EAAK,QAAQ,MAAMC,MAAMT,EAAE,EAAE,CAAC;UAC1E;UACA,cAAY,GAAGG,EAAE,MAAM,IAAIH,EAAE;UAE7B,WAAWJ,EAAGc,EAAY,EAAE,MAAMV,EAAE,SAAS,WAAW,UAAU,CAAC,GAAGW,GAAW,SAAS;UAE1F,UAAA,kBAACxB,EAAc,WAAf,EAAA,UACE,kBAACyB,GAAD;WAAO,MAAK;WAAQ,MAAM;WAAI,aAAa;UAAI,CAAA,EACxB,CAAA;SACP,CAAA;QAClB,GAhBKZ,EAAE,EAgBP;OAER,CAAC;MACC;KAlCK,GAAAG,EAAE,EAkCP;IAER,CAAC,EACI,CAAA;GACF;;CACJ,CAAA;AAET"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
import type { IconName } from '../icon/icon';
|
|
3
|
+
/** Rows of settings with dividers between them. Put it inside a `CardBody` (or a card with `padding="none"`). */
|
|
4
|
+
export declare function SettingsList({ className, ...rest }: HTMLAttributes<HTMLDivElement>): import("react").JSX.Element;
|
|
5
|
+
export interface SettingsRowIds {
|
|
6
|
+
/** Pass to the control as `aria-labelledby`. */
|
|
7
|
+
labelId: string;
|
|
8
|
+
/** Pass to the control as `aria-describedby` (only set when there is a description). */
|
|
9
|
+
descriptionId?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface SettingsRowProps {
|
|
12
|
+
label: ReactNode;
|
|
13
|
+
description?: ReactNode;
|
|
14
|
+
/**
|
|
15
|
+
* The control on the right: a `Switch`-like toggle, a `SegmentedControl`, a value with a link. As a function it
|
|
16
|
+
* gets the ids to name the control after the row: `({ labelId, descriptionId }) => <CellSwitch … />`.
|
|
17
|
+
*/
|
|
18
|
+
control?: ReactNode | ((ids: SettingsRowIds) => ReactNode);
|
|
19
|
+
icon?: IconName;
|
|
20
|
+
/** A badge after the label ("Always on", "Part of Recognition"). */
|
|
21
|
+
badge?: ReactNode;
|
|
22
|
+
/** Child rows, indented under this one (shown only when relevant, e.g. when the parent is on). */
|
|
23
|
+
children?: ReactNode;
|
|
24
|
+
/** A boxed form under the row (e.g. extra fields for this setting). */
|
|
25
|
+
detail?: ReactNode;
|
|
26
|
+
className?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* One setting per row: label and description on the left, the control on the right. Stacks on narrow screens
|
|
30
|
+
* when the control is wide (a segmented control); a toggle stays beside the label.
|
|
31
|
+
*/
|
|
32
|
+
export declare function SettingsRow({ label, description, control, icon, badge, children, detail, className, }: SettingsRowProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cx as e } from "../lib/cx.js";
|
|
3
|
+
import { IconTile as t } from "../icon/icon-tile.js";
|
|
4
|
+
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
5
|
+
import { useId as i } from "react";
|
|
6
|
+
//#region src/settings/settings-row.tsx
|
|
7
|
+
function a({ className: t, ...r }) {
|
|
8
|
+
return /* @__PURE__ */ n("div", {
|
|
9
|
+
className: e("divide-y divide-border font-sans", t),
|
|
10
|
+
...r
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
function o({ label: a, description: o, control: s, icon: c, badge: l, children: u, detail: d, className: f }) {
|
|
14
|
+
let p = i(), m = i(), h = {
|
|
15
|
+
labelId: p,
|
|
16
|
+
descriptionId: o ? m : void 0
|
|
17
|
+
};
|
|
18
|
+
return /* @__PURE__ */ r("div", {
|
|
19
|
+
className: e("grid gap-3 py-4 first:pt-0 last:pb-0", f),
|
|
20
|
+
children: [
|
|
21
|
+
/* @__PURE__ */ r("div", {
|
|
22
|
+
className: "flex flex-wrap items-center justify-between gap-x-6 gap-y-3",
|
|
23
|
+
children: [/* @__PURE__ */ r("div", {
|
|
24
|
+
className: "flex min-w-[min(100%,14rem)] flex-1 items-start gap-3",
|
|
25
|
+
children: [c && /* @__PURE__ */ n(t, {
|
|
26
|
+
icon: c,
|
|
27
|
+
size: "sm",
|
|
28
|
+
tone: "muted"
|
|
29
|
+
}), /* @__PURE__ */ r("div", {
|
|
30
|
+
className: "grid min-w-0 gap-0.5",
|
|
31
|
+
children: [/* @__PURE__ */ r("div", {
|
|
32
|
+
className: "flex flex-wrap items-center gap-2",
|
|
33
|
+
children: [/* @__PURE__ */ n("span", {
|
|
34
|
+
id: p,
|
|
35
|
+
className: "text-[15px] font-semibold text-fg",
|
|
36
|
+
children: a
|
|
37
|
+
}), l]
|
|
38
|
+
}), o && /* @__PURE__ */ n("p", {
|
|
39
|
+
id: m,
|
|
40
|
+
className: "m-0 text-sm text-pretty text-fg-muted",
|
|
41
|
+
children: o
|
|
42
|
+
})]
|
|
43
|
+
})]
|
|
44
|
+
}), s !== void 0 && /* @__PURE__ */ n("div", {
|
|
45
|
+
className: "flex shrink-0 items-center gap-2",
|
|
46
|
+
children: typeof s == "function" ? s(h) : s
|
|
47
|
+
})]
|
|
48
|
+
}),
|
|
49
|
+
d && /* @__PURE__ */ n("div", {
|
|
50
|
+
className: "rounded-md bg-surface-muted p-4",
|
|
51
|
+
children: d
|
|
52
|
+
}),
|
|
53
|
+
u && /* @__PURE__ */ n("div", {
|
|
54
|
+
className: "ml-4 border-l-2 border-border pl-4 sm:ml-6",
|
|
55
|
+
children: u
|
|
56
|
+
})
|
|
57
|
+
]
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
export { a as SettingsList, o as SettingsRow };
|
|
62
|
+
|
|
63
|
+
//# sourceMappingURL=settings-row.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-row.js","names":["SettingsList","rest","cx","className","SettingsRow","labelId","useId","descriptionId","ids","description","icon","IconTile","label","badge","control","detail","children"],"sources":["../../src/settings/settings-row.tsx"],"sourcesContent":["'use client';\n\nimport { useId, type HTMLAttributes, type ReactNode } from 'react';\nimport { cx } from '../lib/cx';\nimport { IconTile } from '../icon/icon-tile';\nimport type { IconName } from '../icon/icon';\n\n/** Rows of settings with dividers between them. Put it inside a `CardBody` (or a card with `padding=\"none\"`). */\nexport function SettingsList({ className, ...rest }: HTMLAttributes<HTMLDivElement>) {\n return <div className={cx('divide-y divide-border font-sans', className)} {...rest} />;\n}\n\nexport interface SettingsRowIds {\n /** Pass to the control as `aria-labelledby`. */\n labelId: string;\n /** Pass to the control as `aria-describedby` (only set when there is a description). */\n descriptionId?: string;\n}\n\nexport interface SettingsRowProps {\n label: ReactNode;\n description?: ReactNode;\n /**\n * The control on the right: a `Switch`-like toggle, a `SegmentedControl`, a value with a link. As a function it\n * gets the ids to name the control after the row: `({ labelId, descriptionId }) => <CellSwitch … />`.\n */\n control?: ReactNode | ((ids: SettingsRowIds) => ReactNode);\n icon?: IconName;\n /** A badge after the label (\"Always on\", \"Part of Recognition\"). */\n badge?: ReactNode;\n /** Child rows, indented under this one (shown only when relevant, e.g. when the parent is on). */\n children?: ReactNode;\n /** A boxed form under the row (e.g. extra fields for this setting). */\n detail?: ReactNode;\n className?: string;\n}\n\n/**\n * One setting per row: label and description on the left, the control on the right. Stacks on narrow screens\n * when the control is wide (a segmented control); a toggle stays beside the label.\n */\nexport function SettingsRow({\n label,\n description,\n control,\n icon,\n badge,\n children,\n detail,\n className,\n}: SettingsRowProps) {\n const labelId = useId();\n const descriptionId = useId();\n const ids = { labelId, descriptionId: description ? descriptionId : undefined };\n return (\n <div className={cx('grid gap-3 py-4 first:pt-0 last:pb-0', className)}>\n <div className=\"flex flex-wrap items-center justify-between gap-x-6 gap-y-3\">\n <div className=\"flex min-w-[min(100%,14rem)] flex-1 items-start gap-3\">\n {icon && <IconTile icon={icon} size=\"sm\" tone=\"muted\" />}\n <div className=\"grid min-w-0 gap-0.5\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span id={labelId} className=\"text-[15px] font-semibold text-fg\">\n {label}\n </span>\n {badge}\n </div>\n {description && (\n <p id={descriptionId} className=\"m-0 text-sm text-pretty text-fg-muted\">\n {description}\n </p>\n )}\n </div>\n </div>\n {control !== undefined && (\n <div className=\"flex shrink-0 items-center gap-2\">\n {typeof control === 'function' ? control(ids) : control}\n </div>\n )}\n </div>\n {detail && <div className=\"rounded-md bg-surface-muted p-4\">{detail}</div>}\n {children && <div className=\"ml-4 border-l-2 border-border pl-4 sm:ml-6\">{children}</div>}\n </div>\n );\n}\n"],"mappings":";;;;;;AAQA,SAAgBA,EAAa,EAAE,cAAW,GAAGC,KAAwC;CACnF,OAAO,kBAAC,OAAD;EAAK,WAAWC,EAAG,oCAAoCC,CAAS;EAAG,GAAIF;CAAO,CAAA;AACvF;AA+BA,SAAgBG,EAAY,EAC1B,UACA,gBACA,YACA,SACA,UACA,aACA,WACA,gBACmB;CACnB,IAAMC,IAAUC,EAAM,GAChBC,IAAgBD,EAAM,GACtBE,IAAM;EAAE;EAAS,eAAeC,IAAcF,IAAgB,KAAA;CAAU;CAC9E,OACE,kBAAC,OAAD;EAAK,WAAWL,EAAG,wCAAwCC,CAAS;EAApE,UAAA;GACE,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACGO,KAAQ,kBAACC,GAAD;MAAgB;MAAM,MAAK;MAAK,MAAK;KAAS,CAAA,GACvD,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,kBAAC,QAAD;QAAM,IAAIN;QAAS,WAAU;QAC1B,UAAAO;OACG,CAAA,GACLC,CACE;MACJ,CAAA,GAAAJ,KACC,kBAAC,KAAD;OAAG,IAAIF;OAAe,WAAU;OAC7B,UAAAE;MACA,CAAA,CAEF;KACF,CAAA,CAAA;IACJ,CAAA,GAAAK,MAAY,KAAA,KACX,kBAAC,OAAD;KAAK,WAAU;KACZ,UAAA,OAAOA,KAAY,aAAaA,EAAQN,CAAG,IAAIM;IAC7C,CAAA,CAEJ;;GACJC,KAAU,kBAAC,OAAD;IAAK,WAAU;IAAmC,UAAAA;GAAY,CAAA;GACxEC,KAAY,kBAAC,OAAD;IAAK,WAAU;IAA8C;GAAc,CAAA;EACrF;;AAET"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ElementType, ReactNode } from 'react';
|
|
2
|
+
import { type IconName } from '../icon/icon';
|
|
3
|
+
export interface NavItem {
|
|
4
|
+
label: string;
|
|
5
|
+
href?: string;
|
|
6
|
+
icon?: IconName;
|
|
7
|
+
/** Count or Badge shown at the end (hidden in the collapsed rail; a dot is shown instead). */
|
|
8
|
+
badge?: ReactNode;
|
|
9
|
+
/** Marks the current page (`aria-current="page"`). A parent with an active child opens automatically. */
|
|
10
|
+
active?: boolean;
|
|
11
|
+
/** `danger` for destructive areas ("Danger zone"): red label, red fill when active. */
|
|
12
|
+
tone?: 'default' | 'danger';
|
|
13
|
+
items?: NavItem[];
|
|
14
|
+
}
|
|
15
|
+
export interface NavSection {
|
|
16
|
+
title?: string;
|
|
17
|
+
items: NavItem[];
|
|
18
|
+
}
|
|
19
|
+
export interface SideNavProps {
|
|
20
|
+
sections: NavSection[];
|
|
21
|
+
/** Link component (e.g. Next.js `Link`). Receives `href`, `className`, `aria-current`, `onClick`, `children`. */
|
|
22
|
+
linkAs?: ElementType;
|
|
23
|
+
/** Accessible name of the navigation landmark. */
|
|
24
|
+
label?: string;
|
|
25
|
+
/**
|
|
26
|
+
* `soft` (default): tinted active item, for the main navigation.
|
|
27
|
+
* `solid`: active item filled with the brand color, for secondary navigation (settings sections).
|
|
28
|
+
*/
|
|
29
|
+
appearance?: 'soft' | 'solid';
|
|
30
|
+
className?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Application navigation: sections, icons, badges and one level of nesting. Inside `AppShell`
|
|
34
|
+
* it collapses to an icon rail on desktop (labels in tooltips) and lives in a drawer on mobile.
|
|
35
|
+
*/
|
|
36
|
+
export declare function SideNav({ sections, linkAs, label, appearance, className }: SideNavProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cx as e } from "../lib/cx.js";
|
|
3
|
+
import { Icon as t } from "../icon/icon.js";
|
|
4
|
+
import { Tooltip as n } from "../tooltip/tooltip.js";
|
|
5
|
+
import { Glyph as r } from "../lib/glyphs.js";
|
|
6
|
+
import { useAppShell as i } from "../app-shell/app-shell-context.js";
|
|
7
|
+
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
8
|
+
import * as s from "@radix-ui/react-collapsible";
|
|
9
|
+
//#region src/side-nav/side-nav.tsx
|
|
10
|
+
var c = (e) => !!e.active || !!e.items?.some(c), l = "flex min-h-10 w-full items-center gap-3 rounded-sm px-3 py-2 font-sans text-sm font-medium no-underline pointer-coarse:min-h-11 focus-visible:outline-3 focus-visible:-outline-offset-3 focus-visible:outline-focus aria-[current=page]:font-semibold", u = {
|
|
11
|
+
soft: {
|
|
12
|
+
default: "text-fg hover:bg-surface-muted aria-[current=page]:bg-surface-muted aria-[current=page]:text-link",
|
|
13
|
+
danger: "text-danger-fg hover:bg-danger-surface aria-[current=page]:bg-danger-surface"
|
|
14
|
+
},
|
|
15
|
+
solid: {
|
|
16
|
+
default: "text-fg hover:bg-surface-muted aria-[current=page]:bg-primary aria-[current=page]:text-on-primary aria-[current=page]:hover:bg-primary-hover",
|
|
17
|
+
danger: "text-danger-fg hover:bg-danger-surface aria-[current=page]:bg-danger-solid aria-[current=page]:text-on-danger-solid aria-[current=page]:hover:bg-danger-solid-hover"
|
|
18
|
+
}
|
|
19
|
+
}, d = (t, n = "default") => e(l, u[t][n]);
|
|
20
|
+
function f({ sections: t, linkAs: n = "a", label: r = "Main", appearance: s = "soft", className: c }) {
|
|
21
|
+
let { collapsed: l, inDrawer: u } = i(), d = l && !u;
|
|
22
|
+
return /* @__PURE__ */ a("nav", {
|
|
23
|
+
"aria-label": r,
|
|
24
|
+
className: e("flex flex-col gap-4", c),
|
|
25
|
+
children: t.map((e, t) => /* @__PURE__ */ o("div", {
|
|
26
|
+
className: "grid gap-0.5",
|
|
27
|
+
children: [e.title && (d ? t > 0 && /* @__PURE__ */ a("div", {
|
|
28
|
+
"aria-hidden": "true",
|
|
29
|
+
className: "mx-3 my-1 h-px bg-border"
|
|
30
|
+
}) : /* @__PURE__ */ a("p", {
|
|
31
|
+
className: "m-0 px-3 pt-1 pb-1.5 font-sans text-xs font-semibold tracking-wide text-fg-muted uppercase",
|
|
32
|
+
children: e.title
|
|
33
|
+
})), /* @__PURE__ */ a("ul", {
|
|
34
|
+
className: "m-0 grid list-none gap-0.5 p-0",
|
|
35
|
+
children: e.items.map((e) => /* @__PURE__ */ a("li", { children: /* @__PURE__ */ a(m, {
|
|
36
|
+
item: e,
|
|
37
|
+
linkAs: n,
|
|
38
|
+
rail: d,
|
|
39
|
+
appearance: s
|
|
40
|
+
}) }, e.label))
|
|
41
|
+
})]
|
|
42
|
+
}, e.title ?? t))
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function p({ item: r, linkAs: s, rail: c, nested: l, appearance: u }) {
|
|
46
|
+
let { closeDrawer: f } = i(), p = /* @__PURE__ */ o(s, {
|
|
47
|
+
href: r.href ?? r.items?.find((e) => e.href)?.href,
|
|
48
|
+
"aria-current": r.active ? "page" : void 0,
|
|
49
|
+
"aria-label": c ? r.label : void 0,
|
|
50
|
+
onClick: f,
|
|
51
|
+
className: e(d(u, r.tone), c && "relative justify-center px-0", l && "pl-11"),
|
|
52
|
+
children: [
|
|
53
|
+
r.icon && /* @__PURE__ */ a(t, {
|
|
54
|
+
name: r.icon,
|
|
55
|
+
size: 22,
|
|
56
|
+
className: "shrink-0"
|
|
57
|
+
}),
|
|
58
|
+
!c && /* @__PURE__ */ a("span", {
|
|
59
|
+
className: "min-w-0 flex-1 truncate",
|
|
60
|
+
children: r.label
|
|
61
|
+
}),
|
|
62
|
+
!c && r.badge,
|
|
63
|
+
c && r.badge && /* @__PURE__ */ a("span", {
|
|
64
|
+
"aria-hidden": "true",
|
|
65
|
+
className: "absolute top-1.5 right-2.5 size-2 rounded-full bg-danger-solid"
|
|
66
|
+
})
|
|
67
|
+
]
|
|
68
|
+
});
|
|
69
|
+
return c ? /* @__PURE__ */ a(n, {
|
|
70
|
+
content: r.label,
|
|
71
|
+
side: "right",
|
|
72
|
+
children: p
|
|
73
|
+
}) : p;
|
|
74
|
+
}
|
|
75
|
+
function m({ item: n, linkAs: i, rail: l, appearance: u }) {
|
|
76
|
+
return !n.items?.length || l ? /* @__PURE__ */ a(p, {
|
|
77
|
+
item: {
|
|
78
|
+
...n,
|
|
79
|
+
active: l ? c(n) : n.active
|
|
80
|
+
},
|
|
81
|
+
linkAs: i,
|
|
82
|
+
rail: l,
|
|
83
|
+
appearance: u
|
|
84
|
+
}) : /* @__PURE__ */ o(s.Root, {
|
|
85
|
+
defaultOpen: c(n),
|
|
86
|
+
children: [/* @__PURE__ */ o(s.Trigger, {
|
|
87
|
+
className: e(d("soft", n.tone), "group cursor-pointer border-0 bg-transparent text-left"),
|
|
88
|
+
children: [
|
|
89
|
+
n.icon && /* @__PURE__ */ a(t, {
|
|
90
|
+
name: n.icon,
|
|
91
|
+
size: 22,
|
|
92
|
+
className: "shrink-0"
|
|
93
|
+
}),
|
|
94
|
+
/* @__PURE__ */ a("span", {
|
|
95
|
+
className: "min-w-0 flex-1 truncate",
|
|
96
|
+
children: n.label
|
|
97
|
+
}),
|
|
98
|
+
n.badge,
|
|
99
|
+
/* @__PURE__ */ a(r, {
|
|
100
|
+
name: "chevron-down",
|
|
101
|
+
size: 16,
|
|
102
|
+
className: "shrink-0 text-fg-muted transition-transform duration-200 group-data-[state=open]:rotate-180 motion-reduce:transition-none"
|
|
103
|
+
})
|
|
104
|
+
]
|
|
105
|
+
}), /* @__PURE__ */ a(s.Content, {
|
|
106
|
+
className: "kf-collapse overflow-hidden",
|
|
107
|
+
children: /* @__PURE__ */ a("ul", {
|
|
108
|
+
className: "m-0 grid list-none gap-0.5 p-0 pt-0.5",
|
|
109
|
+
children: n.items.map((e) => /* @__PURE__ */ a("li", { children: /* @__PURE__ */ a(p, {
|
|
110
|
+
item: e,
|
|
111
|
+
linkAs: i,
|
|
112
|
+
rail: !1,
|
|
113
|
+
nested: !0,
|
|
114
|
+
appearance: u
|
|
115
|
+
}) }, e.label))
|
|
116
|
+
})
|
|
117
|
+
})]
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
//#endregion
|
|
121
|
+
export { f as SideNav };
|
|
122
|
+
|
|
123
|
+
//# sourceMappingURL=side-nav.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"side-nav.js","names":["RadixCollapsible","hasActive","item","rowBase","ROW_COLORS","rowClass","cx","appearance","tone","SideNav","useAppShell","rail","collapsed","inDrawer","label","className","sections","section","i","NavRow","NavLink","Link","link","c","closeDrawer","nested","Icon","Tooltip","Glyph","child"],"sources":["../../src/side-nav/side-nav.tsx"],"sourcesContent":["'use client';\n\nimport * as RadixCollapsible from '@radix-ui/react-collapsible';\nimport type { ElementType, ReactNode } from 'react';\nimport { cx } from '../lib/cx';\nimport { Glyph } from '../lib/glyphs';\nimport { Icon, type IconName } from '../icon/icon';\nimport { Tooltip } from '../tooltip/tooltip';\nimport { useAppShell } from '../app-shell/app-shell-context';\n\nexport interface NavItem {\n label: string;\n href?: string;\n icon?: IconName;\n /** Count or Badge shown at the end (hidden in the collapsed rail; a dot is shown instead). */\n badge?: ReactNode;\n /** Marks the current page (`aria-current=\"page\"`). A parent with an active child opens automatically. */\n active?: boolean;\n /** `danger` for destructive areas (\"Danger zone\"): red label, red fill when active. */\n tone?: 'default' | 'danger';\n items?: NavItem[];\n}\n\nexport interface NavSection {\n title?: string;\n items: NavItem[];\n}\n\nexport interface SideNavProps {\n sections: NavSection[];\n /** Link component (e.g. Next.js `Link`). Receives `href`, `className`, `aria-current`, `onClick`, `children`. */\n linkAs?: ElementType;\n /** Accessible name of the navigation landmark. */\n label?: string;\n /**\n * `soft` (default): tinted active item, for the main navigation.\n * `solid`: active item filled with the brand color, for secondary navigation (settings sections).\n */\n appearance?: 'soft' | 'solid';\n className?: string;\n}\n\nconst hasActive = (item: NavItem): boolean => !!item.active || !!item.items?.some(hasActive);\n\nconst rowBase =\n 'flex min-h-10 w-full items-center gap-3 rounded-sm px-3 py-2 font-sans text-sm font-medium no-underline pointer-coarse:min-h-11 ' +\n 'focus-visible:outline-3 focus-visible:-outline-offset-3 focus-visible:outline-focus aria-[current=page]:font-semibold';\n\n// Colors per appearance and tone: `cx` does not merge classes, so each row gets exactly one set.\nconst ROW_COLORS = {\n soft: {\n default: 'text-fg hover:bg-surface-muted aria-[current=page]:bg-surface-muted aria-[current=page]:text-link',\n danger: 'text-danger-fg hover:bg-danger-surface aria-[current=page]:bg-danger-surface',\n },\n solid: {\n default:\n 'text-fg hover:bg-surface-muted aria-[current=page]:bg-primary aria-[current=page]:text-on-primary aria-[current=page]:hover:bg-primary-hover',\n danger:\n 'text-danger-fg hover:bg-danger-surface aria-[current=page]:bg-danger-solid aria-[current=page]:text-on-danger-solid aria-[current=page]:hover:bg-danger-solid-hover',\n },\n} as const;\n\ntype Appearance = keyof typeof ROW_COLORS;\nconst rowClass = (appearance: Appearance, tone: NavItem['tone'] = 'default') => cx(rowBase, ROW_COLORS[appearance][tone]);\n\n/**\n * Application navigation: sections, icons, badges and one level of nesting. Inside `AppShell`\n * it collapses to an icon rail on desktop (labels in tooltips) and lives in a drawer on mobile.\n */\nexport function SideNav({ sections, linkAs = 'a', label = 'Main', appearance = 'soft', className }: SideNavProps) {\n const { collapsed, inDrawer } = useAppShell();\n const rail = collapsed && !inDrawer;\n return (\n <nav aria-label={label} className={cx('flex flex-col gap-4', className)}>\n {sections.map((section, i) => (\n <div key={section.title ?? i} className=\"grid gap-0.5\">\n {section.title &&\n (rail ? (\n i > 0 && <div aria-hidden=\"true\" className=\"mx-3 my-1 h-px bg-border\" />\n ) : (\n <p className=\"m-0 px-3 pt-1 pb-1.5 font-sans text-xs font-semibold tracking-wide text-fg-muted uppercase\">\n {section.title}\n </p>\n ))}\n <ul className=\"m-0 grid list-none gap-0.5 p-0\">\n {section.items.map((item) => (\n <li key={item.label}>\n <NavRow item={item} linkAs={linkAs} rail={rail} appearance={appearance} />\n </li>\n ))}\n </ul>\n </div>\n ))}\n </nav>\n );\n}\n\nfunction NavLink({\n item,\n linkAs: Link,\n rail,\n nested,\n appearance,\n}: {\n item: NavItem;\n linkAs: ElementType;\n rail: boolean;\n nested?: boolean;\n appearance: Appearance;\n}) {\n const { closeDrawer } = useAppShell();\n const link = (\n <Link\n href={item.href ?? item.items?.find((c) => c.href)?.href}\n aria-current={item.active ? 'page' : undefined}\n aria-label={rail ? item.label : undefined}\n onClick={closeDrawer}\n className={cx(rowClass(appearance, item.tone), rail && 'relative justify-center px-0', nested && 'pl-11')}\n >\n {item.icon && <Icon name={item.icon} size={22} className=\"shrink-0\" />}\n {!rail && <span className=\"min-w-0 flex-1 truncate\">{item.label}</span>}\n {!rail && item.badge}\n {rail && item.badge && (\n <span aria-hidden=\"true\" className=\"absolute top-1.5 right-2.5 size-2 rounded-full bg-danger-solid\" />\n )}\n </Link>\n );\n return rail ? (\n <Tooltip content={item.label} side=\"right\">\n {link}\n </Tooltip>\n ) : (\n link\n );\n}\n\nfunction NavRow({\n item,\n linkAs,\n rail,\n appearance,\n}: {\n item: NavItem;\n linkAs: ElementType;\n rail: boolean;\n appearance: Appearance;\n}) {\n if (!item.items?.length || rail)\n return (\n <NavLink\n item={{ ...item, active: rail ? hasActive(item) : item.active }}\n linkAs={linkAs}\n rail={rail}\n appearance={appearance}\n />\n );\n return (\n <RadixCollapsible.Root defaultOpen={hasActive(item)}>\n <RadixCollapsible.Trigger\n className={cx(rowClass('soft', item.tone), 'group cursor-pointer border-0 bg-transparent text-left')}\n >\n {item.icon && <Icon name={item.icon} size={22} className=\"shrink-0\" />}\n <span className=\"min-w-0 flex-1 truncate\">{item.label}</span>\n {item.badge}\n <Glyph\n name=\"chevron-down\"\n size={16}\n className=\"shrink-0 text-fg-muted transition-transform duration-200 group-data-[state=open]:rotate-180 motion-reduce:transition-none\"\n />\n </RadixCollapsible.Trigger>\n <RadixCollapsible.Content className=\"kf-collapse overflow-hidden\">\n <ul className=\"m-0 grid list-none gap-0.5 p-0 pt-0.5\">\n {item.items.map((child) => (\n <li key={child.label}>\n <NavLink item={child} linkAs={linkAs} rail={false} nested appearance={appearance} />\n </li>\n ))}\n </ul>\n </RadixCollapsible.Content>\n </RadixCollapsible.Root>\n );\n}\n"],"mappings":";;;;;;;;;AA0CA,IAAMC,KAAa,MAA2B,CAAC,CAACC,EAAK,UAAU,CAAC,CAACA,EAAK,OAAO,KAAKD,CAAS,GAErFE,IACJ,yPAIIC,IAAa;CACjB,MAAM;EACJ,SAAS;EACT,QAAQ;CACV;CACA,OAAO;EACL,SACE;EACF,QACE;CACJ;AACF,GAGMC,KAAY,GAAwB,IAAwB,cAAcC,EAAGH,GAASC,EAAWG,EAAW,CAACC,EAAK;AAMxH,SAAgBC,EAAQ,EAAE,aAAU,YAAS,KAAK,WAAQ,QAAQ,gBAAa,QAAQ,gBAA2B;CAChH,IAAM,EAAE,cAAW,gBAAaC,EAAY,GACtCC,IAAOC,KAAa,CAACC;CAC3B,OACE,kBAAC,OAAD;EAAK,cAAYC;EAAO,WAAWR,EAAG,uBAAuBS,CAAS;EACnE,UAAAC,EAAS,KAAK,GAAS,MACtB,kBAAC,OAAD;GAA8B,WAAU;GAAxC,UAAA,CACGC,EAAQ,UACNN,IACCO,IAAI,KAAK,kBAAC,OAAD;IAAK,eAAY;IAAO,WAAU;GAA4B,CAAA,IAEvE,kBAAC,KAAD;IAAG,WAAU;IACV,UAAAD,EAAQ;GACR,CAAA,IAEP,kBAAC,MAAD;IAAI,WAAU;IACX,UAAAA,EAAQ,MAAM,KAAK,MAClB,kBAAC,MAAD,EAAA,UACE,kBAACE,GAAD;KAAc;KAAc;KAAc;KAAkB;IAAa,CAAA,EACvE,GAFKjB,EAAK,KAEV,CACL;GACC,CAAA,CACD;EAhBK,GAAAe,EAAQ,SAASC,CAgBtB,CACN;CACE,CAAA;AAET;AAEA,SAASE,EAAQ,EACf,SACA,QAAQC,GACR,SACA,WACA,iBAOC;CACD,IAAM,EAAE,mBAAgBX,EAAY,GAC9BY,IACJ,kBAACD,GAAD;EACE,MAAMnB,EAAK,QAAQA,EAAK,OAAO,MAAM,MAAMqB,EAAE,IAAI,CAAC,EAAE;EACpD,gBAAcrB,EAAK,SAAS,SAAS,KAAA;EACrC,cAAYS,IAAOT,EAAK,QAAQ,KAAA;EAChC,SAASsB;EACT,WAAWlB,EAAGD,EAASE,GAAYL,EAAK,IAAI,GAAGS,KAAQ,gCAAgCc,KAAU,OAAO;EAL1G,UAAA;GAOGvB,EAAK,QAAQ,kBAACwB,GAAD;IAAM,MAAMxB,EAAK;IAAM,MAAM;IAAI,WAAU;GAAY,CAAA;GACpE,CAACS,KAAQ,kBAAC,QAAD;IAAM,WAAU;IAA2B,UAAAT,EAAK;GAAY,CAAA;GACrE,CAACS,KAAQT,EAAK;GACdS,KAAQT,EAAK,SACZ,kBAAC,QAAD;IAAM,eAAY;IAAO,WAAU;GAAkE,CAAA;EAEnG;;CAER,OAAOS,IACL,kBAACgB,GAAD;EAAS,SAASzB,EAAK;EAAO,MAAK;EAChC,UAAAoB;CACM,CAAA,IAETA;AAEJ;AAEA,SAASH,EAAO,EACd,SACA,WACA,SACA,iBAMC;CAUD,OATI,CAACjB,EAAK,OAAO,UAAUS,IAEvB,kBAACS,GAAD;EACE,MAAM;GAAE,GAAGlB;GAAM,QAAQS,IAAOV,EAAUC,CAAI,IAAIA,EAAK;EAAO;EACtD;EACF;EACM;CACb,CAAA,IAGH,kBAACF,EAAiB,MAAlB;EAAuB,aAAaC,EAAUC,CAAI;EAAlD,UAAA,CACE,kBAACF,EAAiB,SAAlB;GACE,WAAWM,EAAGD,EAAS,QAAQH,EAAK,IAAI,GAAG,wDAAwD;GADrG,UAAA;IAGGA,EAAK,QAAQ,kBAACwB,GAAD;KAAM,MAAMxB,EAAK;KAAM,MAAM;KAAI,WAAU;IAAY,CAAA;IACrE,kBAAC,QAAD;KAAM,WAAU;KAA2B,UAAAA,EAAK;IAAY,CAAA;IAC3DA,EAAK;IACN,kBAAC0B,GAAD;KACE,MAAK;KACL,MAAM;KACN,WAAU;IACX,CAAA;GACuB;EAC1B,CAAA,GAAA,kBAAC5B,EAAiB,SAAlB;GAA0B,WAAU;GAClC,UAAA,kBAAC,MAAD;IAAI,WAAU;IACX,UAAAE,EAAK,MAAM,KAAK,MACf,kBAAC,MAAD,EAAA,UACE,kBAACkB,GAAD;KAAS,MAAMS;KAAe;KAAQ,MAAM;KAAO,QAAA;KAAmB;IAAa,CAAA,EACjF,GAFKA,EAAM,KAEX,CACL;GACC,CAAA;EACoB,CAAA,CACL;;AAE3B"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type ElementType, type ReactNode } from 'react';
|
|
2
|
+
export interface SiteLink {
|
|
3
|
+
label: string;
|
|
4
|
+
href: string;
|
|
5
|
+
active?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface SiteHeaderProps {
|
|
8
|
+
/** Logo linked to home, e.g. `<a href="/"><Logo collapse="scroll" /></a>` (the Logo already names the link "Kodffe"). */
|
|
9
|
+
logo: ReactNode;
|
|
10
|
+
/** Primary links. Not used by `variant="minimal"`. */
|
|
11
|
+
links?: SiteLink[];
|
|
12
|
+
/** Call-to-action area (e.g. "Book a call" Button). Shown at the bottom of the mobile menu too. */
|
|
13
|
+
actions?: ReactNode;
|
|
14
|
+
/** Compact call to action kept in the bar below 768px, next to the menu button (e.g. a small "Book a call"). */
|
|
15
|
+
mobileAction?: ReactNode;
|
|
16
|
+
linkAs?: ElementType;
|
|
17
|
+
/** `default`: the page background, translucent. `brand`: a solid brand bar with white text (product sites). */
|
|
18
|
+
tone?: 'default' | 'brand';
|
|
19
|
+
/** `full` (default): logo, links and actions. `minimal`: logo and actions only (sign-in, checkout, landing pages). */
|
|
20
|
+
variant?: 'full' | 'minimal';
|
|
21
|
+
labels?: {
|
|
22
|
+
nav?: string;
|
|
23
|
+
openMenu?: string;
|
|
24
|
+
menu?: string;
|
|
25
|
+
};
|
|
26
|
+
className?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Marketing header for kodffe.com: logo, primary links, CTA. Sticky with a translucent background.
|
|
30
|
+
* Below 960px the links move into a right drawer with large tap targets.
|
|
31
|
+
*/
|
|
32
|
+
export declare function SiteHeader({ logo, links, actions, mobileAction, linkAs: Link, tone, variant, labels, className, }: SiteHeaderProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cx as e } from "../lib/cx.js";
|
|
3
|
+
import { Drawer as t, DrawerContent as n, DrawerTrigger as r } from "../drawer/drawer.js";
|
|
4
|
+
import { Glyph as i } from "../lib/glyphs.js";
|
|
5
|
+
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
6
|
+
import { useState as s } from "react";
|
|
7
|
+
//#region src/site-header/site-header.tsx
|
|
8
|
+
function c({ logo: c, links: l = [], actions: u, mobileAction: d, linkAs: f = "a", tone: p = "default", variant: m = "full", labels: h = {}, className: g }) {
|
|
9
|
+
let { nav: _ = "Main", openMenu: v = "Open menu", menu: y = "Menu" } = h, [b, x] = s(!1), S = p === "brand", C = e("sticky top-0 z-40 font-sans", S ? "bg-primary-strong text-fg" : "border-b border-border bg-bg/85 backdrop-blur-md", g), w = e("shrink-0 text-2xl", S ? "text-fg" : "text-logo");
|
|
10
|
+
return m === "minimal" ? /* @__PURE__ */ a("header", {
|
|
11
|
+
...S ? { "data-theme": "dark" } : {},
|
|
12
|
+
className: C,
|
|
13
|
+
children: /* @__PURE__ */ o("div", {
|
|
14
|
+
className: "mx-auto flex h-16 max-w-[75rem] items-center justify-between gap-6 px-gutter",
|
|
15
|
+
children: [/* @__PURE__ */ a("div", {
|
|
16
|
+
className: w,
|
|
17
|
+
children: c
|
|
18
|
+
}), u && /* @__PURE__ */ a("div", {
|
|
19
|
+
className: "flex items-center gap-3",
|
|
20
|
+
children: u
|
|
21
|
+
})]
|
|
22
|
+
})
|
|
23
|
+
}) : /* @__PURE__ */ a("header", {
|
|
24
|
+
...S ? { "data-theme": "dark" } : {},
|
|
25
|
+
className: C,
|
|
26
|
+
children: /* @__PURE__ */ o("div", {
|
|
27
|
+
className: "mx-auto flex h-16 max-w-[75rem] items-center gap-6 px-gutter md:h-[4.5rem]",
|
|
28
|
+
children: [
|
|
29
|
+
/* @__PURE__ */ a("div", {
|
|
30
|
+
className: w,
|
|
31
|
+
children: c
|
|
32
|
+
}),
|
|
33
|
+
/* @__PURE__ */ a("nav", {
|
|
34
|
+
"aria-label": _,
|
|
35
|
+
className: "hidden flex-1 justify-center md:flex",
|
|
36
|
+
children: /* @__PURE__ */ a("ul", {
|
|
37
|
+
className: "m-0 flex list-none gap-1 p-0",
|
|
38
|
+
children: l.map((e) => /* @__PURE__ */ a("li", { children: /* @__PURE__ */ a(f, {
|
|
39
|
+
href: e.href,
|
|
40
|
+
"aria-current": e.active ? "page" : void 0,
|
|
41
|
+
className: "rounded-full px-4 py-2 text-[15px] font-medium text-fg-muted no-underline hover:bg-surface-muted hover:text-fg focus-visible:outline-3 focus-visible:outline-focus aria-[current=page]:text-fg aria-[current=page]:font-semibold",
|
|
42
|
+
children: e.label
|
|
43
|
+
}) }, e.href))
|
|
44
|
+
})
|
|
45
|
+
}),
|
|
46
|
+
/* @__PURE__ */ a("div", {
|
|
47
|
+
className: "ml-auto hidden items-center gap-3 md:flex",
|
|
48
|
+
children: u
|
|
49
|
+
}),
|
|
50
|
+
d && /* @__PURE__ */ a("div", {
|
|
51
|
+
className: "ml-auto md:hidden",
|
|
52
|
+
children: d
|
|
53
|
+
}),
|
|
54
|
+
/* @__PURE__ */ o(t, {
|
|
55
|
+
open: b,
|
|
56
|
+
onOpenChange: x,
|
|
57
|
+
children: [/* @__PURE__ */ a(r, {
|
|
58
|
+
asChild: !0,
|
|
59
|
+
children: /* @__PURE__ */ a("button", {
|
|
60
|
+
type: "button",
|
|
61
|
+
"aria-label": v,
|
|
62
|
+
className: e("-mr-2 grid size-11 cursor-pointer place-items-center rounded-full border-0 bg-transparent text-fg hover:bg-surface-muted focus-visible:outline-3 focus-visible:outline-focus md:hidden", !d && "ml-auto"),
|
|
63
|
+
children: /* @__PURE__ */ a(i, {
|
|
64
|
+
name: "menu",
|
|
65
|
+
size: 24
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
}), /* @__PURE__ */ o(n, {
|
|
69
|
+
side: "right",
|
|
70
|
+
size: "sm",
|
|
71
|
+
title: y,
|
|
72
|
+
hideTitle: !0,
|
|
73
|
+
bodyClassName: "flex flex-col",
|
|
74
|
+
children: [/* @__PURE__ */ a("nav", {
|
|
75
|
+
"aria-label": _,
|
|
76
|
+
children: /* @__PURE__ */ a("ul", {
|
|
77
|
+
className: "m-0 grid list-none gap-1 p-0",
|
|
78
|
+
children: l.map((e) => /* @__PURE__ */ a("li", { children: /* @__PURE__ */ a(f, {
|
|
79
|
+
href: e.href,
|
|
80
|
+
"aria-current": e.active ? "page" : void 0,
|
|
81
|
+
onClick: () => x(!1),
|
|
82
|
+
className: "flex min-h-12 items-center rounded-sm px-3 font-display text-xl font-bold text-fg no-underline hover:bg-surface-muted focus-visible:outline-3 focus-visible:outline-focus aria-[current=page]:text-link",
|
|
83
|
+
children: e.label
|
|
84
|
+
}) }, e.href))
|
|
85
|
+
})
|
|
86
|
+
}), u && /* @__PURE__ */ a("div", {
|
|
87
|
+
className: "mt-auto grid gap-3 pt-8 *:w-full",
|
|
88
|
+
children: u
|
|
89
|
+
})]
|
|
90
|
+
})]
|
|
91
|
+
})
|
|
92
|
+
]
|
|
93
|
+
})
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
export { c as SiteHeader };
|
|
98
|
+
|
|
99
|
+
//# sourceMappingURL=site-header.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-header.js","names":["SiteHeader","Link","labels","open","setOpen","useState","brand","tone","shell","cx","className","logoBox","variant","logo","actions","nav","links","l","mobileAction","Drawer","DrawerTrigger","openMenu","Glyph","DrawerContent","menu"],"sources":["../../src/site-header/site-header.tsx"],"sourcesContent":["'use client';\n\nimport { useState, type ElementType, type ReactNode } from 'react';\nimport { cx } from '../lib/cx';\nimport { Glyph } from '../lib/glyphs';\nimport { Drawer, DrawerContent, DrawerTrigger } from '../drawer/drawer';\n\nexport interface SiteLink {\n label: string;\n href: string;\n active?: boolean;\n}\n\nexport interface SiteHeaderProps {\n /** Logo linked to home, e.g. `<a href=\"/\"><Logo collapse=\"scroll\" /></a>` (the Logo already names the link \"Kodffe\"). */\n logo: ReactNode;\n /** Primary links. Not used by `variant=\"minimal\"`. */\n links?: SiteLink[];\n /** Call-to-action area (e.g. \"Book a call\" Button). Shown at the bottom of the mobile menu too. */\n actions?: ReactNode;\n /** Compact call to action kept in the bar below 768px, next to the menu button (e.g. a small \"Book a call\"). */\n mobileAction?: ReactNode;\n linkAs?: ElementType;\n /** `default`: the page background, translucent. `brand`: a solid brand bar with white text (product sites). */\n tone?: 'default' | 'brand';\n /** `full` (default): logo, links and actions. `minimal`: logo and actions only (sign-in, checkout, landing pages). */\n variant?: 'full' | 'minimal';\n labels?: { nav?: string; openMenu?: string; menu?: string };\n className?: string;\n}\n\n/**\n * Marketing header for kodffe.com: logo, primary links, CTA. Sticky with a translucent background.\n * Below 960px the links move into a right drawer with large tap targets.\n */\nexport function SiteHeader({\n logo,\n links = [],\n actions,\n mobileAction,\n linkAs: Link = 'a',\n tone = 'default',\n variant = 'full',\n labels = {},\n className,\n}: SiteHeaderProps) {\n const { nav = 'Main', openMenu = 'Open menu', menu = 'Menu' } = labels;\n const [open, setOpen] = useState(false);\n const brand = tone === 'brand';\n const shell = cx(\n 'sticky top-0 z-40 font-sans',\n // The brand bar is dark in both themes: the dark token set keeps text, links and focus rings AA.\n brand ? 'bg-primary-strong text-fg' : 'border-b border-border bg-bg/85 backdrop-blur-md',\n className,\n );\n const logoBox = cx('shrink-0 text-2xl', brand ? 'text-fg' : 'text-logo');\n if (variant === 'minimal') {\n return (\n <header {...(brand ? { 'data-theme': 'dark' } : {})} className={shell}>\n <div className=\"mx-auto flex h-16 max-w-[75rem] items-center justify-between gap-6 px-gutter\">\n <div className={logoBox}>{logo}</div>\n {actions && <div className=\"flex items-center gap-3\">{actions}</div>}\n </div>\n </header>\n );\n }\n return (\n <header {...(brand ? { 'data-theme': 'dark' } : {})} className={shell}>\n <div className=\"mx-auto flex h-16 max-w-[75rem] items-center gap-6 px-gutter md:h-[4.5rem]\">\n <div className={logoBox}>{logo}</div>\n <nav aria-label={nav} className=\"hidden flex-1 justify-center md:flex\">\n <ul className=\"m-0 flex list-none gap-1 p-0\">\n {links.map((l) => (\n <li key={l.href}>\n <Link\n href={l.href}\n aria-current={l.active ? 'page' : undefined}\n className=\"rounded-full px-4 py-2 text-[15px] font-medium text-fg-muted no-underline hover:bg-surface-muted hover:text-fg focus-visible:outline-3 focus-visible:outline-focus aria-[current=page]:text-fg aria-[current=page]:font-semibold\"\n >\n {l.label}\n </Link>\n </li>\n ))}\n </ul>\n </nav>\n <div className=\"ml-auto hidden items-center gap-3 md:flex\">{actions}</div>\n {mobileAction && <div className=\"ml-auto md:hidden\">{mobileAction}</div>}\n <Drawer open={open} onOpenChange={setOpen}>\n <DrawerTrigger asChild>\n <button\n type=\"button\"\n aria-label={openMenu}\n className={cx(\n '-mr-2 grid size-11 cursor-pointer place-items-center rounded-full border-0 bg-transparent text-fg hover:bg-surface-muted focus-visible:outline-3 focus-visible:outline-focus md:hidden',\n !mobileAction && 'ml-auto',\n )}\n >\n <Glyph name=\"menu\" size={24} />\n </button>\n </DrawerTrigger>\n <DrawerContent side=\"right\" size=\"sm\" title={menu} hideTitle bodyClassName=\"flex flex-col\">\n <nav aria-label={nav}>\n <ul className=\"m-0 grid list-none gap-1 p-0\">\n {links.map((l) => (\n <li key={l.href}>\n <Link\n href={l.href}\n aria-current={l.active ? 'page' : undefined}\n onClick={() => setOpen(false)}\n className=\"flex min-h-12 items-center rounded-sm px-3 font-display text-xl font-bold text-fg no-underline hover:bg-surface-muted focus-visible:outline-3 focus-visible:outline-focus aria-[current=page]:text-link\"\n >\n {l.label}\n </Link>\n </li>\n ))}\n </ul>\n </nav>\n {actions && <div className=\"mt-auto grid gap-3 pt-8 *:w-full\">{actions}</div>}\n </DrawerContent>\n </Drawer>\n </div>\n </header>\n );\n}\n"],"mappings":";;;;;;;AAmCA,SAAgBA,EAAW,EACzB,SACA,WAAQ,CAAC,GACT,YACA,iBACA,QAAQC,IAAO,KACf,UAAO,WACP,aAAU,QACV,YAAS,CAAC,GACV,gBACkB;CAClB,IAAM,EAAE,SAAM,QAAQ,cAAW,aAAa,UAAO,WAAWC,GAC1D,CAACC,GAAMC,KAAWC,EAAS,EAAK,GAChCC,IAAQC,MAAS,SACjBC,IAAQC,EACZ,+BAEAH,IAAQ,8BAA8B,oDACtCI,CACF,GACMC,IAAUF,EAAG,qBAAqBH,IAAQ,YAAY,WAAW;CAWvE,OAVIM,MAAY,YAEZ,kBAAC,UAAD;EAAQ,GAAKN,IAAQ,EAAE,cAAc,OAAO,IAAI,CAAC;EAAI,WAAWE;EAC9D,UAAA,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,kBAAC,OAAD;IAAK,WAAWG;IAAU,UAAAE;GAAU,CAAA,GACnCC,KAAW,kBAAC,OAAD;IAAK,WAAU;IAA2B,UAAAA;GAAa,CAAA,CAChE;;CACC,CAAA,IAIV,kBAAC,UAAD;EAAQ,GAAKR,IAAQ,EAAE,cAAc,OAAO,IAAI,CAAC;EAAI,WAAWE;EAC9D,UAAA,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IACE,kBAAC,OAAD;KAAK,WAAWG;KAAU,UAAAE;IAAU,CAAA;IACpC,kBAAC,OAAD;KAAK,cAAYE;KAAK,WAAU;KAC9B,UAAA,kBAAC,MAAD;MAAI,WAAU;MACX,UAAAC,EAAM,KAAK,MACV,kBAAC,MAAD,EAAA,UACE,kBAACf,GAAD;OACE,MAAMgB,EAAE;OACR,gBAAcA,EAAE,SAAS,SAAS,KAAA;OAClC,WAAU;OAET,UAAAA,EAAE;MACC,CAAA,EACJ,GARKA,EAAE,IAQP,CACL;KACC,CAAA;IACD,CAAA;IACL,kBAAC,OAAD;KAAK,WAAU;KAA6C,UAAAH;IAAa,CAAA;IACxEI,KAAgB,kBAAC,OAAD;KAAK,WAAU;KAAqB,UAAAA;IAAkB,CAAA;IACvE,kBAACC,GAAD;KAAc;KAAM,cAAcf;KAAlC,UAAA,CACE,kBAACgB,GAAD;MAAe,SAAA;MACb,UAAA,kBAAC,UAAD;OACE,MAAK;OACL,cAAYC;OACZ,WAAWZ,EACT,0LACA,CAACS,KAAgB,SACnB;OAEA,UAAA,kBAACI,GAAD;QAAO,MAAK;QAAO,MAAM;OAAK,CAAA;MACxB,CAAA;KACK,CAAA,GACf,kBAACC,GAAD;MAAe,MAAK;MAAQ,MAAK;MAAK,OAAOC;MAAM,WAAA;MAAU,eAAc;MAA3E,UAAA,CACE,kBAAC,OAAD;OAAK,cAAYT;OACf,UAAA,kBAAC,MAAD;QAAI,WAAU;QACX,UAAAC,EAAM,KAAK,MACV,kBAAC,MAAD,EAAA,UACE,kBAACf,GAAD;SACE,MAAMgB,EAAE;SACR,gBAAcA,EAAE,SAAS,SAAS,KAAA;SAClC,eAAeb,EAAQ,EAAK;SAC5B,WAAU;SAET,UAAAa,EAAE;QACC,CAAA,EACJ,GATKA,EAAE,IASP,CACL;OACC,CAAA;MACD,CAAA,GACJH,KAAW,kBAAC,OAAD;OAAK,WAAU;OAAoC,UAAAA;MAAa,CAAA,CAC/D;KACT,CAAA,CAAA;;GACL;;CACC,CAAA;AAEZ"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react';
|
|
2
|
+
export type SkeletonShape = 'text' | 'circle' | 'rect';
|
|
3
|
+
export interface SkeletonProps extends HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
shape?: SkeletonShape;
|
|
5
|
+
/** For `text`: number of lines; the last one is shorter. */
|
|
6
|
+
lines?: number;
|
|
7
|
+
/** Width/height via classes (`w-40 h-24`, `size-10`) or style. */
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Placeholder that mirrors the layout of content while it loads, so the page does not jump.
|
|
12
|
+
* Decorative (`aria-hidden`): give the loading container `role="status"`, `aria-busy="true"` and an
|
|
13
|
+
* `aria-label` such as "Loading runs", and remove them when the content arrives.
|
|
14
|
+
* Actions use `Spinner` (small) or `Mascot` (40 px or more); skeletons are for content.
|
|
15
|
+
*/
|
|
16
|
+
export declare function Skeleton({ shape, lines, className, ...rest }: SkeletonProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { cx as e } from "../lib/cx.js";
|
|
2
|
+
import { jsx as t } from "react/jsx-runtime";
|
|
3
|
+
//#region src/skeleton/skeleton.tsx
|
|
4
|
+
function n({ shape: n = "rect", lines: r = 1, className: i, ...a }) {
|
|
5
|
+
return n === "text" ? /* @__PURE__ */ t("div", {
|
|
6
|
+
"aria-hidden": "true",
|
|
7
|
+
className: e("grid w-full gap-2", i),
|
|
8
|
+
...a,
|
|
9
|
+
children: Array.from({ length: r }, (n, i) => /* @__PURE__ */ t("div", { className: e("kf-skeleton h-3.5 rounded-full", i === r - 1 && r > 1 ? "w-3/5" : "w-full") }, i))
|
|
10
|
+
}) : /* @__PURE__ */ t("div", {
|
|
11
|
+
"aria-hidden": "true",
|
|
12
|
+
className: e("kf-skeleton", n === "circle" ? "size-10 rounded-full" : "h-24 w-full rounded-md", i),
|
|
13
|
+
...a
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { n as Skeleton };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=skeleton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skeleton.js","names":["Skeleton","rest","shape","cx","className","lines"],"sources":["../../src/skeleton/skeleton.tsx"],"sourcesContent":["import type { HTMLAttributes } from 'react';\nimport { cx } from '../lib/cx';\n\nexport type SkeletonShape = 'text' | 'circle' | 'rect';\n\nexport interface SkeletonProps extends HTMLAttributes<HTMLDivElement> {\n shape?: SkeletonShape;\n /** For `text`: number of lines; the last one is shorter. */\n lines?: number;\n /** Width/height via classes (`w-40 h-24`, `size-10`) or style. */\n className?: string;\n}\n\n/**\n * Placeholder that mirrors the layout of content while it loads, so the page does not jump.\n * Decorative (`aria-hidden`): give the loading container `role=\"status\"`, `aria-busy=\"true\"` and an\n * `aria-label` such as \"Loading runs\", and remove them when the content arrives.\n * Actions use `Spinner` (small) or `Mascot` (40 px or more); skeletons are for content.\n */\nexport function Skeleton({ shape = 'rect', lines = 1, className, ...rest }: SkeletonProps) {\n if (shape === 'text') {\n return (\n <div aria-hidden=\"true\" className={cx('grid w-full gap-2', className)} {...rest}>\n {Array.from({ length: lines }, (_, i) => (\n <div key={i} className={cx('kf-skeleton h-3.5 rounded-full', i === lines - 1 && lines > 1 ? 'w-3/5' : 'w-full')} />\n ))}\n </div>\n );\n }\n return (\n <div\n aria-hidden=\"true\"\n className={cx('kf-skeleton', shape === 'circle' ? 'size-10 rounded-full' : 'h-24 w-full rounded-md', className)}\n {...rest}\n />\n );\n}\n"],"mappings":";;;AAmBA,SAAgBA,EAAS,EAAE,WAAQ,QAAQ,WAAQ,GAAG,cAAW,GAAGC,KAAuB;CAUzF,OATIC,MAAU,SAEV,kBAAC,OAAD;EAAK,eAAY;EAAO,WAAWC,EAAG,qBAAqBC,CAAS;EAAG,GAAIH;EACxE,UAAA,MAAM,KAAK,EAAE,QAAQI,EAAM,IAAI,GAAG,MACjC,kBAAC,OAAD,EAAa,WAAWF,EAAG,kCAAkC,MAAME,IAAQ,KAAKA,IAAQ,IAAI,UAAU,QAAQ,EAAI,GAAxG,CAAwG,CACnH;CACE,CAAA,IAIP,kBAAC,OAAD;EACE,eAAY;EACZ,WAAWF,EAAG,eAAeD,MAAU,WAAW,yBAAyB,0BAA0BE,CAAS;EAC9G,GAAIH;CACL,CAAA;AAEL"}
|