@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,61 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cx as e } from "../lib/cx.js";
|
|
3
|
+
import { useFieldProps as t } from "../input/input.js";
|
|
4
|
+
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
5
|
+
import { useRef as i } from "react";
|
|
6
|
+
//#region src/otp-input/otp-input.tsx
|
|
7
|
+
function a({ value: a, onChange: o, onComplete: s, length: c = 6, alphanumeric: l = !1, digitLabel: u = "Digit {n} of {count}", groupSize: d, className: f, ...p }) {
|
|
8
|
+
let m = t(p), h = i([]), g = l ? /[^a-z0-9]/gi : /\D/g, _ = Array.from({ length: c }, (e, t) => a[t] ?? ""), v = (e) => {
|
|
9
|
+
let t = e.replace(g, "").slice(0, c).toUpperCase();
|
|
10
|
+
return o(t), t.length === c && s?.(t), t;
|
|
11
|
+
}, y = (e) => h.current[Math.max(0, Math.min(c - 1, e))]?.focus(), b = (e, t) => {
|
|
12
|
+
let n = t.replace(g, "");
|
|
13
|
+
if (!n) return;
|
|
14
|
+
if (n.length > 1) {
|
|
15
|
+
let t = v(a.slice(0, e) + n);
|
|
16
|
+
y(t.length);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
let r = (a.slice(0, e) + n + a.slice(e + 1)).slice(0, c);
|
|
20
|
+
v(r), y(e + 1);
|
|
21
|
+
}, x = (e, t) => {
|
|
22
|
+
t.key === "Backspace" ? (t.preventDefault(), _[e] ? v(a.slice(0, e) + a.slice(e + 1)) : e > 0 && (v(a.slice(0, e - 1) + a.slice(e)), y(e - 1))) : t.key === "ArrowLeft" ? y(e - 1) : t.key === "ArrowRight" && y(e + 1);
|
|
23
|
+
}, S = (e) => {
|
|
24
|
+
e.preventDefault();
|
|
25
|
+
let t = v(e.clipboardData.getData("text"));
|
|
26
|
+
y(t.length);
|
|
27
|
+
};
|
|
28
|
+
return /* @__PURE__ */ n("div", {
|
|
29
|
+
role: "group",
|
|
30
|
+
"aria-describedby": m["aria-describedby"],
|
|
31
|
+
className: e("flex items-center gap-2 font-sans", f),
|
|
32
|
+
children: _.map((t, i) => /* @__PURE__ */ r("span", {
|
|
33
|
+
className: e("contents"),
|
|
34
|
+
children: [d && i > 0 && i % d === 0 && /* @__PURE__ */ n("span", {
|
|
35
|
+
"aria-hidden": "true",
|
|
36
|
+
className: "mx-1 h-0.5 w-3 rounded-full bg-border"
|
|
37
|
+
}), /* @__PURE__ */ n("input", {
|
|
38
|
+
ref: (e) => {
|
|
39
|
+
h.current[i] = e;
|
|
40
|
+
},
|
|
41
|
+
id: i === 0 ? m.id : void 0,
|
|
42
|
+
value: t,
|
|
43
|
+
inputMode: l ? "text" : "numeric",
|
|
44
|
+
autoComplete: i === 0 ? "one-time-code" : "off",
|
|
45
|
+
maxLength: i === 0 ? c : 1,
|
|
46
|
+
"aria-label": u.replace("{n}", String(i + 1)).replace("{count}", String(c)),
|
|
47
|
+
"aria-invalid": m["aria-invalid"],
|
|
48
|
+
disabled: m.disabled,
|
|
49
|
+
onChange: (e) => b(i, e.target.value),
|
|
50
|
+
onKeyDown: (e) => x(i, e),
|
|
51
|
+
onPaste: S,
|
|
52
|
+
onFocus: (e) => e.target.select(),
|
|
53
|
+
className: e("size-12 rounded-sm border bg-bg text-center font-mono text-xl font-bold text-fg caret-primary outline-none sm:size-13", m.invalid ? "border-danger-border" : "border-border", "focus:border-primary focus:shadow-[0_0_0_3px_color-mix(in_oklab,var(--color-focus),transparent_70%)] disabled:opacity-60")
|
|
54
|
+
})]
|
|
55
|
+
}, i))
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { a as OtpInput };
|
|
60
|
+
|
|
61
|
+
//# sourceMappingURL=otp-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otp-input.js","names":["OtpInput","rest","field","useFieldProps","refs","useRef","pattern","alphanumeric","chars","value","i","commit","clean","next","length","onChange","onComplete","focus","onInput","typed","raw","nextValue","onKeyDown","e","onPaste","cx","className","groupSize","el","c","digitLabel"],"sources":["../../src/otp-input/otp-input.tsx"],"sourcesContent":["'use client';\n\nimport { useRef, type ClipboardEvent, type KeyboardEvent } from 'react';\nimport { cx } from '../lib/cx';\nimport { useFieldProps } from '../input/input';\n\nexport interface OtpInputProps {\n value: string;\n onChange: (value: string) => void;\n /** Called once all digits are filled (e.g. submit automatically). */\n onComplete?: (value: string) => void;\n length?: number;\n /** Allow letters as well as digits. */\n alphanumeric?: boolean;\n id?: string;\n disabled?: boolean;\n invalid?: boolean;\n /** Accessible name pattern for each box; `{n}` and `{count}` are replaced. */\n digitLabel?: string;\n /** Visual gap after this many boxes (e.g. 3 → \"123 456\"). */\n groupSize?: number;\n className?: string;\n}\n\n/**\n * One-time code entry (2FA, email verification). Typing advances, Backspace goes back,\n * pasting a full code fills every box, and `autocomplete=\"one-time-code\"` lets phones suggest\n * the code from SMS.\n */\nexport function OtpInput({\n value,\n onChange,\n onComplete,\n length = 6,\n alphanumeric = false,\n digitLabel = 'Digit {n} of {count}',\n groupSize,\n className,\n ...rest\n}: OtpInputProps) {\n const field = useFieldProps(rest);\n const refs = useRef<(HTMLInputElement | null)[]>([]);\n const pattern = alphanumeric ? /[^a-z0-9]/gi : /\\D/g;\n const chars = Array.from({ length }, (_, i) => value[i] ?? '');\n\n const commit = (next: string) => {\n const clean = next.replace(pattern, '').slice(0, length).toUpperCase();\n onChange(clean);\n if (clean.length === length) onComplete?.(clean);\n return clean;\n };\n\n const focus = (i: number) => refs.current[Math.max(0, Math.min(length - 1, i))]?.focus();\n\n const onInput = (i: number, raw: string) => {\n const typed = raw.replace(pattern, '');\n if (!typed) return;\n if (typed.length > 1) {\n // Autofill or paste into one box: spread from this position.\n const clean = commit(value.slice(0, i) + typed);\n focus(clean.length);\n return;\n }\n const nextValue = (value.slice(0, i) + typed + value.slice(i + 1)).slice(0, length);\n commit(nextValue);\n focus(i + 1);\n };\n\n const onKeyDown = (i: number, e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Backspace') {\n e.preventDefault();\n if (chars[i]) commit(value.slice(0, i) + value.slice(i + 1));\n else if (i > 0) {\n commit(value.slice(0, i - 1) + value.slice(i));\n focus(i - 1);\n }\n } else if (e.key === 'ArrowLeft') focus(i - 1);\n else if (e.key === 'ArrowRight') focus(i + 1);\n };\n\n const onPaste = (e: ClipboardEvent<HTMLInputElement>) => {\n e.preventDefault();\n const clean = commit(e.clipboardData.getData('text'));\n focus(clean.length);\n };\n\n return (\n <div\n role=\"group\"\n aria-describedby={field['aria-describedby']}\n className={cx('flex items-center gap-2 font-sans', className)}\n >\n {chars.map((c, i) => (\n <span key={i} className={cx('contents')}>\n {groupSize && i > 0 && i % groupSize === 0 && (\n <span aria-hidden=\"true\" className=\"mx-1 h-0.5 w-3 rounded-full bg-border\" />\n )}\n <input\n ref={(el) => {\n refs.current[i] = el;\n }}\n id={i === 0 ? field.id : undefined}\n value={c}\n inputMode={alphanumeric ? 'text' : 'numeric'}\n autoComplete={i === 0 ? 'one-time-code' : 'off'}\n maxLength={i === 0 ? length : 1}\n aria-label={digitLabel.replace('{n}', String(i + 1)).replace('{count}', String(length))}\n aria-invalid={field['aria-invalid']}\n disabled={field.disabled}\n onChange={(e) => onInput(i, e.target.value)}\n onKeyDown={(e) => onKeyDown(i, e)}\n onPaste={onPaste}\n onFocus={(e) => e.target.select()}\n className={cx(\n 'size-12 rounded-sm border bg-bg text-center font-mono text-xl font-bold text-fg caret-primary outline-none sm:size-13',\n field.invalid ? 'border-danger-border' : 'border-border',\n 'focus:border-primary focus:shadow-[0_0_0_3px_color-mix(in_oklab,var(--color-focus),transparent_70%)] disabled:opacity-60',\n )}\n />\n </span>\n ))}\n </div>\n );\n}\n"],"mappings":";;;;;;AA6BA,SAAgBA,EAAS,EACvB,UACA,aACA,eACA,YAAS,GACT,kBAAe,IACf,gBAAa,wBACb,cACA,cACA,GAAGC,KACa;CAChB,IAAMC,IAAQC,EAAcF,CAAI,GAC1BG,IAAOC,EAAoC,CAAC,CAAC,GAC7CC,IAAUC,IAAe,gBAAgB,OACzCC,IAAQ,MAAM,KAAK,EAAE,UAAO,IAAI,GAAG,MAAMC,EAAMC,MAAM,EAAE,GAEvDC,KAAU,MAAiB;EAC/B,IAAMC,IAAQC,EAAK,QAAQP,GAAS,EAAE,CAAC,CAAC,MAAM,GAAGQ,CAAM,CAAC,CAAC,YAAY;EAGrE,OAFAC,EAASH,CAAK,GACVA,EAAM,WAAWE,KAAQE,IAAaJ,CAAK,GACxCA;CACT,GAEMK,KAAS,MAAcb,EAAK,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAIU,IAAS,GAAGJ,CAAC,CAAC,EAAE,EAAE,MAAM,GAEjFQ,KAAW,GAAW,MAAgB;EAC1C,IAAMC,IAAQC,EAAI,QAAQd,GAAS,EAAE;EACrC,IAAI,CAACa,GAAO;EACZ,IAAIA,EAAM,SAAS,GAAG;GAEpB,IAAMP,IAAQD,EAAOF,EAAM,MAAM,GAAGC,CAAC,IAAIS,CAAK;GAC9C,EAAMP,EAAM,MAAM;GAClB;EACF;EACA,IAAMS,KAAaZ,EAAM,MAAM,GAAGC,CAAC,IAAIS,IAAQV,EAAM,MAAMC,IAAI,CAAC,EAAA,CAAG,MAAM,GAAGI,CAAM;EAElF,AADAH,EAAOU,CAAS,GAChBJ,EAAMP,IAAI,CAAC;CACb,GAEMY,KAAa,GAAW,MAAuC;EACnE,AAAIC,EAAE,QAAQ,eACZA,EAAE,eAAe,GACbf,EAAME,KAAIC,EAAOF,EAAM,MAAM,GAAGC,CAAC,IAAID,EAAM,MAAMC,IAAI,CAAC,CAAC,IAClDA,IAAI,MACXC,EAAOF,EAAM,MAAM,GAAGC,IAAI,CAAC,IAAID,EAAM,MAAMC,CAAC,CAAC,GAC7CO,EAAMP,IAAI,CAAC,MAEJa,EAAE,QAAQ,cAAaN,EAAMP,IAAI,CAAC,IACpCa,EAAE,QAAQ,gBAAcN,EAAMP,IAAI,CAAC;CAC9C,GAEMc,KAAW,MAAwC;EACvD,EAAE,eAAe;EACjB,IAAMZ,IAAQD,EAAO,EAAE,cAAc,QAAQ,MAAM,CAAC;EACpD,EAAMC,EAAM,MAAM;CACpB;CAEA,OACE,kBAAC,OAAD;EACE,MAAK;EACL,oBAAkBV,EAAM;EACxB,WAAWuB,EAAG,qCAAqCC,CAAS;EAE3D,UAAAlB,EAAM,KAAK,GAAG,MACb,kBAAC,QAAD;GAAc,WAAWiB,EAAG,UAAU;GAAtC,UAAA,CACGE,KAAa,IAAI,KAAK,IAAIA,MAAc,KACvC,kBAAC,QAAD;IAAM,eAAY;IAAO,WAAU;GAAyC,CAAA,GAE9E,kBAAC,SAAD;IACE,MAAM,MAAO;KACX,EAAK,QAAQ,KAAKC;IACpB;IACA,IAAI,MAAM,IAAI1B,EAAM,KAAK,KAAA;IACzB,OAAO2B;IACP,WAAWtB,IAAe,SAAS;IACnC,cAAc,MAAM,IAAI,kBAAkB;IAC1C,WAAW,MAAM,IAAIO,IAAS;IAC9B,cAAYgB,EAAW,QAAQ,OAAO,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,WAAW,OAAOhB,CAAM,CAAC;IACtF,gBAAcZ,EAAM;IACpB,UAAUA,EAAM;IAChB,WAAW,MAAMgB,EAAQ,GAAG,EAAE,OAAO,KAAK;IAC1C,YAAY,MAAMI,EAAU,GAAG,CAAC;IACvB;IACT,UAAU,MAAM,EAAE,OAAO,OAAO;IAChC,WAAWG,EACT,yHACAvB,EAAM,UAAU,yBAAyB,iBACzC,0HACF;GACD,CAAA,CACG;EA1BK,GAAA,CA0BL,CACP;CACE,CAAA;AAET"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type PointerEvent as ReactPointerEvent, type ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Shared building blocks for dialog-based overlays (Modal, Drawer).
|
|
4
|
+
* Internal: not exported from the package index.
|
|
5
|
+
*/
|
|
6
|
+
export declare function OverlayBackdrop({ className }: {
|
|
7
|
+
className?: string;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
9
|
+
export declare function CloseButton({ label }: {
|
|
10
|
+
label: string;
|
|
11
|
+
}): import("react").JSX.Element;
|
|
12
|
+
export interface PanelHeaderProps {
|
|
13
|
+
title: ReactNode;
|
|
14
|
+
description?: ReactNode;
|
|
15
|
+
hideTitle?: boolean;
|
|
16
|
+
hideClose?: boolean;
|
|
17
|
+
closeLabel: string;
|
|
18
|
+
/** Extra props for the header element (e.g. swipe handlers). */
|
|
19
|
+
dragProps?: SwipeHandlers;
|
|
20
|
+
/** `brand`: a solid brand band with white text (dark token set, so the close button stays AA). */
|
|
21
|
+
tone?: 'default' | 'brand';
|
|
22
|
+
}
|
|
23
|
+
export declare function PanelHeader({ title, description, hideTitle, hideClose, closeLabel, dragProps, tone }: PanelHeaderProps): import("react").JSX.Element;
|
|
24
|
+
export declare function PanelBody({ children, className }: {
|
|
25
|
+
children?: ReactNode;
|
|
26
|
+
className?: string;
|
|
27
|
+
}): import("react").JSX.Element;
|
|
28
|
+
/** Footer actions: stacked full-width on mobile (primary action at the bottom, next to the thumb), a right-aligned row from `sm`. */
|
|
29
|
+
export declare function PanelFooter({ children }: {
|
|
30
|
+
children: ReactNode;
|
|
31
|
+
}): import("react").JSX.Element;
|
|
32
|
+
/** Visual drag handle for bottom sheets. */
|
|
33
|
+
export declare function SheetHandle({ className, ...handlers }: {
|
|
34
|
+
className?: string;
|
|
35
|
+
} & SwipeHandlers): import("react").JSX.Element;
|
|
36
|
+
export type SwipeDirection = 'down' | 'left' | 'right';
|
|
37
|
+
export interface SwipeHandlers {
|
|
38
|
+
onPointerDown?: (e: ReactPointerEvent<HTMLElement>) => void;
|
|
39
|
+
onPointerMove?: (e: ReactPointerEvent<HTMLElement>) => void;
|
|
40
|
+
onPointerUp?: (e: ReactPointerEvent<HTMLElement>) => void;
|
|
41
|
+
onPointerCancel?: (e: ReactPointerEvent<HTMLElement>) => void;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Swipe-to-dismiss for sheets and drawers. The panel follows the finger; releasing past
|
|
45
|
+
* 25% of its size (or with a quick flick) dismisses it. The exit animation continues from
|
|
46
|
+
* the dragged position because the exit keyframes only define their end state.
|
|
47
|
+
*/
|
|
48
|
+
export declare function useSwipeDismiss(direction: SwipeDirection, onDismiss: () => void, { touchOnly }?: {
|
|
49
|
+
touchOnly?: boolean | undefined;
|
|
50
|
+
}): {
|
|
51
|
+
panelRef: import("react").RefObject<HTMLDivElement | null>;
|
|
52
|
+
handlers: SwipeHandlers;
|
|
53
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cx as e } from "../lib/cx.js";
|
|
3
|
+
import { jsx as t, jsxs as n } from "react/jsx-runtime";
|
|
4
|
+
import { useCallback as r, useRef as i } from "react";
|
|
5
|
+
import * as a from "@radix-ui/react-dialog";
|
|
6
|
+
//#region src/overlay/overlay.tsx
|
|
7
|
+
function o({ className: n }) {
|
|
8
|
+
return /* @__PURE__ */ t(a.Overlay, { className: e("kf-overlay fixed inset-0 z-50 bg-overlay", n) });
|
|
9
|
+
}
|
|
10
|
+
function s({ label: n }) {
|
|
11
|
+
return /* @__PURE__ */ t(a.Close, {
|
|
12
|
+
"aria-label": n,
|
|
13
|
+
className: e("-mr-2 -mt-1 grid size-10 shrink-0 cursor-pointer place-items-center rounded-full text-fg-muted", "transition-colors duration-150 hover:bg-surface-muted hover:text-fg", "focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-focus"),
|
|
14
|
+
children: /* @__PURE__ */ t("svg", {
|
|
15
|
+
viewBox: "0 0 24 24",
|
|
16
|
+
width: "20",
|
|
17
|
+
height: "20",
|
|
18
|
+
"aria-hidden": "true",
|
|
19
|
+
focusable: "false",
|
|
20
|
+
children: /* @__PURE__ */ t("path", {
|
|
21
|
+
d: "M6 6l12 12M18 6L6 18",
|
|
22
|
+
stroke: "currentColor",
|
|
23
|
+
strokeWidth: "2.25",
|
|
24
|
+
strokeLinecap: "round",
|
|
25
|
+
fill: "none"
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function c({ title: r, description: i, hideTitle: o, hideClose: c, closeLabel: l, dragProps: u, tone: d = "default" }) {
|
|
31
|
+
let f = !o || !c, p = d === "brand" && f;
|
|
32
|
+
return /* @__PURE__ */ n("div", {
|
|
33
|
+
...p ? { "data-theme": "dark" } : {},
|
|
34
|
+
className: e("flex items-start gap-4 px-6", p ? "mb-5 bg-primary-strong pt-6 pb-5 text-fg" : f ? "pt-6 pb-4" : "pt-2"),
|
|
35
|
+
...u,
|
|
36
|
+
children: [/* @__PURE__ */ n("div", {
|
|
37
|
+
className: e("min-w-0 flex-1", o && "sr-only"),
|
|
38
|
+
children: [/* @__PURE__ */ t(a.Title, {
|
|
39
|
+
className: "m-0 font-display text-xl font-bold tracking-tight text-balance text-fg",
|
|
40
|
+
children: r
|
|
41
|
+
}), i && /* @__PURE__ */ t(a.Description, {
|
|
42
|
+
className: "m-0 mt-1 text-sm text-fg-muted",
|
|
43
|
+
children: i
|
|
44
|
+
})]
|
|
45
|
+
}), !c && /* @__PURE__ */ t(s, { label: l })]
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function l({ children: n, className: r }) {
|
|
49
|
+
return /* @__PURE__ */ t("div", {
|
|
50
|
+
className: e("min-h-0 flex-1 overflow-y-auto overscroll-contain px-6 pb-6 text-[15px]", r),
|
|
51
|
+
children: n
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function u({ children: e }) {
|
|
55
|
+
return /* @__PURE__ */ t("div", {
|
|
56
|
+
className: "flex flex-col-reverse gap-2 border-t border-border px-6 py-4 *:w-full sm:flex-row sm:justify-end sm:*:w-auto",
|
|
57
|
+
children: e
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function d({ className: n, ...r }) {
|
|
61
|
+
return /* @__PURE__ */ t("div", {
|
|
62
|
+
"aria-hidden": "true",
|
|
63
|
+
className: e("flex cursor-grab touch-none justify-center pt-3 pb-1 active:cursor-grabbing", n),
|
|
64
|
+
...r,
|
|
65
|
+
children: /* @__PURE__ */ t("span", { className: "h-1.5 w-10 rounded-full bg-border" })
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function f(e, t, { touchOnly: n = !1 } = {}) {
|
|
69
|
+
let a = i(null), o = i(null), s = e === "down" ? "Y" : "X", c = e === "left" ? -1 : 1, l = (e) => s === "Y" ? e.clientY : e.clientX, u = r(() => {
|
|
70
|
+
let e = a.current;
|
|
71
|
+
e && (e.style.transition = "transform 200ms var(--ease-standard)", e.style.transform = "");
|
|
72
|
+
}, []);
|
|
73
|
+
return {
|
|
74
|
+
panelRef: a,
|
|
75
|
+
handlers: {
|
|
76
|
+
onPointerDown(e) {
|
|
77
|
+
e.button !== 0 || n && e.pointerType === "mouse" || e.target.closest("button, a, input, select, textarea") || (o.current = {
|
|
78
|
+
start: l(e),
|
|
79
|
+
time: performance.now(),
|
|
80
|
+
delta: 0
|
|
81
|
+
}, e.currentTarget.setPointerCapture(e.pointerId), a.current && (a.current.style.transition = "none"));
|
|
82
|
+
},
|
|
83
|
+
onPointerMove(e) {
|
|
84
|
+
if (!o.current || !a.current) return;
|
|
85
|
+
let t = Math.max(0, (l(e) - o.current.start) * c);
|
|
86
|
+
o.current.delta = t, a.current.style.transform = `translate${s}(${t * c}px)`;
|
|
87
|
+
},
|
|
88
|
+
onPointerUp() {
|
|
89
|
+
let e = o.current, n = a.current;
|
|
90
|
+
if (o.current = null, !e || !n) return;
|
|
91
|
+
let r = s === "Y" ? n.offsetHeight : n.offsetWidth, i = e.delta / Math.max(1, performance.now() - e.time);
|
|
92
|
+
e.delta > r * .25 || e.delta > 24 && i > .6 ? (n.style.transition = "", t()) : u();
|
|
93
|
+
},
|
|
94
|
+
onPointerCancel() {
|
|
95
|
+
o.current = null, u();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
export { s as CloseButton, o as OverlayBackdrop, l as PanelBody, u as PanelFooter, c as PanelHeader, d as SheetHandle, f as useSwipeDismiss };
|
|
102
|
+
|
|
103
|
+
//# sourceMappingURL=overlay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlay.js","names":["Dialog","OverlayBackdrop","cx","className","CloseButton","label","PanelHeader","visible","hideTitle","hideClose","brand","tone","dragProps","title","description","closeLabel","PanelBody","PanelFooter","SheetHandle","handlers","useSwipeDismiss","panelRef","useRef","drag","axis","direction","sign","read","reset","useCallback","panel","touchOnly","delta","d","size","velocity","onDismiss"],"sources":["../../src/overlay/overlay.tsx"],"sourcesContent":["'use client';\n\nimport * as Dialog from '@radix-ui/react-dialog';\nimport { useCallback, useRef, type PointerEvent as ReactPointerEvent, type ReactNode } from 'react';\nimport { cx } from '../lib/cx';\n\n/**\n * Shared building blocks for dialog-based overlays (Modal, Drawer).\n * Internal: not exported from the package index.\n */\n\nexport function OverlayBackdrop({ className }: { className?: string }) {\n return <Dialog.Overlay className={cx('kf-overlay fixed inset-0 z-50 bg-overlay', className)} />;\n}\n\nexport function CloseButton({ label }: { label: string }) {\n return (\n <Dialog.Close\n aria-label={label}\n className={cx(\n '-mr-2 -mt-1 grid size-10 shrink-0 cursor-pointer place-items-center rounded-full text-fg-muted',\n 'transition-colors duration-150 hover:bg-surface-muted hover:text-fg',\n 'focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-focus',\n )}\n >\n <svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" aria-hidden=\"true\" focusable=\"false\">\n <path d=\"M6 6l12 12M18 6L6 18\" stroke=\"currentColor\" strokeWidth=\"2.25\" strokeLinecap=\"round\" fill=\"none\" />\n </svg>\n </Dialog.Close>\n );\n}\n\nexport interface PanelHeaderProps {\n title: ReactNode;\n description?: ReactNode;\n hideTitle?: boolean;\n hideClose?: boolean;\n closeLabel: string;\n /** Extra props for the header element (e.g. swipe handlers). */\n dragProps?: SwipeHandlers;\n /** `brand`: a solid brand band with white text (dark token set, so the close button stays AA). */\n tone?: 'default' | 'brand';\n}\n\nexport function PanelHeader({ title, description, hideTitle, hideClose, closeLabel, dragProps, tone = 'default' }: PanelHeaderProps) {\n const visible = !hideTitle || !hideClose;\n const brand = tone === 'brand' && visible;\n return (\n <div\n {...(brand ? { 'data-theme': 'dark' } : {})}\n className={cx(\n 'flex items-start gap-4 px-6',\n brand ? 'mb-5 bg-primary-strong pt-6 pb-5 text-fg' : visible ? 'pt-6 pb-4' : 'pt-2',\n )}\n {...dragProps}\n >\n <div className={cx('min-w-0 flex-1', hideTitle && 'sr-only')}>\n <Dialog.Title className=\"m-0 font-display text-xl font-bold tracking-tight text-balance text-fg\">{title}</Dialog.Title>\n {description && <Dialog.Description className=\"m-0 mt-1 text-sm text-fg-muted\">{description}</Dialog.Description>}\n </div>\n {!hideClose && <CloseButton label={closeLabel} />}\n </div>\n );\n}\n\nexport function PanelBody({ children, className }: { children?: ReactNode; className?: string }) {\n return <div className={cx('min-h-0 flex-1 overflow-y-auto overscroll-contain px-6 pb-6 text-[15px]', className)}>{children}</div>;\n}\n\n/** Footer actions: stacked full-width on mobile (primary action at the bottom, next to the thumb), a right-aligned row from `sm`. */\nexport function PanelFooter({ children }: { children: ReactNode }) {\n return (\n <div className=\"flex flex-col-reverse gap-2 border-t border-border px-6 py-4 *:w-full sm:flex-row sm:justify-end sm:*:w-auto\">\n {children}\n </div>\n );\n}\n\n/** Visual drag handle for bottom sheets. */\nexport function SheetHandle({ className, ...handlers }: { className?: string } & SwipeHandlers) {\n return (\n <div aria-hidden=\"true\" className={cx('flex cursor-grab touch-none justify-center pt-3 pb-1 active:cursor-grabbing', className)} {...handlers}>\n <span className=\"h-1.5 w-10 rounded-full bg-border\" />\n </div>\n );\n}\n\nexport type SwipeDirection = 'down' | 'left' | 'right';\n\nexport interface SwipeHandlers {\n onPointerDown?: (e: ReactPointerEvent<HTMLElement>) => void;\n onPointerMove?: (e: ReactPointerEvent<HTMLElement>) => void;\n onPointerUp?: (e: ReactPointerEvent<HTMLElement>) => void;\n onPointerCancel?: (e: ReactPointerEvent<HTMLElement>) => void;\n}\n\n/**\n * Swipe-to-dismiss for sheets and drawers. The panel follows the finger; releasing past\n * 25% of its size (or with a quick flick) dismisses it. The exit animation continues from\n * the dragged position because the exit keyframes only define their end state.\n */\nexport function useSwipeDismiss(direction: SwipeDirection, onDismiss: () => void, { touchOnly = false } = {}) {\n const panelRef = useRef<HTMLDivElement>(null);\n const drag = useRef<{ start: number; time: number; delta: number } | null>(null);\n const axis = direction === 'down' ? 'Y' : 'X';\n const sign = direction === 'left' ? -1 : 1;\n\n const read = (e: ReactPointerEvent) => (axis === 'Y' ? e.clientY : e.clientX);\n\n const reset = useCallback(() => {\n const panel = panelRef.current;\n if (!panel) return;\n panel.style.transition = 'transform 200ms var(--ease-standard)';\n panel.style.transform = '';\n }, []);\n\n const handlers: SwipeHandlers = {\n onPointerDown(e) {\n if (e.button !== 0 || (touchOnly && e.pointerType === 'mouse')) return;\n if ((e.target as HTMLElement).closest('button, a, input, select, textarea')) return;\n drag.current = { start: read(e), time: performance.now(), delta: 0 };\n e.currentTarget.setPointerCapture(e.pointerId);\n if (panelRef.current) panelRef.current.style.transition = 'none';\n },\n onPointerMove(e) {\n if (!drag.current || !panelRef.current) return;\n const delta = Math.max(0, (read(e) - drag.current.start) * sign);\n drag.current.delta = delta;\n panelRef.current.style.transform = `translate${axis}(${delta * sign}px)`;\n },\n onPointerUp() {\n const d = drag.current;\n const panel = panelRef.current;\n drag.current = null;\n if (!d || !panel) return;\n const size = axis === 'Y' ? panel.offsetHeight : panel.offsetWidth;\n const velocity = d.delta / Math.max(1, performance.now() - d.time);\n if (d.delta > size * 0.25 || (d.delta > 24 && velocity > 0.6)) {\n panel.style.transition = '';\n onDismiss();\n } else {\n reset();\n }\n },\n onPointerCancel() {\n drag.current = null;\n reset();\n },\n };\n\n return { panelRef, handlers };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ElementType, ReactNode } from 'react';
|
|
2
|
+
import type { IconName } from '../icon/icon';
|
|
3
|
+
export interface PageHeaderAction {
|
|
4
|
+
label: string;
|
|
5
|
+
icon?: IconName;
|
|
6
|
+
/** A link when set, a button otherwise. */
|
|
7
|
+
href?: string;
|
|
8
|
+
onClick?: () => void;
|
|
9
|
+
/** The main action (one per header): a solid button, always visible. */
|
|
10
|
+
primary?: boolean;
|
|
11
|
+
tone?: 'default' | 'danger';
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface PageHeaderProps {
|
|
15
|
+
title: ReactNode;
|
|
16
|
+
description?: ReactNode;
|
|
17
|
+
/** A small line under the description ("Last synced 5 min ago"). */
|
|
18
|
+
info?: ReactNode;
|
|
19
|
+
/** An icon tile before the title. */
|
|
20
|
+
icon?: IconName;
|
|
21
|
+
/** Instead of the icon tile, e.g. an `Avatar` on account and profile pages. */
|
|
22
|
+
media?: ReactNode;
|
|
23
|
+
/** A link above the title back to the parent page ("People"). */
|
|
24
|
+
back?: {
|
|
25
|
+
label: string;
|
|
26
|
+
href: string;
|
|
27
|
+
};
|
|
28
|
+
/** Link component for `back` (e.g. Next.js `Link`). */
|
|
29
|
+
linkAs?: ElementType;
|
|
30
|
+
/**
|
|
31
|
+
* Up to 5 actions on the right. The `primary` one stays visible on every screen; below 640px the others
|
|
32
|
+
* move into a "More" menu. For custom controls use `actionsSlot`.
|
|
33
|
+
*/
|
|
34
|
+
actions?: PageHeaderAction[];
|
|
35
|
+
/** Custom content on the right (a search field, a filter), before `actions`. */
|
|
36
|
+
actionsSlot?: ReactNode;
|
|
37
|
+
/**
|
|
38
|
+
* Page tabs inside the header, usually a `TabsList` (wrap the page in `<Tabs variant="segmented-inverse">` on
|
|
39
|
+
* `brand`, `"segmented"` on `plain`) or a nav of links.
|
|
40
|
+
*/
|
|
41
|
+
tabs?: ReactNode;
|
|
42
|
+
/** `brand` (default): a solid brand band (dashboards). `plain`: no band, for pages inside cards or settings. */
|
|
43
|
+
tone?: 'brand' | 'plain';
|
|
44
|
+
/** Heading level of the title. Default 1 (one per page). */
|
|
45
|
+
headingLevel?: 1 | 2;
|
|
46
|
+
labels?: {
|
|
47
|
+
more?: string;
|
|
48
|
+
};
|
|
49
|
+
className?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The header at the top of an app page (ADR-0013): optional back link, icon or avatar, title, description and
|
|
53
|
+
* actions, with the page tabs inside the band. One per page, right under the app's top bar.
|
|
54
|
+
*/
|
|
55
|
+
export declare function PageHeader({ title, description, info, icon, media, back, linkAs: Link, actions, actionsSlot, tabs, tone, headingLevel, labels, className, }: PageHeaderProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cx as e } from "../lib/cx.js";
|
|
3
|
+
import { Button as t } from "../button/button.js";
|
|
4
|
+
import { Glyph as n } from "../lib/glyphs.js";
|
|
5
|
+
import { DropdownMenu as r, DropdownMenuContent as i, DropdownMenuItem as a, DropdownMenuTrigger as o } from "../menu/dropdown-menu.js";
|
|
6
|
+
import { IconTile as s } from "../icon/icon-tile.js";
|
|
7
|
+
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
8
|
+
//#region src/page-header/page-header.tsx
|
|
9
|
+
function u({ title: u, description: d, info: f, icon: p, media: m, back: h, linkAs: g = "a", actions: _ = [], actionsSlot: v, tabs: y, tone: b = "brand", headingLevel: x = 1, labels: S = {}, className: C }) {
|
|
10
|
+
let w = b === "brand", T = `h${x}`, E = _.find((e) => e.primary), D = _.filter((e) => e !== E), O = (e) => e.primary ? w ? "inverse" : "primary" : w ? "inverse-outline" : "secondary", k = (e, n) => {
|
|
11
|
+
let r = {
|
|
12
|
+
size: "sm",
|
|
13
|
+
variant: O(e),
|
|
14
|
+
iconStart: e.icon,
|
|
15
|
+
className: n
|
|
16
|
+
};
|
|
17
|
+
return e.href && !e.disabled ? /* @__PURE__ */ c(t, {
|
|
18
|
+
...r,
|
|
19
|
+
href: e.href,
|
|
20
|
+
children: e.label
|
|
21
|
+
}, e.label) : /* @__PURE__ */ c(t, {
|
|
22
|
+
...r,
|
|
23
|
+
disabled: e.disabled,
|
|
24
|
+
onClick: e.onClick,
|
|
25
|
+
children: e.label
|
|
26
|
+
}, e.label);
|
|
27
|
+
};
|
|
28
|
+
return /* @__PURE__ */ l("header", {
|
|
29
|
+
...w ? { "data-theme": "dark" } : {},
|
|
30
|
+
className: e("grid gap-4 font-sans text-fg", w ? "rounded-xl bg-primary-strong px-5 py-5 sm:px-6" : "pb-2", C),
|
|
31
|
+
children: [
|
|
32
|
+
h && /* @__PURE__ */ l(g, {
|
|
33
|
+
href: h.href,
|
|
34
|
+
className: "-mb-1 inline-flex w-fit items-center gap-1 rounded-xs text-sm font-medium text-fg-muted no-underline hover:text-fg focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-focus",
|
|
35
|
+
children: [/* @__PURE__ */ c(n, {
|
|
36
|
+
name: "chevron-left",
|
|
37
|
+
size: 16
|
|
38
|
+
}), h.label]
|
|
39
|
+
}),
|
|
40
|
+
/* @__PURE__ */ l("div", {
|
|
41
|
+
className: "flex flex-wrap items-start justify-between gap-x-6 gap-y-4",
|
|
42
|
+
children: [/* @__PURE__ */ l("div", {
|
|
43
|
+
className: "flex min-w-[min(100%,16rem)] flex-1 items-center gap-4",
|
|
44
|
+
children: [m ?? (p && /* @__PURE__ */ c(s, {
|
|
45
|
+
icon: p,
|
|
46
|
+
size: "lg",
|
|
47
|
+
tone: w ? "inverse" : "primary"
|
|
48
|
+
})), /* @__PURE__ */ l("div", {
|
|
49
|
+
className: "grid min-w-0 gap-1",
|
|
50
|
+
children: [
|
|
51
|
+
/* @__PURE__ */ c(T, {
|
|
52
|
+
className: "m-0 font-display text-2xl leading-tight font-bold tracking-tight text-balance sm:text-[1.75rem]",
|
|
53
|
+
children: u
|
|
54
|
+
}),
|
|
55
|
+
d && /* @__PURE__ */ c("p", {
|
|
56
|
+
className: "m-0 text-[15px] text-fg-muted",
|
|
57
|
+
children: d
|
|
58
|
+
}),
|
|
59
|
+
f && /* @__PURE__ */ c("p", {
|
|
60
|
+
className: "m-0 text-xs text-fg-muted",
|
|
61
|
+
children: f
|
|
62
|
+
})
|
|
63
|
+
]
|
|
64
|
+
})]
|
|
65
|
+
}), (_.length > 0 || v) && /* @__PURE__ */ l("div", {
|
|
66
|
+
className: "flex w-full flex-wrap items-center gap-2 sm:w-auto sm:shrink-0",
|
|
67
|
+
children: [
|
|
68
|
+
v && /* @__PURE__ */ c("div", {
|
|
69
|
+
className: "w-full min-w-0 sm:w-auto",
|
|
70
|
+
children: v
|
|
71
|
+
}),
|
|
72
|
+
D.length > 0 && /* @__PURE__ */ c("div", {
|
|
73
|
+
className: "hidden flex-wrap items-center gap-2 sm:flex",
|
|
74
|
+
children: D.map((e) => k(e))
|
|
75
|
+
}),
|
|
76
|
+
D.length > 0 && /* @__PURE__ */ l(r, { children: [/* @__PURE__ */ c(o, {
|
|
77
|
+
asChild: !0,
|
|
78
|
+
children: /* @__PURE__ */ c("button", {
|
|
79
|
+
type: "button",
|
|
80
|
+
"aria-label": S.more ?? "More actions",
|
|
81
|
+
className: e("grid size-9 cursor-pointer place-items-center rounded-full border-0 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-focus sm:hidden pointer-coarse:size-11", w ? "bg-white/10 text-white shadow-[inset_0_0_0_1.5px_rgb(255_255_255/0.45)] hover:bg-white/20" : "bg-surface text-fg shadow-[inset_0_0_0_1px_var(--kf-border)] hover:bg-surface-muted"),
|
|
82
|
+
children: /* @__PURE__ */ c(n, {
|
|
83
|
+
name: "more",
|
|
84
|
+
size: 18
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
}), /* @__PURE__ */ c(i, {
|
|
88
|
+
align: "end",
|
|
89
|
+
children: D.map((e) => /* @__PURE__ */ c(a, {
|
|
90
|
+
icon: e.icon,
|
|
91
|
+
tone: e.tone,
|
|
92
|
+
disabled: e.disabled,
|
|
93
|
+
...e.href ? {
|
|
94
|
+
href: e.href,
|
|
95
|
+
linkAs: g
|
|
96
|
+
} : { onSelect: e.onClick },
|
|
97
|
+
children: e.label
|
|
98
|
+
}, e.label))
|
|
99
|
+
})] }),
|
|
100
|
+
E && k(E)
|
|
101
|
+
]
|
|
102
|
+
})]
|
|
103
|
+
}),
|
|
104
|
+
y && /* @__PURE__ */ c("div", {
|
|
105
|
+
className: "min-w-0",
|
|
106
|
+
children: y
|
|
107
|
+
})
|
|
108
|
+
]
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
export { u as PageHeader };
|
|
113
|
+
|
|
114
|
+
//# sourceMappingURL=page-header.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-header.js","names":["PageHeader","Link","brand","tone","H","headingLevel","primary","actions","a","others","variantFor","button","common","Button","cx","className","back","Glyph","media","icon","IconTile","title","description","info","actionsSlot","DropdownMenu","DropdownMenuTrigger","labels","DropdownMenuContent","DropdownMenuItem","tabs"],"sources":["../../src/page-header/page-header.tsx"],"sourcesContent":["'use client';\n\nimport type { ElementType, ReactNode } from 'react';\nimport { cx } from '../lib/cx';\nimport { IconTile } from '../icon/icon-tile';\nimport { Glyph } from '../lib/glyphs';\nimport type { IconName } from '../icon/icon';\nimport { Button, type ButtonVariant } from '../button/button';\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../menu/dropdown-menu';\n\nexport interface PageHeaderAction {\n label: string;\n icon?: IconName;\n /** A link when set, a button otherwise. */\n href?: string;\n onClick?: () => void;\n /** The main action (one per header): a solid button, always visible. */\n primary?: boolean;\n tone?: 'default' | 'danger';\n disabled?: boolean;\n}\n\nexport interface PageHeaderProps {\n title: ReactNode;\n description?: ReactNode;\n /** A small line under the description (\"Last synced 5 min ago\"). */\n info?: ReactNode;\n /** An icon tile before the title. */\n icon?: IconName;\n /** Instead of the icon tile, e.g. an `Avatar` on account and profile pages. */\n media?: ReactNode;\n /** A link above the title back to the parent page (\"People\"). */\n back?: { label: string; href: string };\n /** Link component for `back` (e.g. Next.js `Link`). */\n linkAs?: ElementType;\n /**\n * Up to 5 actions on the right. The `primary` one stays visible on every screen; below 640px the others\n * move into a \"More\" menu. For custom controls use `actionsSlot`.\n */\n actions?: PageHeaderAction[];\n /** Custom content on the right (a search field, a filter), before `actions`. */\n actionsSlot?: ReactNode;\n /**\n * Page tabs inside the header, usually a `TabsList` (wrap the page in `<Tabs variant=\"segmented-inverse\">` on\n * `brand`, `\"segmented\"` on `plain`) or a nav of links.\n */\n tabs?: ReactNode;\n /** `brand` (default): a solid brand band (dashboards). `plain`: no band, for pages inside cards or settings. */\n tone?: 'brand' | 'plain';\n /** Heading level of the title. Default 1 (one per page). */\n headingLevel?: 1 | 2;\n labels?: { more?: string };\n className?: string;\n}\n\n/**\n * The header at the top of an app page (ADR-0013): optional back link, icon or avatar, title, description and\n * actions, with the page tabs inside the band. One per page, right under the app's top bar.\n */\nexport function PageHeader({\n title,\n description,\n info,\n icon,\n media,\n back,\n linkAs: Link = 'a',\n actions = [],\n actionsSlot,\n tabs,\n tone = 'brand',\n headingLevel = 1,\n labels = {},\n className,\n}: PageHeaderProps) {\n const brand = tone === 'brand';\n const H = `h${headingLevel}` as const;\n const primary = actions.find((a) => a.primary);\n const others = actions.filter((a) => a !== primary);\n\n // One color set per tone (`cx` does not merge classes). Destructive actions are not solid red in a header:\n // they look like the other secondary actions and turn red in the \"More\" menu; confirm them with a dialog.\n const variantFor = (a: PageHeaderAction): ButtonVariant =>\n a.primary ? (brand ? 'inverse' : 'primary') : brand ? 'inverse-outline' : 'secondary';\n\n const button = (a: PageHeaderAction, className?: string) => {\n const common = { size: 'sm' as const, variant: variantFor(a), iconStart: a.icon, className };\n return a.href && !a.disabled ? (\n <Button key={a.label} {...common} href={a.href}>\n {a.label}\n </Button>\n ) : (\n <Button key={a.label} {...common} disabled={a.disabled} onClick={a.onClick}>\n {a.label}\n </Button>\n );\n };\n\n return (\n <header\n // The band is dark in both themes: switch to the dark token set so text and focus rings stay AA.\n {...(brand ? { 'data-theme': 'dark' } : {})}\n className={cx(\n 'grid gap-4 font-sans text-fg',\n brand ? 'rounded-xl bg-primary-strong px-5 py-5 sm:px-6' : 'pb-2',\n className,\n )}\n >\n {back && (\n <Link\n href={back.href}\n className=\"-mb-1 inline-flex w-fit items-center gap-1 rounded-xs text-sm font-medium text-fg-muted no-underline hover:text-fg focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-focus\"\n >\n <Glyph name=\"chevron-left\" size={16} />\n {back.label}\n </Link>\n )}\n\n <div className=\"flex flex-wrap items-start justify-between gap-x-6 gap-y-4\">\n {/* At least 16rem for the title: on narrow screens the actions wrap under it instead of squeezing it. */}\n <div className=\"flex min-w-[min(100%,16rem)] flex-1 items-center gap-4\">\n {media ?? (icon && <IconTile icon={icon} size=\"lg\" tone={brand ? 'inverse' : 'primary'} />)}\n <div className=\"grid min-w-0 gap-1\">\n <H className=\"m-0 font-display text-2xl leading-tight font-bold tracking-tight text-balance sm:text-[1.75rem]\">\n {title}\n </H>\n {description && <p className=\"m-0 text-[15px] text-fg-muted\">{description}</p>}\n {info && <p className=\"m-0 text-xs text-fg-muted\">{info}</p>}\n </div>\n </div>\n\n {(actions.length > 0 || actionsSlot) && (\n // Below 640px the actions take a full row under the title, so a search field never overflows.\n <div className=\"flex w-full flex-wrap items-center gap-2 sm:w-auto sm:shrink-0\">\n {actionsSlot && <div className=\"w-full min-w-0 sm:w-auto\">{actionsSlot}</div>}\n {/* Secondary actions: visible from 640px, in the \"More\" menu below (a wrapper: Button sets its own display). */}\n {others.length > 0 && (\n <div className=\"hidden flex-wrap items-center gap-2 sm:flex\">{others.map((a) => button(a))}</div>\n )}\n {others.length > 0 && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n aria-label={labels.more ?? 'More actions'}\n className={cx(\n 'grid size-9 cursor-pointer place-items-center rounded-full border-0 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-focus sm:hidden pointer-coarse:size-11',\n brand\n ? 'bg-white/10 text-white shadow-[inset_0_0_0_1.5px_rgb(255_255_255/0.45)] hover:bg-white/20'\n : 'bg-surface text-fg shadow-[inset_0_0_0_1px_var(--kf-border)] hover:bg-surface-muted',\n )}\n >\n <Glyph name=\"more\" size={18} />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {others.map((a) => (\n <DropdownMenuItem\n key={a.label}\n icon={a.icon}\n tone={a.tone}\n disabled={a.disabled}\n {...(a.href ? { href: a.href, linkAs: Link } : { onSelect: a.onClick })}\n >\n {a.label}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n {primary && button(primary)}\n </div>\n )}\n </div>\n\n {tabs && <div className=\"min-w-0\">{tabs}</div>}\n </header>\n );\n}\n"],"mappings":";;;;;;;;AA2DA,SAAgBA,EAAW,EACzB,UACA,gBACA,SACA,SACA,UACA,SACA,QAAQC,IAAO,KACf,aAAU,CAAC,GACX,gBACA,SACA,UAAO,SACP,kBAAe,GACf,YAAS,CAAC,GACV,gBACkB;CAClB,IAAMC,IAAQC,MAAS,SACjBC,IAAI,IAAIC,KACRC,IAAUC,EAAQ,MAAM,MAAMC,EAAE,OAAO,GACvCC,IAASF,EAAQ,QAAQ,MAAMC,MAAMF,CAAO,GAI5CI,KAAc,MAClBF,EAAE,UAAWN,IAAQ,YAAY,YAAaA,IAAQ,oBAAoB,aAEtES,KAAU,GAAqB,MAAuB;EAC1D,IAAMC,IAAS;GAAE,MAAM;GAAe,SAASF,EAAWF,CAAC;GAAG,WAAWA,EAAE;GAAM;EAAU;EAC3F,OAAOA,EAAE,QAAQ,CAACA,EAAE,WAClB,kBAACK,GAAD;GAAsB,GAAID;GAAQ,MAAMJ,EAAE;GACvC,UAAAA,EAAE;EACG,GAFKA,EAAE,KAEP,IAER,kBAACK,GAAD;GAAsB,GAAID;GAAQ,UAAUJ,EAAE;GAAU,SAASA,EAAE;GAChE,UAAAA,EAAE;EACG,GAFKA,EAAE,KAEP;CAEZ;CAEA,OACE,kBAAC,UAAD;EAEE,GAAKN,IAAQ,EAAE,cAAc,OAAO,IAAI,CAAC;EACzC,WAAWY,EACT,gCACAZ,IAAQ,mDAAmD,QAC3Da,CACF;EAPF,UAAA;GASGC,KACC,kBAACf,GAAD;IACE,MAAMe,EAAK;IACX,WAAU;IAFZ,UAAA,CAIE,kBAACC,GAAD;KAAO,MAAK;KAAe,MAAM;IAAK,CAAA,GACrCD,EAAK,KACF;;GAGR,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CAEE,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACGE,MAAUC,KAAQ,kBAACC,GAAD;MAAgB;MAAM,MAAK;MAAK,MAAMlB,IAAQ,YAAY;KAAY,CAAA,IACzF,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA;OACE,kBAACE,GAAD;QAAG,WAAU;QACV,UAAAiB;OACA,CAAA;OACFC,KAAe,kBAAC,KAAD;QAAG,WAAU;QAAiC,UAAAA;OAAe,CAAA;OAC5EC,KAAQ,kBAAC,KAAD;QAAG,WAAU;QAA6B,UAAAA;OAAQ,CAAA;MACxD;KACF,CAAA,CAAA;IAEH,CAAA,IAAAhB,EAAQ,SAAS,KAAKiB,MAEtB,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA;MACGA,KAAe,kBAAC,OAAD;OAAK,WAAU;OAA4B,UAAAA;MAAiB,CAAA;MAE3Ef,EAAO,SAAS,KACf,kBAAC,OAAD;OAAK,WAAU;OAA+C,UAAAA,EAAO,KAAK,MAAME,EAAOH,CAAC,CAAC;MAAO,CAAA;MAEjGC,EAAO,SAAS,KACf,kBAACgB,GAAD,EAAA,UAAA,CACE,kBAACC,GAAD;OAAqB,SAAA;OACnB,UAAA,kBAAC,UAAD;QACE,MAAK;QACL,cAAYC,EAAO,QAAQ;QAC3B,WAAWb,EACT,2LACAZ,IACI,8FACA,qFACN;QAEA,UAAA,kBAACe,GAAD;SAAO,MAAK;SAAO,MAAM;QAAK,CAAA;OACxB,CAAA;MACW,CAAA,GACrB,kBAACW,GAAD;OAAqB,OAAM;OACxB,UAAAnB,EAAO,KAAK,MACX,kBAACoB,GAAD;QAEE,MAAMrB,EAAE;QACR,MAAMA,EAAE;QACR,UAAUA,EAAE;QACZ,GAAKA,EAAE,OAAO;SAAE,MAAMA,EAAE;SAAM,QAAQP;QAAK,IAAI,EAAE,UAAUO,EAAE,QAAQ;QAEpE,UAAAA,EAAE;OACa,GAPXA,EAAE,KAOS,CACnB;MACkB,CAAA,CACT,EAAA,CAAA;MAEfF,KAAWK,EAAOL,CAAO;KACvB;IAEJ,CAAA,CAAA;;GAEJwB,KAAQ,kBAAC,OAAD;IAAK,WAAU;IAAW,UAAAA;GAAU,CAAA;EACvC;;AAEZ"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type IconName } from '../icon/icon';
|
|
3
|
+
export interface EntityHeaderProps {
|
|
4
|
+
name: string;
|
|
5
|
+
/** A short code next to the name ("ACM"). */
|
|
6
|
+
abbreviation?: string;
|
|
7
|
+
/** An icon tile (companies, projects) or a photo (`avatarSrc`, people). */
|
|
8
|
+
icon?: IconName;
|
|
9
|
+
avatarSrc?: string;
|
|
10
|
+
/** Tile color for `icon`, e.g. the client's category color. */
|
|
11
|
+
color?: string;
|
|
12
|
+
/** Shows an edit button on the picture. */
|
|
13
|
+
onChangeImage?: () => void;
|
|
14
|
+
badges?: ReactNode;
|
|
15
|
+
location?: string;
|
|
16
|
+
links?: {
|
|
17
|
+
label: string;
|
|
18
|
+
href: string;
|
|
19
|
+
icon?: IconName;
|
|
20
|
+
}[];
|
|
21
|
+
/** Right side: "Edit", "More". */
|
|
22
|
+
actions?: ReactNode;
|
|
23
|
+
labels?: {
|
|
24
|
+
changeImage?: string;
|
|
25
|
+
};
|
|
26
|
+
className?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The header of a record's page (a client, a product, a company): picture, name, code, badges, links, actions.
|
|
30
|
+
* A div, not <header>: inside <main> it is page content, and several would be duplicate banner landmarks.
|
|
31
|
+
*/
|
|
32
|
+
export declare function EntityHeader({ name, abbreviation, icon, avatarSrc, color, onChangeImage, badges, location, links, actions, labels, className, }: EntityHeaderProps): import("react").JSX.Element;
|
|
33
|
+
export interface ProfileHeaderProps {
|
|
34
|
+
name: string;
|
|
35
|
+
avatarSrc?: string;
|
|
36
|
+
/** "Product designer · Design team" */
|
|
37
|
+
subtitle?: ReactNode;
|
|
38
|
+
badges?: ReactNode;
|
|
39
|
+
location?: string;
|
|
40
|
+
/** Pre-formatted local time ("10:42 (UTC−05:00)"), computed by the page to avoid hydration mismatches. */
|
|
41
|
+
localTime?: string;
|
|
42
|
+
actions?: ReactNode;
|
|
43
|
+
/** Status dot on the avatar (see `Avatar`). */
|
|
44
|
+
statusColor?: string;
|
|
45
|
+
statusLabel?: string;
|
|
46
|
+
/** Cover band color or gradient. Default: the brand gradient. */
|
|
47
|
+
cover?: string;
|
|
48
|
+
className?: string;
|
|
49
|
+
}
|
|
50
|
+
/** A person's profile header: cover band, large avatar, name, role · team, badges, location and local time. */
|
|
51
|
+
export declare function ProfileHeader({ name, avatarSrc, subtitle, badges, location, localTime, actions, statusColor, statusLabel, cover, className, }: ProfileHeaderProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cx as e } from "../lib/cx.js";
|
|
3
|
+
import { Icon as t } from "../icon/icon.js";
|
|
4
|
+
import { IconButton as n } from "../button/button.js";
|
|
5
|
+
import { Avatar as r } from "../data-display/avatar.js";
|
|
6
|
+
import { IconTile as i } from "../icon/icon-tile.js";
|
|
7
|
+
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
8
|
+
import "react";
|
|
9
|
+
//#region src/page-header/record-headers.tsx
|
|
10
|
+
function s({ name: s, abbreviation: c, icon: l, avatarSrc: u, color: d = "var(--kf-primary)", onChangeImage: f, badges: p, location: m, links: h = [], actions: g, labels: _ = {}, className: v }) {
|
|
11
|
+
return /* @__PURE__ */ o("div", {
|
|
12
|
+
className: e("flex flex-wrap items-start justify-between gap-x-6 gap-y-4 font-sans", v),
|
|
13
|
+
children: [/* @__PURE__ */ o("div", {
|
|
14
|
+
className: "flex min-w-[min(100%,16rem)] flex-1 items-center gap-4",
|
|
15
|
+
children: [/* @__PURE__ */ o("div", {
|
|
16
|
+
className: "relative shrink-0",
|
|
17
|
+
children: [l && !u ? /* @__PURE__ */ a(i, {
|
|
18
|
+
icon: l,
|
|
19
|
+
size: "xl",
|
|
20
|
+
color: d
|
|
21
|
+
}) : /* @__PURE__ */ a(r, {
|
|
22
|
+
name: s,
|
|
23
|
+
src: u,
|
|
24
|
+
size: "xl",
|
|
25
|
+
className: "size-16!",
|
|
26
|
+
decorative: !0
|
|
27
|
+
}), f && /* @__PURE__ */ a(n, {
|
|
28
|
+
icon: "edit",
|
|
29
|
+
label: _.changeImage ?? "Change picture",
|
|
30
|
+
size: "sm",
|
|
31
|
+
variant: "primary",
|
|
32
|
+
onClick: f,
|
|
33
|
+
className: "absolute -right-1.5 -bottom-1.5 ring-3 ring-bg"
|
|
34
|
+
})]
|
|
35
|
+
}), /* @__PURE__ */ o("div", {
|
|
36
|
+
className: "grid min-w-0 gap-1.5",
|
|
37
|
+
children: [/* @__PURE__ */ o("div", {
|
|
38
|
+
className: "flex flex-wrap items-center gap-2",
|
|
39
|
+
children: [
|
|
40
|
+
/* @__PURE__ */ a("h1", {
|
|
41
|
+
className: "m-0 font-display text-2xl font-bold tracking-tight text-fg",
|
|
42
|
+
children: s
|
|
43
|
+
}),
|
|
44
|
+
c && /* @__PURE__ */ a("span", {
|
|
45
|
+
className: "rounded-sm bg-surface-muted px-1.5 py-0.5 font-mono text-xs font-semibold text-fg-muted",
|
|
46
|
+
children: c
|
|
47
|
+
}),
|
|
48
|
+
p
|
|
49
|
+
]
|
|
50
|
+
}), (m || h.length > 0) && /* @__PURE__ */ o("div", {
|
|
51
|
+
className: "flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-fg-muted",
|
|
52
|
+
children: [m && /* @__PURE__ */ o("span", {
|
|
53
|
+
className: "inline-flex items-center gap-1",
|
|
54
|
+
children: [/* @__PURE__ */ a(t, {
|
|
55
|
+
name: "c-location",
|
|
56
|
+
size: 16
|
|
57
|
+
}), m]
|
|
58
|
+
}), h.map((e) => /* @__PURE__ */ o("a", {
|
|
59
|
+
href: e.href,
|
|
60
|
+
className: "inline-flex items-center gap-1 rounded-xs text-link no-underline hover:underline focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-focus",
|
|
61
|
+
children: [/* @__PURE__ */ a(t, {
|
|
62
|
+
name: e.icon ?? "c-external",
|
|
63
|
+
size: 16
|
|
64
|
+
}), e.label]
|
|
65
|
+
}, e.href))]
|
|
66
|
+
})]
|
|
67
|
+
})]
|
|
68
|
+
}), g && /* @__PURE__ */ a("div", {
|
|
69
|
+
className: "flex flex-wrap items-center gap-2",
|
|
70
|
+
children: g
|
|
71
|
+
})]
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function c({ name: n, avatarSrc: i, subtitle: s, badges: c, location: l, localTime: u, actions: d, statusColor: f, statusLabel: p, cover: m, className: h }) {
|
|
75
|
+
return /* @__PURE__ */ o("div", {
|
|
76
|
+
className: e("overflow-hidden rounded-xl border border-border bg-surface font-sans", h),
|
|
77
|
+
children: [/* @__PURE__ */ a("div", {
|
|
78
|
+
"aria-hidden": "true",
|
|
79
|
+
className: e("h-24 sm:h-32", !m && "bg-gradient-brand"),
|
|
80
|
+
style: m ? { background: m } : void 0
|
|
81
|
+
}), /* @__PURE__ */ o("div", {
|
|
82
|
+
className: "grid gap-4 px-card pb-card",
|
|
83
|
+
children: [/* @__PURE__ */ o("div", {
|
|
84
|
+
className: "-mt-10 flex flex-wrap items-end justify-between gap-3",
|
|
85
|
+
children: [/* @__PURE__ */ a(r, {
|
|
86
|
+
name: n,
|
|
87
|
+
src: i,
|
|
88
|
+
size: "xl",
|
|
89
|
+
statusColor: f,
|
|
90
|
+
statusLabel: p,
|
|
91
|
+
className: "size-24! text-3xl! ring-4 ring-surface"
|
|
92
|
+
}), d && /* @__PURE__ */ a("div", {
|
|
93
|
+
className: "flex flex-wrap items-center gap-2",
|
|
94
|
+
children: d
|
|
95
|
+
})]
|
|
96
|
+
}), /* @__PURE__ */ o("div", {
|
|
97
|
+
className: "grid gap-1.5",
|
|
98
|
+
children: [
|
|
99
|
+
/* @__PURE__ */ o("div", {
|
|
100
|
+
className: "flex flex-wrap items-center gap-2",
|
|
101
|
+
children: [/* @__PURE__ */ a("h1", {
|
|
102
|
+
className: "m-0 font-display text-2xl font-bold tracking-tight text-fg",
|
|
103
|
+
children: n
|
|
104
|
+
}), c]
|
|
105
|
+
}),
|
|
106
|
+
s && /* @__PURE__ */ a("p", {
|
|
107
|
+
className: "m-0 text-[15px] text-fg-muted",
|
|
108
|
+
children: s
|
|
109
|
+
}),
|
|
110
|
+
(l || u) && /* @__PURE__ */ o("p", {
|
|
111
|
+
className: "m-0 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-fg-muted",
|
|
112
|
+
children: [l && /* @__PURE__ */ o("span", {
|
|
113
|
+
className: "inline-flex items-center gap-1",
|
|
114
|
+
children: [/* @__PURE__ */ a(t, {
|
|
115
|
+
name: "c-location",
|
|
116
|
+
size: 16
|
|
117
|
+
}), l]
|
|
118
|
+
}), u && /* @__PURE__ */ o("span", {
|
|
119
|
+
className: "inline-flex items-center gap-1",
|
|
120
|
+
children: [/* @__PURE__ */ a(t, {
|
|
121
|
+
name: "time",
|
|
122
|
+
size: 16
|
|
123
|
+
}), u]
|
|
124
|
+
})]
|
|
125
|
+
})
|
|
126
|
+
]
|
|
127
|
+
})]
|
|
128
|
+
})]
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
132
|
+
export { s as EntityHeader, c as ProfileHeader };
|
|
133
|
+
|
|
134
|
+
//# sourceMappingURL=record-headers.js.map
|