@godxjp/ui 18.4.0 → 18.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -1
- package/dist/app/theme-axes.d.ts +11 -3
- package/dist/app/theme-axes.js +2 -1
- package/dist/components/charts/chart-frame.d.ts +17 -3
- package/dist/components/charts/chart-frame.js +7 -1
- package/dist/components/charts/chart-summary.d.ts +21 -0
- package/dist/components/charts/chart-summary.js +32 -5
- package/dist/components/charts/compact-bar-trend.d.ts +30 -0
- package/dist/components/charts/compact-bar-trend.js +74 -0
- package/dist/components/charts/index.d.ts +6 -0
- package/dist/components/charts/index.js +2 -0
- package/dist/components/data-display/badge.d.ts +8 -0
- package/dist/components/data-display/badge.js +7 -1
- package/dist/components/data-display/data-table.d.ts +40 -4
- package/dist/components/data-display/data-table.js +69 -2
- package/dist/components/data-display/empty-state.js +1 -1
- package/dist/components/data-display/index.d.ts +4 -2
- package/dist/components/data-display/index.js +10 -1
- package/dist/components/data-display/list-row.d.ts +41 -4
- package/dist/components/data-display/list-row.js +23 -3
- package/dist/components/data-display/qr-code.d.ts +1 -1
- package/dist/components/data-display/service-launcher-card.d.ts +40 -0
- package/dist/components/data-display/service-launcher-card.js +97 -0
- package/dist/components/data-display/table.d.ts +47 -2
- package/dist/components/data-display/table.js +29 -4
- package/dist/components/data-entry/command-palette.d.ts +37 -0
- package/dist/components/data-entry/command-palette.js +125 -0
- package/dist/components/data-entry/command.d.ts +10 -10
- package/dist/components/data-entry/index.d.ts +3 -0
- package/dist/components/data-entry/index.js +2 -0
- package/dist/components/data-entry/input-otp.d.ts +1 -0
- package/dist/components/feedback/dialog.d.ts +16 -1
- package/dist/components/feedback/dialog.js +46 -33
- package/dist/components/feedback/index.d.ts +5 -2
- package/dist/components/feedback/index.js +8 -2
- package/dist/components/feedback/sheet.d.ts +26 -4
- package/dist/components/feedback/sheet.js +54 -7
- package/dist/components/feedback/two-factor-setup.d.ts +31 -0
- package/dist/components/feedback/two-factor-setup.js +97 -0
- package/dist/components/general/index.d.ts +1 -1
- package/dist/components/general/logo.d.ts +49 -9
- package/dist/components/general/logo.js +67 -14
- package/dist/components/layout/app-shell.d.ts +1 -1
- package/dist/components/layout/app-shell.js +55 -30
- package/dist/components/layout/auth-account-summary.d.ts +5 -0
- package/dist/components/layout/auth-account-summary.js +31 -0
- package/dist/components/layout/auth-divider.d.ts +7 -0
- package/dist/components/layout/auth-divider.js +21 -0
- package/dist/components/layout/auth-footer.d.ts +5 -0
- package/dist/components/layout/auth-footer.js +24 -0
- package/dist/components/layout/auth-identity.d.ts +5 -0
- package/dist/components/layout/auth-identity.js +17 -0
- package/dist/components/layout/auth-shell.d.ts +9 -1
- package/dist/components/layout/auth-shell.js +25 -6
- package/dist/components/layout/auth-stack.d.ts +4 -0
- package/dist/components/layout/auth-stack.js +8 -0
- package/dist/components/layout/centered-shell.d.ts +5 -2
- package/dist/components/layout/centered-shell.js +29 -19
- package/dist/components/layout/error-surface.d.ts +35 -0
- package/dist/components/layout/error-surface.js +155 -0
- package/dist/components/layout/flex.d.ts +1 -1
- package/dist/components/layout/flex.js +4 -0
- package/dist/components/layout/index.d.ts +23 -3
- package/dist/components/layout/index.js +21 -2
- package/dist/components/layout/legal-document-shell.d.ts +34 -0
- package/dist/components/layout/legal-document-shell.js +186 -0
- package/dist/components/layout/master-detail.d.ts +24 -0
- package/dist/components/layout/master-detail.js +48 -0
- package/dist/components/layout/org-switcher.d.ts +9 -0
- package/dist/components/layout/org-switcher.js +218 -0
- package/dist/components/layout/page-container.d.ts +1 -1
- package/dist/components/layout/page-container.js +7 -2
- package/dist/components/layout/sidebar-link.d.ts +43 -0
- package/dist/components/layout/sidebar-link.js +13 -0
- package/dist/components/layout/sidebar.d.ts +42 -5
- package/dist/components/layout/sidebar.js +170 -85
- package/dist/components/navigation/app-setting-picker.js +17 -6
- package/dist/components/navigation/filter-bar.d.ts +12 -3
- package/dist/components/navigation/filter-bar.js +8 -2
- package/dist/components/navigation/index.d.ts +2 -2
- package/dist/components/navigation/index.js +3 -1
- package/dist/components/navigation/steps.js +38 -19
- package/dist/components/navigation/tabs-scroll.d.ts +70 -0
- package/dist/components/navigation/tabs-scroll.js +76 -0
- package/dist/components/navigation/tabs.js +47 -23
- package/dist/components/ui/avatar.d.ts +2 -1
- package/dist/components/ui/avatar.js +2 -1
- package/dist/components/ui/input-otp.d.ts +4 -1
- package/dist/components/ui/input-otp.js +2 -1
- package/dist/components/ui/toggle-group.d.ts +7 -4
- package/dist/components/ui/toggle-group.js +32 -21
- package/dist/email/brand-mark.d.ts +108 -0
- package/dist/email/brand-mark.js +88 -0
- package/dist/email/color.d.ts +53 -0
- package/dist/email/color.js +37 -0
- package/dist/email/geometry.d.ts +120 -0
- package/dist/email/geometry.js +90 -0
- package/dist/email/index.d.ts +83 -0
- package/dist/email/index.js +82 -0
- package/dist/email/inline-style.d.ts +27 -0
- package/dist/email/inline-style.js +26 -0
- package/dist/email/tokens.generated.d.ts +167 -0
- package/dist/email/tokens.generated.js +162 -0
- package/dist/email/urgency.d.ts +22 -0
- package/dist/email/urgency.js +14 -0
- package/dist/i18n/messages/en.json +36 -3
- package/dist/i18n/messages/ja.json +36 -3
- package/dist/i18n/messages/vi.json +36 -3
- package/dist/inertia/index.d.ts +50 -0
- package/dist/inertia/index.js +5 -0
- package/dist/props/components/app.prop.d.ts +11 -1
- package/dist/props/components/charts.prop.d.ts +40 -1
- package/dist/props/components/data-display.prop.d.ts +75 -1
- package/dist/props/components/data-entry.prop.d.ts +6 -0
- package/dist/props/components/feedback.prop.d.ts +10 -0
- package/dist/props/components/index.d.ts +3 -3
- package/dist/props/components/layout.prop.d.ts +560 -10
- package/dist/props/components/layout.prop.js +1 -0
- package/dist/props/components/navigation.prop.d.ts +24 -2
- package/dist/props/registry.d.ts +433 -8
- package/dist/props/registry.js +527 -9
- package/dist/props/vocabulary/data.prop.d.ts +27 -0
- package/dist/props/vocabulary/index.d.ts +3 -3
- package/dist/props/vocabulary/interaction.prop.d.ts +10 -1
- package/dist/props/vocabulary/layout.prop.d.ts +49 -0
- package/dist/styles/base.css +8 -2
- package/dist/styles/card-layout.css +161 -7
- package/dist/styles/chart-layout.css +97 -0
- package/dist/styles/control.css +76 -2
- package/dist/styles/data-display-layout.css +78 -6
- package/dist/styles/dialog-layout.css +105 -4
- package/dist/styles/fonts.css +15 -4
- package/dist/styles/index.css +11 -3
- package/dist/styles/layout.css +466 -1
- package/dist/styles/logo-layout.css +95 -0
- package/dist/styles/navigation-layout.css +152 -0
- package/dist/styles/shell-layout.css +790 -40
- package/dist/styles/table-layout.css +164 -1
- package/dist/theme/dxs.canonical.css +77 -0
- package/dist/tokens/axes.css +40 -0
- package/dist/tokens/base.css +6 -0
- package/dist/tokens/components/card.css +48 -0
- package/dist/tokens/components/chart.css +47 -0
- package/dist/tokens/components/control.css +16 -0
- package/dist/tokens/components/data-display.css +13 -0
- package/dist/tokens/components/email.css +93 -0
- package/dist/tokens/components/error-surface.css +36 -0
- package/dist/tokens/components/feedback.css +17 -0
- package/dist/tokens/components/legal-document.css +67 -0
- package/dist/tokens/components/list-row.css +43 -2
- package/dist/tokens/components/logo.css +74 -0
- package/dist/tokens/components/navigation.css +28 -0
- package/dist/tokens/components/sheet.css +19 -0
- package/dist/tokens/components/shell.css +210 -0
- package/dist/tokens/components/sidebar.css +33 -0
- package/dist/tokens/components/table.css +45 -0
- package/dist/tokens/foundation.css +31 -2
- package/dist/tokens/semantic/layout.css +69 -2
- package/package.json +22 -5
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Keep-the-active-tab-visible logic for the horizontal `TabsList` scroll strip (gh#204).
|
|
4
|
+
*
|
|
5
|
+
* `TabsList` scrolls its own overflow (gh#175) so long localized labels never clip. That fixed
|
|
6
|
+
* clipping but not the SCROLL POSITION: after a responsive resize / route re-render the strip kept
|
|
7
|
+
* (or shifted) its internal `scrollLeft`, so the ACTIVE trigger — typically the FIRST one — could
|
|
8
|
+
* end up completely outside the scrollport while still carrying `data-state="active"` /
|
|
9
|
+
* `aria-selected="true"`. A tab you cannot see is a tab you cannot use.
|
|
10
|
+
*
|
|
11
|
+
* The fix observes the strip (size changes) and its triggers (`data-state` changes) and scrolls the
|
|
12
|
+
* element that must stay reachable back into view. Deliberate constraints:
|
|
13
|
+
*
|
|
14
|
+
* - **Never fights the user.** The scroll only runs when the target is NOT already fully inside the
|
|
15
|
+
* scrollport, so an intentional manual/touch scroll that leaves the active tab visible is left
|
|
16
|
+
* alone, and a click (target is under the pointer) or arrow-key move (the browser already
|
|
17
|
+
* scrolled focus into view) is a no-op.
|
|
18
|
+
* - **Focus wins over selection.** With `activationMode="manual"` the roving focus can sit on a
|
|
19
|
+
* trigger that is not the selected one; yanking the strip to the selected tab would strand the
|
|
20
|
+
* keyboard user. The focused trigger inside the list is therefore the preferred target.
|
|
21
|
+
* - **Reduced motion.** `prefers-reduced-motion: reduce` downgrades every correction to an instant
|
|
22
|
+
* jump; resize corrections are instant regardless (a smooth animation mid-resize reads as jank).
|
|
23
|
+
* - **RTL / vertical safe.** All geometry is physical viewport rects and the scroll uses
|
|
24
|
+
* `scrollIntoView({ block: "nearest", inline: "nearest" })`, which resolves direction natively —
|
|
25
|
+
* so a first trigger in `dir="rtl"` (visually on the right) and a vertical rail both work
|
|
26
|
+
* without direction branches. `nearest` also means no ancestor/page scroll when the strip is
|
|
27
|
+
* already where it should be.
|
|
28
|
+
*/
|
|
29
|
+
/** Minimal rect shape — only the edges the visibility test needs. */
|
|
30
|
+
export type TabsScrollBox = {
|
|
31
|
+
left: number;
|
|
32
|
+
right: number;
|
|
33
|
+
top: number;
|
|
34
|
+
bottom: number;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* True when `trigger` sits entirely inside the `scrollport` box. Pure geometry — physical viewport
|
|
38
|
+
* coordinates, so it is direction- and orientation-agnostic (LTR, RTL, vertical rail alike).
|
|
39
|
+
*
|
|
40
|
+
* A zero-area scrollport (unmounted, `display:none`, or an environment without layout such as
|
|
41
|
+
* jsdom) reports `true`: there is no measurable overflow to correct, and scrolling a hidden strip
|
|
42
|
+
* would be meaningless.
|
|
43
|
+
*/
|
|
44
|
+
export declare function isTabFullyVisible(scrollport: TabsScrollBox, trigger: TabsScrollBox): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* The trigger the strip must keep reachable: the focused trigger when the roving focus is inside
|
|
47
|
+
* this list (manual activation can park focus off the selected tab), otherwise the active one.
|
|
48
|
+
*/
|
|
49
|
+
export declare function resolveTabScrollTarget(list: HTMLElement): HTMLElement | null;
|
|
50
|
+
/** `true` when the user has asked for reduced motion (falsy/unsupported environments say no). */
|
|
51
|
+
export declare function prefersReducedMotion(): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Scrolls the list's keep-visible target back into the scrollport when — and only when — it is not
|
|
54
|
+
* already fully inside it. Returns the element it scrolled, or `null` when nothing needed doing
|
|
55
|
+
* (which is the common case, and what keeps this from fighting a deliberate user scroll).
|
|
56
|
+
*/
|
|
57
|
+
export declare function syncActiveTabIntoView(list: HTMLElement | null, behavior?: ScrollBehavior): HTMLElement | null;
|
|
58
|
+
/**
|
|
59
|
+
* Wires the two signals that can strand the active tab off-screen:
|
|
60
|
+
*
|
|
61
|
+
* - a `ResizeObserver` on the strip — a responsive width change (1440 → 1024 → 390) keeps/shifts
|
|
62
|
+
* the old `scrollLeft`; the observer's initial delivery also covers first mount at a new width;
|
|
63
|
+
* - a `MutationObserver` on descendant `data-state` — the selected tab changing from outside the
|
|
64
|
+
* strip (router/URL state, a controlled parent) must bring the newly-selected trigger into view.
|
|
65
|
+
*
|
|
66
|
+
* Resize corrections are instant; a selection change animates unless reduced motion is requested.
|
|
67
|
+
* Both observers are optional: without them (SSR, ancient runtimes) the component degrades to the
|
|
68
|
+
* pre-fix behaviour instead of throwing.
|
|
69
|
+
*/
|
|
70
|
+
export declare function useKeepActiveTabVisible(listRef: React.RefObject<HTMLElement | null>): void;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
const VISIBILITY_EPSILON = 1;
|
|
4
|
+
function isTabFullyVisible(scrollport, trigger) {
|
|
5
|
+
const width = scrollport.right - scrollport.left;
|
|
6
|
+
const height = scrollport.bottom - scrollport.top;
|
|
7
|
+
if (width <= 0 && height <= 0) return true;
|
|
8
|
+
return trigger.left >= scrollport.left - VISIBILITY_EPSILON && trigger.right <= scrollport.right + VISIBILITY_EPSILON && trigger.top >= scrollport.top - VISIBILITY_EPSILON && trigger.bottom <= scrollport.bottom + VISIBILITY_EPSILON;
|
|
9
|
+
}
|
|
10
|
+
function resolveTabScrollTarget(list) {
|
|
11
|
+
const focused = list.ownerDocument?.activeElement;
|
|
12
|
+
if (focused instanceof HTMLElement && focused !== list && list.contains(focused) && focused.getAttribute("role") === "tab") {
|
|
13
|
+
return focused;
|
|
14
|
+
}
|
|
15
|
+
return list.querySelector('[role="tab"][data-state="active"]');
|
|
16
|
+
}
|
|
17
|
+
function prefersReducedMotion() {
|
|
18
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
19
|
+
try {
|
|
20
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function syncActiveTabIntoView(list, behavior = "auto") {
|
|
26
|
+
if (!list) return null;
|
|
27
|
+
const target = resolveTabScrollTarget(list);
|
|
28
|
+
if (!target || typeof target.scrollIntoView !== "function") return null;
|
|
29
|
+
if (isTabFullyVisible(list.getBoundingClientRect(), target.getBoundingClientRect())) return null;
|
|
30
|
+
target.scrollIntoView({
|
|
31
|
+
block: "nearest",
|
|
32
|
+
inline: "nearest",
|
|
33
|
+
behavior: prefersReducedMotion() ? "auto" : behavior
|
|
34
|
+
});
|
|
35
|
+
return target;
|
|
36
|
+
}
|
|
37
|
+
function useKeepActiveTabVisible(listRef) {
|
|
38
|
+
React.useEffect(() => {
|
|
39
|
+
const list = listRef.current;
|
|
40
|
+
if (!list) return void 0;
|
|
41
|
+
syncActiveTabIntoView(list, "auto");
|
|
42
|
+
const cleanups = [];
|
|
43
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
44
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
45
|
+
syncActiveTabIntoView(list, "auto");
|
|
46
|
+
});
|
|
47
|
+
resizeObserver.observe(list);
|
|
48
|
+
cleanups.push(() => {
|
|
49
|
+
resizeObserver.disconnect();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (typeof MutationObserver !== "undefined") {
|
|
53
|
+
const mutationObserver = new MutationObserver(() => {
|
|
54
|
+
syncActiveTabIntoView(list, "smooth");
|
|
55
|
+
});
|
|
56
|
+
mutationObserver.observe(list, {
|
|
57
|
+
subtree: true,
|
|
58
|
+
attributes: true,
|
|
59
|
+
attributeFilter: ["data-state"]
|
|
60
|
+
});
|
|
61
|
+
cleanups.push(() => {
|
|
62
|
+
mutationObserver.disconnect();
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return () => {
|
|
66
|
+
for (const cleanup of cleanups) cleanup();
|
|
67
|
+
};
|
|
68
|
+
}, [listRef]);
|
|
69
|
+
}
|
|
70
|
+
export {
|
|
71
|
+
isTabFullyVisible,
|
|
72
|
+
prefersReducedMotion,
|
|
73
|
+
resolveTabScrollTarget,
|
|
74
|
+
syncActiveTabIntoView,
|
|
75
|
+
useKeepActiveTabVisible
|
|
76
|
+
};
|
|
@@ -3,6 +3,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
|
5
5
|
import { cn } from "../../lib/utils.js";
|
|
6
|
+
import { useKeepActiveTabVisible } from "./tabs-scroll.js";
|
|
6
7
|
function resolveFallbackTabValue(items, requested) {
|
|
7
8
|
if (!items || items.length === 0) return requested;
|
|
8
9
|
if (requested !== void 0) {
|
|
@@ -41,8 +42,9 @@ function Tabs({
|
|
|
41
42
|
TabsList,
|
|
42
43
|
{
|
|
43
44
|
"data-slot": "tabs-list",
|
|
45
|
+
variant: variant === "line" ? "line" : "default",
|
|
44
46
|
className: cn(
|
|
45
|
-
variant === "line" && "h-auto w-full justify-start
|
|
47
|
+
variant === "line" && "h-auto w-full justify-start border-b p-0",
|
|
46
48
|
variant === "card" && "w-full justify-start",
|
|
47
49
|
listClassName
|
|
48
50
|
),
|
|
@@ -52,7 +54,9 @@ function Tabs({
|
|
|
52
54
|
value: item.value,
|
|
53
55
|
disabled: item.disabled,
|
|
54
56
|
className: cn(
|
|
55
|
-
|
|
57
|
+
// Geometry only — the active underline is the token-owned `::after` bar the
|
|
58
|
+
// line variant already owns (never a second, hand-rolled border-b indicator).
|
|
59
|
+
variant === "line" && "rounded-none px-4 py-2",
|
|
56
60
|
variant === "card" && "data-[state=active]:shadow-sm"
|
|
57
61
|
),
|
|
58
62
|
children: item.label
|
|
@@ -75,26 +79,40 @@ function Tabs({
|
|
|
75
79
|
}
|
|
76
80
|
);
|
|
77
81
|
}
|
|
78
|
-
const TabsList = React.forwardRef(({ className, variant = "default", ...props }, ref) =>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
82
|
+
const TabsList = React.forwardRef(({ className, variant = "default", ...props }, ref) => {
|
|
83
|
+
const listRef = React.useRef(null);
|
|
84
|
+
const setListRef = React.useCallback(
|
|
85
|
+
(node) => {
|
|
86
|
+
listRef.current = node;
|
|
87
|
+
if (typeof ref === "function") ref(node);
|
|
88
|
+
else if (ref) ref.current = node;
|
|
89
|
+
},
|
|
90
|
+
[ref]
|
|
91
|
+
);
|
|
92
|
+
useKeepActiveTabVisible(listRef);
|
|
93
|
+
return /* @__PURE__ */ jsx(
|
|
94
|
+
TabsPrimitive.List,
|
|
95
|
+
{
|
|
96
|
+
ref: setListRef,
|
|
97
|
+
"data-slot": "tabs-list",
|
|
98
|
+
"data-variant": variant,
|
|
99
|
+
className: cn(
|
|
100
|
+
// `min-w-0 max-w-full` let the list shrink to (and never exceed) whatever width its
|
|
101
|
+
// ancestors actually give it instead of forcing them wider; horizontal orientation then
|
|
102
|
+
// scrolls its own overflow rather than clipping/hiding long localized labels in a narrow
|
|
103
|
+
// container (gh#175). Hidden scrollbar keeps the strip visually clean while staying
|
|
104
|
+
// swipeable on touch and reachable via keyboard (arrow-key roving focus still scrolls the
|
|
105
|
+
// newly-focused trigger into view natively); `useKeepActiveTabVisible` above re-pins the
|
|
106
|
+
// active trigger when a resize would otherwise strand it off-strip (gh#204). Vertical
|
|
107
|
+
// orientation is untouched — it already stacks in a column and is sized by its own
|
|
108
|
+
// `h-*`/`w-*` overrides.
|
|
109
|
+
"group/tabs-list text-muted-foreground data-[variant=default]:bg-muted inline-flex w-fit max-w-full min-w-0 items-center justify-center rounded-lg p-1 group-data-[orientation=vertical]/tabs:flex-col data-[orientation=horizontal]:[scrollbar-width:none] data-[orientation=horizontal]:overflow-x-auto data-[orientation=horizontal]:overflow-y-hidden data-[variant=line]:gap-1 data-[variant=line]:rounded-none data-[variant=line]:bg-transparent [&[data-orientation=horizontal]::-webkit-scrollbar]:hidden",
|
|
110
|
+
className
|
|
111
|
+
),
|
|
112
|
+
...props
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
});
|
|
98
116
|
TabsList.displayName = TabsPrimitive.List.displayName;
|
|
99
117
|
const TabsTrigger = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
100
118
|
TabsPrimitive.Trigger,
|
|
@@ -102,7 +120,13 @@ const TabsTrigger = React.forwardRef(({ className, ...props }, ref) => /* @__PUR
|
|
|
102
120
|
ref,
|
|
103
121
|
"data-slot": "tabs-trigger",
|
|
104
122
|
className: cn(
|
|
105
|
-
|
|
123
|
+
// The SELECTED-state ring (`ring-1 ring-primary/25`) is scoped to the default/card lists —
|
|
124
|
+
// the `line` variant is underline-only (gh#248) and must NEVER paint a surrounding ring, or
|
|
125
|
+
// it competes with (and at equal specificity overrides) the `:focus-visible` ring. The
|
|
126
|
+
// focus-visible ring/outline below is deliberately left unscoped: every variant keeps a
|
|
127
|
+
// visible keyboard focus indicator (WCAG 2.4.7). The line indicator itself lives in
|
|
128
|
+
// src/styles/navigation-layout.css so it reads the --tabs-indicator-* tokens.
|
|
129
|
+
"text-muted-foreground ring-offset-background hover:text-foreground focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-ring/50 data-[state=active]:bg-background data-[state=active]:text-foreground group-data-[variant=default]/tabs-list:data-[state=active]:ring-primary/25 relative inline-flex flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-3 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start group-data-[variant=line]/tabs-list:border-e-0 group-data-[variant=line]/tabs-list:border-b-0 focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=default]/tabs-list:data-[state=active]:ring-1 group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none",
|
|
106
130
|
className
|
|
107
131
|
),
|
|
108
132
|
...props
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import * as AvatarPrimitive from "@radix-ui/react-avatar";
|
|
3
|
-
|
|
3
|
+
import type { AvatarProp } from "../../props/components/data-display.prop.js";
|
|
4
|
+
export declare const Avatar: React.ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarProps & React.RefAttributes<HTMLSpanElement>, "ref"> & Pick<AvatarProp, "shape"> & React.RefAttributes<HTMLSpanElement>>;
|
|
4
5
|
export declare const AvatarImage: React.ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarImageProps & React.RefAttributes<HTMLImageElement>, "ref"> & React.RefAttributes<HTMLImageElement>>;
|
|
5
6
|
export declare const AvatarFallback: React.ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarFallbackProps & React.RefAttributes<HTMLSpanElement>, "ref"> & React.RefAttributes<HTMLSpanElement>>;
|
|
@@ -3,11 +3,12 @@ import { jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import * as AvatarPrimitive from "@radix-ui/react-avatar";
|
|
5
5
|
import { cn } from "../../lib/utils.js";
|
|
6
|
-
const Avatar = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
6
|
+
const Avatar = React.forwardRef(({ className, shape = "circle", ...props }, ref) => /* @__PURE__ */ jsx(
|
|
7
7
|
AvatarPrimitive.Root,
|
|
8
8
|
{
|
|
9
9
|
ref,
|
|
10
10
|
"data-slot": "avatar",
|
|
11
|
+
"data-shape": shape === "square" ? "square" : void 0,
|
|
11
12
|
className: cn("ui-avatar", className),
|
|
12
13
|
...props
|
|
13
14
|
}
|
|
@@ -26,7 +26,10 @@ export declare const InputOTP: React.ForwardRefExoticComponent<(Omit<Omit<React.
|
|
|
26
26
|
render?: never;
|
|
27
27
|
children: React.ReactNode;
|
|
28
28
|
} & React.RefAttributes<HTMLInputElement>, "ref">) & React.RefAttributes<HTMLInputElement>>;
|
|
29
|
-
export
|
|
29
|
+
export type { InputOTPGroupAppearanceProp, InputOTPGroupProp, InputOTPGroupProp as InputOTPGroupProps, } from "../../props/components/data-entry.prop.js";
|
|
30
|
+
export declare const InputOTPGroup: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & {
|
|
31
|
+
appearance?: import("./input-otp.js").InputOTPGroupAppearanceProp;
|
|
32
|
+
} & React.RefAttributes<HTMLDivElement>>;
|
|
30
33
|
export declare const InputOTPSlot: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & {
|
|
31
34
|
index: number;
|
|
32
35
|
} & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -16,11 +16,12 @@ const InputOTP = React.forwardRef(({ className, containerClassName, ...props },
|
|
|
16
16
|
));
|
|
17
17
|
InputOTP.displayName = "InputOTP";
|
|
18
18
|
const InputOTPGroup = React.forwardRef(
|
|
19
|
-
({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
19
|
+
({ appearance = "slots", className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
20
20
|
"div",
|
|
21
21
|
{
|
|
22
22
|
ref,
|
|
23
23
|
"data-slot": "input-otp-group",
|
|
24
|
+
"data-appearance": appearance,
|
|
24
25
|
className: cn("ui-otp-group", className),
|
|
25
26
|
...props
|
|
26
27
|
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
|
|
3
3
|
import { type ToggleProps } from "./toggle.js";
|
|
4
|
+
type ToggleGroupVariant = ToggleProps["variant"];
|
|
5
|
+
type ToggleGroupSize = ToggleProps["size"];
|
|
4
6
|
export declare const ToggleGroup: React.ForwardRefExoticComponent<((Omit<ToggleGroupPrimitive.ToggleGroupSingleProps & React.RefAttributes<HTMLDivElement>, "ref"> | Omit<ToggleGroupPrimitive.ToggleGroupMultipleProps & React.RefAttributes<HTMLDivElement>, "ref">) & {
|
|
5
|
-
variant?:
|
|
6
|
-
size?:
|
|
7
|
+
variant?: ToggleGroupVariant;
|
|
8
|
+
size?: ToggleGroupSize;
|
|
7
9
|
}) & React.RefAttributes<HTMLDivElement>>;
|
|
8
10
|
export declare const ToggleGroupItem: React.ForwardRefExoticComponent<Omit<ToggleGroupPrimitive.ToggleGroupItemProps & React.RefAttributes<HTMLButtonElement>, "ref"> & {
|
|
9
|
-
variant?:
|
|
10
|
-
size?:
|
|
11
|
+
variant?: ToggleGroupVariant;
|
|
12
|
+
size?: ToggleGroupSize;
|
|
11
13
|
} & React.RefAttributes<HTMLButtonElement>>;
|
|
14
|
+
export {};
|
|
@@ -4,28 +4,39 @@ import * as React from "react";
|
|
|
4
4
|
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
|
|
5
5
|
import { cn } from "../../lib/utils.js";
|
|
6
6
|
import { toggleVariants } from "./toggle.js";
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
{
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
7
|
+
const ToggleGroupContext = React.createContext({});
|
|
8
|
+
const ToggleGroup = React.forwardRef(({ className, variant, size, children, ...props }, ref) => {
|
|
9
|
+
const context = React.useMemo(() => ({ variant, size }), [variant, size]);
|
|
10
|
+
return /* @__PURE__ */ jsx(
|
|
11
|
+
ToggleGroupPrimitive.Root,
|
|
12
|
+
{
|
|
13
|
+
ref,
|
|
14
|
+
"data-slot": "toggle-group",
|
|
15
|
+
"data-variant": variant,
|
|
16
|
+
"data-size": size,
|
|
17
|
+
className: cn("ui-toggle-group", className),
|
|
18
|
+
...props,
|
|
19
|
+
children: /* @__PURE__ */ jsx(ToggleGroupContext.Provider, { value: context, children })
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
});
|
|
19
23
|
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
|
|
20
|
-
const ToggleGroupItem = React.forwardRef(({ className, variant, size, ...props }, ref) =>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
const ToggleGroupItem = React.forwardRef(({ className, variant, size, ...props }, ref) => {
|
|
25
|
+
const context = React.useContext(ToggleGroupContext);
|
|
26
|
+
const resolvedVariant = variant ?? context.variant;
|
|
27
|
+
const resolvedSize = size ?? context.size;
|
|
28
|
+
return /* @__PURE__ */ jsx(
|
|
29
|
+
ToggleGroupPrimitive.Item,
|
|
30
|
+
{
|
|
31
|
+
ref,
|
|
32
|
+
"data-slot": "toggle-group-item",
|
|
33
|
+
"data-variant": resolvedVariant,
|
|
34
|
+
"data-size": resolvedSize,
|
|
35
|
+
className: cn(toggleVariants({ variant: resolvedVariant, size: resolvedSize }), className),
|
|
36
|
+
...props
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
});
|
|
29
40
|
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
|
|
30
41
|
export {
|
|
31
42
|
ToggleGroup,
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canonical GoDX brand mark, as email-safe markup.
|
|
3
|
+
*
|
|
4
|
+
* The mark is an emerald CAPSULE with an internal light GLYPH bar — the same artwork the React
|
|
5
|
+
* `<Logo mark="godx" />` paints, at the same 32×32 viewBox and the same coordinates (a test asserts
|
|
6
|
+
* the capsule path is byte-identical to the component's, so the two can never diverge). A capsule
|
|
7
|
+
* WITHOUT the internal glyph is the incomplete mark and must not ship. The ARTWORK matches the
|
|
8
|
+
* canonical SCR-302 raster exactly; only the rendered BOX is email-specific — the canonical email
|
|
9
|
+
* header sets it at 22px (`--email-mark-width`) beside the "GoDX" wordmark, where the web
|
|
10
|
+
* `<Logo mark="godx" />` box is 32px.
|
|
11
|
+
*
|
|
12
|
+
* Nothing here references an external or relative asset: an email template has no base URL it can
|
|
13
|
+
* trust, remote images are blocked by default in most clients, and a `cid:` attachment needs
|
|
14
|
+
* transport support. So the mark is delivered three ways, in order of fidelity:
|
|
15
|
+
*
|
|
16
|
+
* 1. `svg` — inline `<svg>`. Apple Mail, iOS Mail, Thunderbird, Samsung Mail. Best fidelity.
|
|
17
|
+
* 2. `dataUri` — the same SVG as a `data:` URL for `<img src>`. No network fetch.
|
|
18
|
+
* 3. `tableHtml` — bulletproof `<table>`/`<div>` markup with background-color + border-radius.
|
|
19
|
+
* Renders everywhere; degrades to a square emerald block with a light bar in Outlook's Word
|
|
20
|
+
* renderer (which drops `border-radius`) — still recognisably the mark, never a broken image.
|
|
21
|
+
*
|
|
22
|
+
* Colours come from `EMAIL_COLORS` (derived from `--brand` / `--brand-foreground`, the roles
|
|
23
|
+
* `--logo-godx-color` points at), so a re-themed identity role re-tints the mark. NOT `--success`:
|
|
24
|
+
* that is the wakatake STATUS green, and binding identity to it rendered the mark ~ΔE76 17 away
|
|
25
|
+
* from the canonical emerald until gh#250 introduced the dedicated `--brand` role.
|
|
26
|
+
*/
|
|
27
|
+
import { type EmailHex } from "./color.js";
|
|
28
|
+
/** A rounded rectangle in the mark's 32×32 coordinate space. */
|
|
29
|
+
export interface EmailBrandMarkRect {
|
|
30
|
+
readonly x: number;
|
|
31
|
+
readonly y: number;
|
|
32
|
+
readonly width: number;
|
|
33
|
+
readonly height: number;
|
|
34
|
+
readonly radius: number;
|
|
35
|
+
}
|
|
36
|
+
/** Intrinsic coordinate space of the mark artwork. */
|
|
37
|
+
export declare const EMAIL_BRAND_MARK_VIEWBOX = "0 0 32 32";
|
|
38
|
+
/** The emerald capsule. */
|
|
39
|
+
export declare const EMAIL_BRAND_MARK_CAPSULE: EmailBrandMarkRect;
|
|
40
|
+
/** The internal light glyph bar inside the capsule. */
|
|
41
|
+
export declare const EMAIL_BRAND_MARK_GLYPH: EmailBrandMarkRect;
|
|
42
|
+
/**
|
|
43
|
+
* Path data for a rounded rectangle, in the exact serialisation the canonical mark uses — so the
|
|
44
|
+
* generated capsule string equals the artwork shipped by `<Logo mark="godx" />` character for
|
|
45
|
+
* character.
|
|
46
|
+
*/
|
|
47
|
+
export declare function roundedRectPath({ x, y, width, height, radius }: EmailBrandMarkRect): string;
|
|
48
|
+
/** Options for rendering the mark. Every default comes from the token contract. */
|
|
49
|
+
export interface EmailBrandMarkOptions {
|
|
50
|
+
/** Rendered width in px. Default `EMAIL_BRAND_MARK.widthPx` (the canonical 22px header box). */
|
|
51
|
+
width?: number;
|
|
52
|
+
/** Rendered height in px. Default `EMAIL_BRAND_MARK.heightPx` (the canonical 22px header box). */
|
|
53
|
+
height?: number;
|
|
54
|
+
/** Capsule fill. Default `EMAIL_COLORS.brand`. */
|
|
55
|
+
color?: EmailHex;
|
|
56
|
+
/** Internal glyph fill. Default `EMAIL_COLORS.brandForeground`. */
|
|
57
|
+
glyphColor?: EmailHex;
|
|
58
|
+
/**
|
|
59
|
+
* Accessible name. Pass `""` for a decorative mark that sits next to a readable wordmark — the
|
|
60
|
+
* markup then carries `alt=""` / `aria-hidden` instead of a redundant announcement.
|
|
61
|
+
*/
|
|
62
|
+
label?: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Inline `<svg>` markup for the mark — self-contained, no external or relative asset reference.
|
|
66
|
+
* The `xmlns` attribute is required: an email body is not always parsed as HTML5.
|
|
67
|
+
*/
|
|
68
|
+
export declare function emailBrandMarkSvg(o?: EmailBrandMarkOptions): string;
|
|
69
|
+
/**
|
|
70
|
+
* The mark as a `data:image/svg+xml` URL for `<img src>`. Percent-encoded (not base64) so it stays
|
|
71
|
+
* readable in a template and needs no `Buffer`/`btoa`. Still zero network requests.
|
|
72
|
+
*/
|
|
73
|
+
export declare function emailBrandMarkDataUri(o?: EmailBrandMarkOptions): string;
|
|
74
|
+
/**
|
|
75
|
+
* Bulletproof `<table>` markup for clients with no SVG support. Uses only `background-color`,
|
|
76
|
+
* `border-radius`, fixed cell sizes and `font-size:0` — the intersection every client renders.
|
|
77
|
+
*/
|
|
78
|
+
export declare function emailBrandMarkTableHtml(o?: EmailBrandMarkOptions): string;
|
|
79
|
+
/** The canonical mark at its default size and colours, in all three delivery forms. */
|
|
80
|
+
export interface EmailBrandMark {
|
|
81
|
+
readonly viewBox: string;
|
|
82
|
+
readonly width: string;
|
|
83
|
+
readonly widthPx: number;
|
|
84
|
+
readonly height: string;
|
|
85
|
+
readonly heightPx: number;
|
|
86
|
+
readonly capsule: EmailBrandMarkRect;
|
|
87
|
+
readonly glyph: EmailBrandMarkRect;
|
|
88
|
+
readonly capsulePath: string;
|
|
89
|
+
readonly glyphPath: string;
|
|
90
|
+
/**
|
|
91
|
+
* Mark ↔ wordmark separation in the canonical header LOCKUP. The mark alone is not the header:
|
|
92
|
+
* the canonical card sets the mark beside a readable "GoDX" wordmark, and the wordmark — not the
|
|
93
|
+
* mark — carries the accessible name (render the mark with `label: ""` in that pairing).
|
|
94
|
+
*/
|
|
95
|
+
readonly gap: string;
|
|
96
|
+
readonly gapPx: number;
|
|
97
|
+
/** Wordmark type size in the lockup. */
|
|
98
|
+
readonly wordmarkFontSize: string;
|
|
99
|
+
readonly wordmarkFontSizePx: number;
|
|
100
|
+
readonly wordmarkFontWeight: number;
|
|
101
|
+
/** Inline `<svg>` — highest fidelity. */
|
|
102
|
+
readonly svg: string;
|
|
103
|
+
/** `data:` URL of the same SVG, for `<img src>`. */
|
|
104
|
+
readonly dataUri: string;
|
|
105
|
+
/** `<table>` fallback for clients with no SVG support. */
|
|
106
|
+
readonly tableHtml: string;
|
|
107
|
+
}
|
|
108
|
+
export declare const EMAIL_BRAND_MARK: EmailBrandMark;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { EMAIL_COLORS } from "./color.js";
|
|
2
|
+
import { EMAIL_GEOMETRY_SOURCE } from "./tokens.generated.js";
|
|
3
|
+
const EMAIL_BRAND_MARK_VIEWBOX = "0 0 32 32";
|
|
4
|
+
const EMAIL_BRAND_MARK_CAPSULE = Object.freeze({
|
|
5
|
+
x: 1,
|
|
6
|
+
y: 7,
|
|
7
|
+
width: 30,
|
|
8
|
+
height: 18,
|
|
9
|
+
radius: 7
|
|
10
|
+
});
|
|
11
|
+
const EMAIL_BRAND_MARK_GLYPH = Object.freeze({
|
|
12
|
+
x: 7,
|
|
13
|
+
y: 13,
|
|
14
|
+
width: 18,
|
|
15
|
+
height: 6,
|
|
16
|
+
radius: 1
|
|
17
|
+
});
|
|
18
|
+
const sep = (value) => value < 0 ? String(value) : ` ${value}`;
|
|
19
|
+
const arc = (r, dx, dy) => `a${r} ${r} 0 0 1${sep(dx)}${sep(dy)}`;
|
|
20
|
+
function roundedRectPath({ x, y, width, height, radius }) {
|
|
21
|
+
const h = width - 2 * radius;
|
|
22
|
+
const v = height - 2 * radius;
|
|
23
|
+
return `M${x + radius} ${y}h${h}${arc(radius, radius, radius)}v${v}${arc(radius, -radius, radius)}H${x + radius}${arc(radius, -radius, -radius)}v-${v}${arc(radius, radius, -radius)}Z`;
|
|
24
|
+
}
|
|
25
|
+
const CAPSULE_PATH = roundedRectPath(EMAIL_BRAND_MARK_CAPSULE);
|
|
26
|
+
const GLYPH_PATH = roundedRectPath(EMAIL_BRAND_MARK_GLYPH);
|
|
27
|
+
const DEFAULT_WIDTH = Number.parseFloat(EMAIL_GEOMETRY_SOURCE["--email-mark-width"]);
|
|
28
|
+
const DEFAULT_HEIGHT = Number.parseFloat(EMAIL_GEOMETRY_SOURCE["--email-mark-height"]);
|
|
29
|
+
function options(o = {}) {
|
|
30
|
+
return {
|
|
31
|
+
width: o.width ?? DEFAULT_WIDTH,
|
|
32
|
+
height: o.height ?? DEFAULT_HEIGHT,
|
|
33
|
+
color: o.color ?? EMAIL_COLORS.brand,
|
|
34
|
+
glyphColor: o.glyphColor ?? EMAIL_COLORS.brandForeground,
|
|
35
|
+
label: o.label ?? "GoDX"
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const attr = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
39
|
+
function emailBrandMarkSvg(o = {}) {
|
|
40
|
+
const { width, height, color, glyphColor, label } = options(o);
|
|
41
|
+
const a11y = label ? ` role="img" aria-label="${attr(label)}"` : ` role="presentation" aria-hidden="true"`;
|
|
42
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="${EMAIL_BRAND_MARK_VIEWBOX}" focusable="false"${a11y} style="display:block;border:0;outline:none;text-decoration:none"><path fill="${color}" d="${CAPSULE_PATH}"/><path fill="${glyphColor}" d="${GLYPH_PATH}"/></svg>`;
|
|
43
|
+
}
|
|
44
|
+
function emailBrandMarkDataUri(o = {}) {
|
|
45
|
+
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(emailBrandMarkSvg(o))}`;
|
|
46
|
+
}
|
|
47
|
+
function emailBrandMarkTableHtml(o = {}) {
|
|
48
|
+
const { width, height, color, glyphColor, label } = options(o);
|
|
49
|
+
const scale = width / 32;
|
|
50
|
+
const box = (rect) => ({
|
|
51
|
+
width: Math.round(rect.width * scale),
|
|
52
|
+
height: Math.round(rect.height * scale * height / width),
|
|
53
|
+
radius: Math.round(rect.radius * scale)
|
|
54
|
+
});
|
|
55
|
+
const capsule = box(EMAIL_BRAND_MARK_CAPSULE);
|
|
56
|
+
const glyph = box(EMAIL_BRAND_MARK_GLYPH);
|
|
57
|
+
const a11y = label ? ` role="img" aria-label="${attr(label)}"` : ` role="presentation" aria-hidden="true"`;
|
|
58
|
+
return `<table${a11y} border="0" cellpadding="0" cellspacing="0" width="${capsule.width}" style="border-collapse:separate;border-spacing:0;width:${capsule.width}px"><tr><td width="${capsule.width}" height="${capsule.height}" align="center" valign="middle" style="width:${capsule.width}px;height:${capsule.height}px;line-height:${capsule.height}px;font-size:0;mso-line-height-rule:exactly;background-color:${color};border-radius:${capsule.radius}px"><div style="width:${glyph.width}px;height:${glyph.height}px;line-height:${glyph.height}px;font-size:0;background-color:${glyphColor};border-radius:${glyph.radius}px">​</div></td></tr></table>`;
|
|
59
|
+
}
|
|
60
|
+
const EMAIL_BRAND_MARK = Object.freeze({
|
|
61
|
+
viewBox: EMAIL_BRAND_MARK_VIEWBOX,
|
|
62
|
+
width: EMAIL_GEOMETRY_SOURCE["--email-mark-width"],
|
|
63
|
+
widthPx: DEFAULT_WIDTH,
|
|
64
|
+
height: EMAIL_GEOMETRY_SOURCE["--email-mark-height"],
|
|
65
|
+
heightPx: DEFAULT_HEIGHT,
|
|
66
|
+
capsule: EMAIL_BRAND_MARK_CAPSULE,
|
|
67
|
+
glyph: EMAIL_BRAND_MARK_GLYPH,
|
|
68
|
+
capsulePath: CAPSULE_PATH,
|
|
69
|
+
glyphPath: GLYPH_PATH,
|
|
70
|
+
gap: EMAIL_GEOMETRY_SOURCE["--email-mark-gap"],
|
|
71
|
+
gapPx: Number.parseFloat(EMAIL_GEOMETRY_SOURCE["--email-mark-gap"]),
|
|
72
|
+
wordmarkFontSize: EMAIL_GEOMETRY_SOURCE["--email-wordmark-font-size"],
|
|
73
|
+
wordmarkFontSizePx: Number.parseFloat(EMAIL_GEOMETRY_SOURCE["--email-wordmark-font-size"]),
|
|
74
|
+
wordmarkFontWeight: Number.parseFloat(EMAIL_GEOMETRY_SOURCE["--email-wordmark-font-weight"]),
|
|
75
|
+
svg: emailBrandMarkSvg(),
|
|
76
|
+
dataUri: emailBrandMarkDataUri(),
|
|
77
|
+
tableHtml: emailBrandMarkTableHtml()
|
|
78
|
+
});
|
|
79
|
+
export {
|
|
80
|
+
EMAIL_BRAND_MARK,
|
|
81
|
+
EMAIL_BRAND_MARK_CAPSULE,
|
|
82
|
+
EMAIL_BRAND_MARK_GLYPH,
|
|
83
|
+
EMAIL_BRAND_MARK_VIEWBOX,
|
|
84
|
+
emailBrandMarkDataUri,
|
|
85
|
+
emailBrandMarkSvg,
|
|
86
|
+
emailBrandMarkTableHtml,
|
|
87
|
+
roundedRectPath
|
|
88
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email colour contract — semantic web roles resolved to LITERAL hex.
|
|
3
|
+
*
|
|
4
|
+
* HTML email has no cascade worth trusting: Gmail strips <style>, Outlook ignores custom
|
|
5
|
+
* properties, and every colour must appear inline as `#rrggbb` on the element that paints it. The
|
|
6
|
+
* web tokens, meanwhile, are HSL channel triplets (`--primary: 204 100% 39%`) so they can be
|
|
7
|
+
* alpha-composed. This module bridges the two — the triplets are GENERATED from
|
|
8
|
+
* src/tokens/foundation.css (scripts/gen-email-tokens.mjs) and converted here, at module load, by
|
|
9
|
+
* the same `hslToHex` a consumer can call on its own brand roles. There is no hex literal in this
|
|
10
|
+
* directory, so an email palette that drifts from the web palette is structurally impossible.
|
|
11
|
+
*/
|
|
12
|
+
import { EMAIL_COLOR_SOURCE } from "./tokens.generated.js";
|
|
13
|
+
/**
|
|
14
|
+
* The email palette slots. Each maps 1:1 to a semantic role in `src/tokens/foundation.css`
|
|
15
|
+
* (`EMAIL_COLOR_SOURCE[token].cssVar` names it), so a re-themed role re-tints the email.
|
|
16
|
+
*/
|
|
17
|
+
export type EmailColorToken = keyof typeof EMAIL_COLOR_SOURCE;
|
|
18
|
+
/** A `#rrggbb` string — the only colour form every email client renders identically. */
|
|
19
|
+
export type EmailHex = `#${string}`;
|
|
20
|
+
/**
|
|
21
|
+
* Convert a CSS HSL channel triplet (`"204 100% 39%"` — the shape every `@godxjp/ui` colour role
|
|
22
|
+
* uses) into the `#rrggbb` literal an email client needs. Rounds each channel the way a browser
|
|
23
|
+
* does, so the hex is exactly what `hsl(var(--role))` paints on the web.
|
|
24
|
+
*
|
|
25
|
+
* Consumers with a custom brand role can call this directly:
|
|
26
|
+
* `hslToHex(getComputedStyle(el).getPropertyValue("--primary"))`.
|
|
27
|
+
*
|
|
28
|
+
* @throws if the input is not a bare `H S% L%` triplet (a hex or `hsl(...)` wrapper is rejected
|
|
29
|
+
* rather than silently mis-parsed).
|
|
30
|
+
*/
|
|
31
|
+
export declare function hslToHex(triplet: string): EmailHex;
|
|
32
|
+
/**
|
|
33
|
+
* The canonical (light-scheme) transactional-email palette, as literal hex.
|
|
34
|
+
*
|
|
35
|
+
* | slot | web role | used for |
|
|
36
|
+
* | ----------------------------- | --------------------------------- | --------------------------------- |
|
|
37
|
+
* | `background` | `--background` | the page wrapper behind the card |
|
|
38
|
+
* | `foreground` | `--foreground` | body copy |
|
|
39
|
+
* | `surface` / `surfaceForeground`| `--card` / `--card-foreground` | the 480px card |
|
|
40
|
+
* | `muted` / `mutedForeground` | `--muted` / `--muted-foreground` | quiet bands, legal copy |
|
|
41
|
+
* | `border` | `--border` | card edge, footer hairline |
|
|
42
|
+
* | `primary` / `primaryForeground`| `--primary` / `--primary-foreground` | the one CTA |
|
|
43
|
+
* | `focus` | `--ring` | focus affordance in webmail panes |
|
|
44
|
+
* | `brand` / `brandForeground` | `--brand` / `--brand-foreground` | the GoDX capsule + its glyph |
|
|
45
|
+
* | `urgency` / `urgencyForeground`| `--attention` / `--attention-foreground` | action-required accent / solid large label |
|
|
46
|
+
*/
|
|
47
|
+
export declare const EMAIL_COLORS: Readonly<Record<EmailColorToken, EmailHex>>;
|
|
48
|
+
/**
|
|
49
|
+
* The dark-scheme palette, derived from the same roles under `.dark` /
|
|
50
|
+
* `:root[data-theme="dark"]`. Use it inside `@media (prefers-color-scheme: dark)` for the clients
|
|
51
|
+
* that honour it — never as the only palette, since most clients do not.
|
|
52
|
+
*/
|
|
53
|
+
export declare const EMAIL_COLORS_DARK: Readonly<Record<EmailColorToken, EmailHex>>;
|