@godxjp/ui 19.4.2 → 19.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/dist/app/app-provider.js +10 -1
- package/dist/app/theme-axes.d.ts +18 -3
- package/dist/app/theme-axes.js +15 -3
- package/dist/components/data-display/badge.js +4 -1
- package/dist/components/data-display/data-table.js +9 -2
- package/dist/components/data-entry/checkbox.d.ts +12 -0
- package/dist/components/data-entry/checkbox.js +34 -3
- package/dist/components/data-entry/index.d.ts +2 -0
- package/dist/components/data-entry/index.js +2 -0
- package/dist/components/data-entry/segmented.d.ts +2 -0
- package/dist/components/data-entry/segmented.js +5 -0
- package/dist/components/data-entry/select.d.ts +2 -0
- package/dist/components/data-entry/select.js +8 -3
- package/dist/components/feedback/sheet.js +1 -1
- package/dist/components/feedback/sonner.d.ts +7 -1
- package/dist/components/feedback/sonner.js +27 -7
- package/dist/components/general/button.js +6 -1
- package/dist/components/general/inert-background.d.ts +8 -3
- package/dist/components/general/inert-background.js +26 -2
- package/dist/components/general/logo.d.ts +53 -0
- package/dist/components/general/logo.js +57 -3
- package/dist/components/general/typography.d.ts +7 -1
- package/dist/components/general/typography.js +20 -5
- package/dist/components/layout/index.d.ts +4 -0
- package/dist/components/layout/index.js +4 -0
- package/dist/components/layout/nav-list.d.ts +26 -0
- package/dist/components/layout/nav-list.js +21 -0
- package/dist/components/layout/topbar-item.d.ts +17 -0
- package/dist/components/layout/topbar-item.js +21 -0
- package/dist/components/navigation/app-setting-picker.js +22 -5
- package/dist/components/navigation/context-menu.js +2 -2
- package/dist/components/navigation/dropdown-menu.js +2 -2
- package/dist/components/navigation/tabs.js +42 -10
- package/dist/components/ui/segmented.d.ts +48 -0
- package/dist/components/ui/segmented.js +48 -0
- package/dist/email/tokens.generated.d.ts +3 -3
- package/dist/email/tokens.generated.js +3 -3
- package/dist/i18n/messages/en.json +2 -1
- package/dist/i18n/messages/ja.json +2 -1
- package/dist/i18n/messages/vi.json +2 -1
- package/dist/props/components/app.prop.d.ts +9 -2
- package/dist/props/components/general.prop.d.ts +31 -1
- package/dist/props/components/layout.prop.d.ts +48 -2
- package/dist/props/registry.d.ts +19 -0
- package/dist/props/registry.js +27 -0
- package/dist/props/vocabulary/shared.prop.d.ts +11 -2
- package/dist/styles/alert-layout.css +4 -0
- package/dist/styles/base.css +2 -0
- package/dist/styles/control.css +94 -26
- package/dist/styles/data-display-layout.css +2 -1
- package/dist/styles/dialog-layout.css +2 -1
- package/dist/styles/focus-ring.css +85 -24
- package/dist/styles/layout.css +2 -1
- package/dist/styles/logo-layout.css +56 -0
- package/dist/styles/navigation-layout.css +15 -0
- package/dist/styles/shell-layout.css +64 -10
- package/dist/styles/text-layout.css +11 -0
- package/dist/tokens/antd.generated.css +40 -0
- package/dist/tokens/axes.css +6 -0
- package/dist/tokens/base.css +4 -0
- package/dist/tokens/components/control.css +5 -1
- package/dist/tokens/components/feedback.css +2 -0
- package/dist/tokens/components/legal-document.css +0 -2
- package/dist/tokens/components/logo.css +13 -0
- package/dist/tokens/components/navigation.css +2 -0
- package/dist/tokens/components/segmented.css +27 -0
- package/dist/tokens/components/shell.css +15 -2
- package/dist/tokens/components/text.css +7 -0
- package/dist/tokens/foundation.css +19 -12
- package/docs/DESIGN-AUTHORITY.md +346 -0
- package/docs/FRAME-COVERAGE-REPORT.md +5 -2
- package/docs/README.md +14 -13
- package/docs/data-entry/segmented.tsx +124 -0
- package/docs/general/typography.tsx +84 -1
- package/docs/layout/nav-list.tsx +63 -0
- package/docs/layout/topbar-item.tsx +157 -0
- package/package.json +8 -2
package/dist/app/app-provider.js
CHANGED
|
@@ -16,7 +16,8 @@ import {
|
|
|
16
16
|
} from "../lib/datetime/index.js";
|
|
17
17
|
import { DEFAULT_STORAGE_KEY, readStoredPreferences, writeStoredPreferences } from "./storage.js";
|
|
18
18
|
import {
|
|
19
|
-
applyThemeAxes
|
|
19
|
+
applyThemeAxes,
|
|
20
|
+
PREFERS_DARK_SCHEME_QUERY
|
|
20
21
|
} from "./theme-axes.js";
|
|
21
22
|
import { resolveDefaultTimeFormat } from "./time-format-labels.js";
|
|
22
23
|
import { resolveDefaultTimezone } from "./timezones.js";
|
|
@@ -174,6 +175,14 @@ function AppProvider({
|
|
|
174
175
|
if (typeof document === "undefined") return;
|
|
175
176
|
applyThemeAxes(document.documentElement, { theme, brand, density, fontSize, scaling });
|
|
176
177
|
}, [theme, brand, density, fontSize, scaling]);
|
|
178
|
+
React.useEffect(() => {
|
|
179
|
+
if (theme !== "system") return;
|
|
180
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
181
|
+
const query = window.matchMedia(PREFERS_DARK_SCHEME_QUERY);
|
|
182
|
+
const reapply = () => applyThemeAxes(document.documentElement, { theme: "system" });
|
|
183
|
+
query.addEventListener("change", reapply);
|
|
184
|
+
return () => query.removeEventListener("change", reapply);
|
|
185
|
+
}, [theme]);
|
|
177
186
|
const setLocale = React.useCallback(
|
|
178
187
|
(next) => {
|
|
179
188
|
prefsRef.current = { ...prefsRef.current, locale: next };
|
package/dist/app/theme-axes.d.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import type { PageDensityProp } from "../props/vocabulary/layout.prop.js";
|
|
2
|
-
/**
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Theme spine — `data-theme` (alias of the legacy `.dark` class). `"system"` is a CHOICE, not a
|
|
4
|
+
* rendered value: it defers to the OS `prefers-color-scheme` and is what gets persisted, so the
|
|
5
|
+
* app keeps following the OS tomorrow morning. `data-theme` only ever carries `light` or `dark`.
|
|
6
|
+
*/
|
|
7
|
+
export type AppTheme = "light" | "dark" | "system";
|
|
8
|
+
/** What `data-theme` actually carries — the resolution of an {@link AppTheme} choice. */
|
|
9
|
+
export type ResolvedAppTheme = Exclude<AppTheme, "system">;
|
|
4
10
|
/** Primary-palette preset — `data-brand`. `null` = keep the app's own `--primary`. */
|
|
5
11
|
export type AppBrand = "brand" | "crm" | "logistics" | "partner" | "slate" | "dxs";
|
|
6
12
|
/** Control / table / spacing density — `data-density` (same vocab as PageContainer). */
|
|
7
13
|
export type AppDensity = PageDensityProp;
|
|
8
14
|
/** Base type size — `data-font-size`; a preset rescales the whole golden scale. */
|
|
9
15
|
export type AppFontSize = "sm" | "default" | "lg";
|
|
10
|
-
export declare const APP_THEMES: readonly ["light", "dark"];
|
|
16
|
+
export declare const APP_THEMES: readonly ["light", "dark", "system"];
|
|
11
17
|
export declare const APP_BRANDS: readonly ["brand", "crm", "logistics", "partner", "slate", "dxs"];
|
|
12
18
|
export declare const APP_DENSITIES: readonly ["compact", "default", "comfortable"];
|
|
13
19
|
export declare const APP_FONT_SIZES: readonly ["sm", "default", "lg"];
|
|
@@ -20,6 +26,15 @@ export type AppThemeAxes = {
|
|
|
20
26
|
scaling: number | null;
|
|
21
27
|
};
|
|
22
28
|
export declare const isAppTheme: (v: unknown) => v is AppTheme;
|
|
29
|
+
/** The media query `theme: "system"` follows. */
|
|
30
|
+
export declare const PREFERS_DARK_SCHEME_QUERY = "(prefers-color-scheme: dark)";
|
|
31
|
+
/** `false` wherever there is no `matchMedia` (SSR, jsdom without the stub) — light is the default. */
|
|
32
|
+
export declare function prefersDarkScheme(): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Resolve a stored CHOICE to the value `data-theme` carries. `light`/`dark` pass through
|
|
35
|
+
* untouched, so a preference stored before `system` existed keeps working.
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveAppTheme(theme: AppTheme): ResolvedAppTheme;
|
|
23
38
|
export declare const isAppBrand: (v: unknown) => v is AppBrand;
|
|
24
39
|
export declare const isAppDensity: (v: unknown) => v is AppDensity;
|
|
25
40
|
export declare const isAppFontSize: (v: unknown) => v is AppFontSize;
|
package/dist/app/theme-axes.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const APP_THEMES = ["light", "dark"];
|
|
1
|
+
const APP_THEMES = ["light", "dark", "system"];
|
|
2
2
|
const APP_BRANDS = [
|
|
3
3
|
"brand",
|
|
4
4
|
"crm",
|
|
@@ -14,11 +14,20 @@ const APP_DENSITIES = [
|
|
|
14
14
|
];
|
|
15
15
|
const APP_FONT_SIZES = ["sm", "default", "lg"];
|
|
16
16
|
const isAppTheme = (v) => APP_THEMES.includes(v);
|
|
17
|
+
const PREFERS_DARK_SCHEME_QUERY = "(prefers-color-scheme: dark)";
|
|
18
|
+
function prefersDarkScheme() {
|
|
19
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
20
|
+
return window.matchMedia(PREFERS_DARK_SCHEME_QUERY).matches;
|
|
21
|
+
}
|
|
22
|
+
function resolveAppTheme(theme) {
|
|
23
|
+
if (theme !== "system") return theme;
|
|
24
|
+
return prefersDarkScheme() ? "dark" : "light";
|
|
25
|
+
}
|
|
17
26
|
const isAppBrand = (v) => APP_BRANDS.includes(v);
|
|
18
27
|
const isAppDensity = (v) => APP_DENSITIES.includes(v);
|
|
19
28
|
const isAppFontSize = (v) => APP_FONT_SIZES.includes(v);
|
|
20
29
|
function applyThemeAxes(el, axes) {
|
|
21
|
-
if (axes.theme !== void 0) el.dataset.theme = axes.theme;
|
|
30
|
+
if (axes.theme !== void 0) el.dataset.theme = resolveAppTheme(axes.theme);
|
|
22
31
|
if (axes.density !== void 0) el.dataset.density = axes.density;
|
|
23
32
|
if (axes.fontSize !== void 0) el.dataset.fontSize = axes.fontSize;
|
|
24
33
|
if (axes.brand !== void 0) {
|
|
@@ -35,9 +44,12 @@ export {
|
|
|
35
44
|
APP_DENSITIES,
|
|
36
45
|
APP_FONT_SIZES,
|
|
37
46
|
APP_THEMES,
|
|
47
|
+
PREFERS_DARK_SCHEME_QUERY,
|
|
38
48
|
applyThemeAxes,
|
|
39
49
|
isAppBrand,
|
|
40
50
|
isAppDensity,
|
|
41
51
|
isAppFontSize,
|
|
42
|
-
isAppTheme
|
|
52
|
+
isAppTheme,
|
|
53
|
+
prefersDarkScheme,
|
|
54
|
+
resolveAppTheme
|
|
43
55
|
};
|
|
@@ -50,7 +50,10 @@ const STATUS_MAP = {
|
|
|
50
50
|
ASSIGNMENT_STATUS_TERMINATED: { tone: "destructive", icon: XCircle }
|
|
51
51
|
};
|
|
52
52
|
const badgeVariants = cva(
|
|
53
|
-
|
|
53
|
+
// `ui-focus-ring` = the system's single focus source (styles/focus-ring.css). It replaces a
|
|
54
|
+
// hand-rolled `focus:ring-2 focus:ring-ring`, which was token-blind (no --focus-ring-* knob
|
|
55
|
+
// reached it) and fired on `:focus`, so a mouse click on an interactive badge lit a ring.
|
|
56
|
+
"ui-focus-ring inline-flex items-center border font-medium transition-colors",
|
|
54
57
|
{
|
|
55
58
|
variants: {
|
|
56
59
|
variant: {
|
|
@@ -683,8 +683,15 @@ DataTable.Content = function DataTableContent() {
|
|
|
683
683
|
rowPadding,
|
|
684
684
|
// Hover highlight when rows are clickable OR explicitly hoverable…
|
|
685
685
|
(onRowClick || hoverable) && "hover:bg-muted/50",
|
|
686
|
-
// …but the affordance (cursor + focus
|
|
687
|
-
|
|
686
|
+
// …but the affordance (cursor + focus mark) only when clickable.
|
|
687
|
+
//
|
|
688
|
+
// `ui-focus-ring` = the single focus source (styles/focus-ring.css). It
|
|
689
|
+
// replaces `focus-visible:ring-ring focus-visible:ring-2
|
|
690
|
+
// focus-visible:ring-inset`, which was a hand-rolled second ring: it read no
|
|
691
|
+
// `--focus-ring-*` knob, so a service could not retune it, and — being a
|
|
692
|
+
// utility — it painted regardless of the `--focus-outline` switch, which is
|
|
693
|
+
// exactly the hole the switch exists to close.
|
|
694
|
+
onRowClick && "ui-focus-ring cursor-pointer",
|
|
688
695
|
isSelected && "bg-muted/30",
|
|
689
696
|
rowClassName?.(original)
|
|
690
697
|
),
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
|
3
3
|
import { CheckboxGroup } from "./checkbox-group.js";
|
|
4
|
+
/**
|
|
5
|
+
* Decorative checkbox glyph — a non-interactive `<span>`, NOT the real {@link Checkbox} (which is a
|
|
6
|
+
* `<button>`). Used where the row itself is the interactive element and a nested `<button>` would
|
|
7
|
+
* be invalid HTML (Cascader's option rows). Shares {@link CheckboxGlyph} so "partial" can never
|
|
8
|
+
* drift apart from the real control's rendering again.
|
|
9
|
+
*/
|
|
10
|
+
export declare function CheckboxVisual({ checked, indeterminate, disabled, className, }: {
|
|
11
|
+
checked: boolean;
|
|
12
|
+
indeterminate?: boolean;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
className?: string;
|
|
15
|
+
}): React.JSX.Element;
|
|
4
16
|
/** Checkbox — dùng standalone hoặc `Checkbox.Group` với `options` (Ant Design style). */
|
|
5
17
|
export declare const Checkbox: React.ForwardRefExoticComponent<Omit<CheckboxPrimitive.CheckboxProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>> & {
|
|
6
18
|
Group: typeof CheckboxGroup;
|
|
@@ -2,16 +2,42 @@
|
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
|
5
|
-
import { Check } from "lucide-react";
|
|
5
|
+
import { Check, Minus } from "lucide-react";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
7
|
import { useFieldIdentity } from "../../lib/field-a11y.js";
|
|
8
8
|
import { CheckboxGroup } from "./checkbox-group.js";
|
|
9
|
+
function CheckboxGlyph({ state }) {
|
|
10
|
+
return state === "indeterminate" ? /* @__PURE__ */ jsx(Minus, { className: "ui-checkbox-icon", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Check, { className: "ui-checkbox-icon", "aria-hidden": "true" });
|
|
11
|
+
}
|
|
12
|
+
function CheckboxVisual({
|
|
13
|
+
checked,
|
|
14
|
+
indeterminate,
|
|
15
|
+
disabled,
|
|
16
|
+
className
|
|
17
|
+
}) {
|
|
18
|
+
const state = indeterminate ? "indeterminate" : checked;
|
|
19
|
+
return /* @__PURE__ */ jsx(
|
|
20
|
+
"span",
|
|
21
|
+
{
|
|
22
|
+
"aria-hidden": "true",
|
|
23
|
+
"data-slot": "checkbox",
|
|
24
|
+
"data-state": indeterminate ? "indeterminate" : checked ? "checked" : "unchecked",
|
|
25
|
+
"data-disabled": disabled ? "" : void 0,
|
|
26
|
+
className: cn("ui-checkbox inline-flex items-center justify-center", className),
|
|
27
|
+
children: checked || indeterminate ? /* @__PURE__ */ jsx(CheckboxGlyph, { state }) : null
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
}
|
|
9
31
|
const CheckboxRoot = React.forwardRef(({ className, ...props }, ref) => {
|
|
10
32
|
const identity = useFieldIdentity({
|
|
11
33
|
id: props.id,
|
|
12
34
|
name: props.name,
|
|
13
35
|
"data-field": props["data-field"]
|
|
14
36
|
});
|
|
37
|
+
const [uncontrolled, setUncontrolled] = React.useState(
|
|
38
|
+
props.defaultChecked ?? false
|
|
39
|
+
);
|
|
40
|
+
const state = props.checked ?? uncontrolled;
|
|
15
41
|
return /* @__PURE__ */ jsx(
|
|
16
42
|
CheckboxPrimitive.Root,
|
|
17
43
|
{
|
|
@@ -31,7 +57,11 @@ const CheckboxRoot = React.forwardRef(({ className, ...props }, ref) => {
|
|
|
31
57
|
),
|
|
32
58
|
...props,
|
|
33
59
|
...identity,
|
|
34
|
-
|
|
60
|
+
onCheckedChange: (next) => {
|
|
61
|
+
setUncontrolled(next);
|
|
62
|
+
props.onCheckedChange?.(next);
|
|
63
|
+
},
|
|
64
|
+
children: /* @__PURE__ */ jsx(CheckboxPrimitive.Indicator, { "data-slot": "checkbox-indicator", className: "ui-choice-indicator", children: /* @__PURE__ */ jsx(CheckboxGlyph, { state }) })
|
|
35
65
|
}
|
|
36
66
|
);
|
|
37
67
|
});
|
|
@@ -40,5 +70,6 @@ const Checkbox = Object.assign(CheckboxRoot, {
|
|
|
40
70
|
Group: CheckboxGroup
|
|
41
71
|
});
|
|
42
72
|
export {
|
|
43
|
-
Checkbox
|
|
73
|
+
Checkbox,
|
|
74
|
+
CheckboxVisual
|
|
44
75
|
};
|
|
@@ -63,5 +63,7 @@ export { Rating } from "./rating.js";
|
|
|
63
63
|
export type { RatingProps } from "./rating.js";
|
|
64
64
|
export { TagInput } from "./tag-input.js";
|
|
65
65
|
export type { TagInputProps } from "./tag-input.js";
|
|
66
|
+
export { Segmented } from "./segmented.js";
|
|
67
|
+
export type { SegmentedOption, SegmentedProp, SegmentedProps } from "./segmented.js";
|
|
66
68
|
export { BranchScopePicker } from "./branch-scope-picker.js";
|
|
67
69
|
export type { BranchScopeModeProp, BranchScopeOptionProp, BranchScopePickerProp, BranchScopePickerProps, BranchScopeValueProp, } from "./branch-scope-picker.js";
|
|
@@ -51,6 +51,7 @@ import { PasswordStrength, usePasswordStrength } from "./password-strength.js";
|
|
|
51
51
|
import { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator } from "./input-otp.js";
|
|
52
52
|
import { Rating } from "./rating.js";
|
|
53
53
|
import { TagInput } from "./tag-input.js";
|
|
54
|
+
import { Segmented } from "./segmented.js";
|
|
54
55
|
import { BranchScopePicker } from "./branch-scope-picker.js";
|
|
55
56
|
export {
|
|
56
57
|
BranchScopePicker,
|
|
@@ -93,6 +94,7 @@ export {
|
|
|
93
94
|
SHOW_CHILD,
|
|
94
95
|
SHOW_PARENT,
|
|
95
96
|
SearchInput,
|
|
97
|
+
Segmented,
|
|
96
98
|
Select,
|
|
97
99
|
SelectContent,
|
|
98
100
|
SelectGroup,
|
|
@@ -25,6 +25,8 @@ export declare const SelectTrigger: React.ForwardRefExoticComponent<Omit<SelectP
|
|
|
25
25
|
* `full` (default) fills the field column — right inside a FormField. `auto` sizes to the
|
|
26
26
|
* selected label — right in a PageContainer `extra` slot, a toolbar or a footer row, where a
|
|
27
27
|
* full-width trigger swallows the row and starves its siblings (text truncated to "UA…").
|
|
28
|
+
* `bounded` holds one width from `--control-bounded-width`, for a trigger whose VALUE varies
|
|
29
|
+
* in length and must not move its neighbours when it changes (gh#375).
|
|
28
30
|
*/
|
|
29
31
|
width?: ControlWidthProp;
|
|
30
32
|
/**
|
|
@@ -98,7 +98,12 @@ const SelectTrigger = React.forwardRef(({ className, children, size = "md", widt
|
|
|
98
98
|
"data-width": width,
|
|
99
99
|
className: cn(
|
|
100
100
|
controlTriggerClass,
|
|
101
|
-
|
|
101
|
+
// `bounded` deliberately emits NO width utility: its width is owned by the
|
|
102
|
+
// `[data-width="bounded"]` rule in control.css. A utility here would win the layer order
|
|
103
|
+
// and make that rule — and therefore the token — dead, the way `w-full` did to
|
|
104
|
+
// `.ui-app-setting-picker-icon` (gh#366, gh#371).
|
|
105
|
+
width === "auto" && "w-auto",
|
|
106
|
+
width === "full" && "w-full",
|
|
102
107
|
"aria-invalid:border-destructive data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground whitespace-nowrap transition-[color,box-shadow] outline-none *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center",
|
|
103
108
|
className
|
|
104
109
|
),
|
|
@@ -143,11 +148,11 @@ const SelectScrollDownButton = React.forwardRef(({ className, ...props }, ref) =
|
|
|
143
148
|
));
|
|
144
149
|
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
|
|
145
150
|
const SelectContent = React.forwardRef(({ className, children, position = "popper", ...props }, ref) => {
|
|
146
|
-
useInertHiddenBackground();
|
|
151
|
+
const contentRef = useInertHiddenBackground(ref);
|
|
147
152
|
return /* @__PURE__ */ jsx(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs(
|
|
148
153
|
SelectPrimitive.Content,
|
|
149
154
|
{
|
|
150
|
-
ref,
|
|
155
|
+
ref: contentRef,
|
|
151
156
|
"data-slot": "select-content",
|
|
152
157
|
className: cn(
|
|
153
158
|
"ui-select-content data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
@@ -127,7 +127,7 @@ const SheetContent = React.forwardRef(
|
|
|
127
127
|
DialogPrimitive.Close,
|
|
128
128
|
{
|
|
129
129
|
"data-slot": "sheet-close",
|
|
130
|
-
className: "ui-sheet-close
|
|
130
|
+
className: "ui-sheet-close ui-focus-ring disabled:pointer-events-none",
|
|
131
131
|
children: [
|
|
132
132
|
/* @__PURE__ */ jsx(X, { className: "ui-sheet-close-icon", "aria-hidden": "true" }),
|
|
133
133
|
/* @__PURE__ */ jsx("span", { className: "sr-only", children: t("feedback.alert.dismiss") })
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { type ToasterProps } from "sonner";
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* `richColors` defaults ON: it is the switch that makes sonner apply the per-type properties at
|
|
5
|
+
* all (`[data-rich-colors='true'][data-type='success'] { … }`), so without it the tokens above
|
|
6
|
+
* are declared and never read. A consumer can still pass `richColors={false}` for a deliberately
|
|
7
|
+
* monochrome stack.
|
|
8
|
+
*/
|
|
9
|
+
declare function Toaster({ style, richColors, ...props }: ToasterProps): React.JSX.Element;
|
|
4
10
|
export { Toaster };
|
|
@@ -10,22 +10,42 @@ function useDocumentTheme() {
|
|
|
10
10
|
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
11
11
|
mq.addEventListener("change", onStoreChange);
|
|
12
12
|
const obs = new MutationObserver(onStoreChange);
|
|
13
|
-
obs.observe(document.documentElement, {
|
|
13
|
+
obs.observe(document.documentElement, {
|
|
14
|
+
attributes: true,
|
|
15
|
+
attributeFilter: ["class", "data-theme"]
|
|
16
|
+
});
|
|
14
17
|
return () => {
|
|
15
18
|
mq.removeEventListener("change", onStoreChange);
|
|
16
19
|
obs.disconnect();
|
|
17
20
|
};
|
|
18
21
|
},
|
|
19
|
-
() => document.documentElement.classList.contains("dark") ? "dark" : "light",
|
|
22
|
+
() => document.documentElement.dataset.theme === "dark" || document.documentElement.classList.contains("dark") ? "dark" : "light",
|
|
20
23
|
() => "light"
|
|
21
24
|
);
|
|
22
25
|
}
|
|
23
|
-
|
|
26
|
+
const TOAST_TONES = [
|
|
27
|
+
{ type: "success", hue: "success", ink: "text-success" },
|
|
28
|
+
{ type: "error", hue: "destructive", ink: "text-error" },
|
|
29
|
+
{ type: "warning", hue: "warning", ink: "text-warning" },
|
|
30
|
+
{ type: "info", hue: "info", ink: "text-info" }
|
|
31
|
+
];
|
|
32
|
+
const toneVars = Object.fromEntries(
|
|
33
|
+
TOAST_TONES.flatMap(({ type, hue, ink }) => [
|
|
34
|
+
[
|
|
35
|
+
`--${type}-bg`,
|
|
36
|
+
`color-mix(in srgb, hsl(var(--${hue})) calc(var(--alert-bg-alpha) * 100%), hsl(var(--popover)))`
|
|
37
|
+
],
|
|
38
|
+
[`--${type}-border`, `hsl(var(--${hue}) / var(--alert-border-alpha))`],
|
|
39
|
+
[`--${type}-text`, `hsl(var(--${ink}))`]
|
|
40
|
+
])
|
|
41
|
+
);
|
|
42
|
+
function Toaster({ style, richColors = true, ...props }) {
|
|
24
43
|
const theme = useDocumentTheme();
|
|
25
44
|
return /* @__PURE__ */ jsx(
|
|
26
45
|
Sonner,
|
|
27
46
|
{
|
|
28
47
|
theme,
|
|
48
|
+
richColors,
|
|
29
49
|
className: "toaster group",
|
|
30
50
|
icons: {
|
|
31
51
|
success: /* @__PURE__ */ jsx(CheckCircle2, { className: "ui-toast-icon", "aria-hidden": "true" }),
|
|
@@ -43,10 +63,10 @@ function Toaster({ style, ...props }) {
|
|
|
43
63
|
"--normal-text": "hsl(var(--popover-foreground))",
|
|
44
64
|
"--normal-border": "hsl(var(--border))",
|
|
45
65
|
"--border-radius": "var(--radius)",
|
|
46
|
-
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
// var
|
|
66
|
+
...toneVars,
|
|
67
|
+
// MERGED LAST, never spread over: a consumer's `style` is an override of these
|
|
68
|
+
// defaults, one custom property at a time. Assigning it instead of merging would
|
|
69
|
+
// wipe every var above, and an unset `--normal-bg` renders the toast transparent.
|
|
50
70
|
...style
|
|
51
71
|
},
|
|
52
72
|
position: "bottom-right",
|
|
@@ -9,7 +9,12 @@ import { useTranslation } from "../../i18n/use-translation.js";
|
|
|
9
9
|
const buttonVariants = cva("ui-button", {
|
|
10
10
|
variants: {
|
|
11
11
|
variant: {
|
|
12
|
-
|
|
12
|
+
// Colour is the TOKEN LAYER's (`.ui-button--default` in styles/control.css), not a utility's.
|
|
13
|
+
// The three utilities that used to sit here — `bg-primary text-primary-foreground
|
|
14
|
+
// hover:bg-primary/90` — restated the fill the components layer already declares and, being
|
|
15
|
+
// utilities, out-ranked it: the hover step this library generates from antd's algorithm
|
|
16
|
+
// (`--primary-hover`) could never take effect while `hover:bg-primary/90` was emitted here.
|
|
17
|
+
default: "ui-button--default",
|
|
13
18
|
destructive: "ui-button--destructive bg-destructive text-destructive-foreground",
|
|
14
19
|
outline: "ui-button--outline border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
|
|
15
20
|
dashed: "ui-button--dashed border border-dashed bg-background hover:bg-accent hover:text-accent-foreground",
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import * as React from "react";
|
|
1
2
|
/**
|
|
2
|
-
* Call from an overlay's content wrapper
|
|
3
|
-
*
|
|
3
|
+
* Call from an overlay's content wrapper, and spread the returned ref onto the Radix content
|
|
4
|
+
* element itself — that element is what carries `data-state`, and registering it is what lets the
|
|
5
|
+
* background be released on close intent rather than on unmount (gh#385).
|
|
6
|
+
*
|
|
7
|
+
* The caller's own forwarded ref is composed in, so a content component still hands its node to
|
|
8
|
+
* the consumer. Idempotent and refcounted: one observer serves every overlay on the page.
|
|
4
9
|
*/
|
|
5
|
-
export declare function useInertHiddenBackground(): void;
|
|
10
|
+
export declare function useInertHiddenBackground<T extends HTMLElement>(forwarded?: React.Ref<T>): (node: T | null) => void;
|
|
@@ -2,9 +2,20 @@
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
const TABBABLE = 'a[href], area[href], button, input, select, textarea, iframe, object, embed, details, audio[controls], video[controls], [contenteditable="true"], [tabindex]:not([tabindex^="-"])';
|
|
4
4
|
const owned = /* @__PURE__ */ new Set();
|
|
5
|
+
const anchors = /* @__PURE__ */ new Set();
|
|
5
6
|
let observer = null;
|
|
6
7
|
let mounted = 0;
|
|
8
|
+
function anyOverlayOpen() {
|
|
9
|
+
for (const anchor of anchors) {
|
|
10
|
+
if (anchor.isConnected && anchor.getAttribute("data-state") !== "closed") return true;
|
|
11
|
+
}
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
7
14
|
function sync() {
|
|
15
|
+
if (!anyOverlayOpen()) {
|
|
16
|
+
release();
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
8
19
|
for (const el of document.querySelectorAll("[data-aria-hidden]")) {
|
|
9
20
|
if (owned.has(el) || el.hasAttribute("inert")) continue;
|
|
10
21
|
if (el.matches("[data-radix-focus-guard]")) continue;
|
|
@@ -23,7 +34,7 @@ function release() {
|
|
|
23
34
|
for (const el of owned) el.removeAttribute("inert");
|
|
24
35
|
owned.clear();
|
|
25
36
|
}
|
|
26
|
-
function useInertHiddenBackground() {
|
|
37
|
+
function useInertHiddenBackground(forwarded) {
|
|
27
38
|
React.useEffect(() => {
|
|
28
39
|
mounted += 1;
|
|
29
40
|
if (!observer) {
|
|
@@ -31,7 +42,8 @@ function useInertHiddenBackground() {
|
|
|
31
42
|
observer.observe(document.body, {
|
|
32
43
|
subtree: true,
|
|
33
44
|
attributes: true,
|
|
34
|
-
|
|
45
|
+
// `data-state` is the close-intent signal; `data-aria-hidden` is the what-to-hide signal.
|
|
46
|
+
attributeFilter: ["data-aria-hidden", "data-state"]
|
|
35
47
|
});
|
|
36
48
|
}
|
|
37
49
|
sync();
|
|
@@ -43,6 +55,18 @@ function useInertHiddenBackground() {
|
|
|
43
55
|
release();
|
|
44
56
|
};
|
|
45
57
|
}, []);
|
|
58
|
+
const registered = React.useRef(null);
|
|
59
|
+
const forwardedRef = React.useRef(forwarded);
|
|
60
|
+
forwardedRef.current = forwarded;
|
|
61
|
+
return React.useCallback((node) => {
|
|
62
|
+
if (registered.current) anchors.delete(registered.current);
|
|
63
|
+
registered.current = node;
|
|
64
|
+
if (node) anchors.add(node);
|
|
65
|
+
sync();
|
|
66
|
+
const target = forwardedRef.current;
|
|
67
|
+
if (typeof target === "function") target(node);
|
|
68
|
+
else if (target) target.current = node;
|
|
69
|
+
}, []);
|
|
46
70
|
}
|
|
47
71
|
export {
|
|
48
72
|
useInertHiddenBackground
|
|
@@ -6,6 +6,15 @@ export interface LogoProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, "
|
|
|
6
6
|
/**
|
|
7
7
|
* The brand glyph — a short mark (a letter/initials, default `"g"`) or a custom node such as an
|
|
8
8
|
* inline `<svg>`. Keep it to 1–2 glyphs; the box is square and centres its content.
|
|
9
|
+
*
|
|
10
|
+
* A STRING is classified by the ink band it occupies (`g` x-height→descender, `GX` cap→baseline,
|
|
11
|
+
* `神` the full em box) and optically centred for that band — a line box is not a letterform, and
|
|
12
|
+
* the classes' centres are 0.21em apart. Any other node is left alone. Retune a band, or pin one
|
|
13
|
+
* mark, with the `--logo-glyph-*-optical-offset` tokens.
|
|
14
|
+
*
|
|
15
|
+
* A string is also FITTED to the box: two full-width forms want ~2em against a box sized for
|
|
16
|
+
* roughly one, so `東京` at `xs` is SET SMALLER rather than wrapping. The mark is always one
|
|
17
|
+
* line. Retune how much of the box a fitted glyph may take with `--logo-glyph-fit-max-width`.
|
|
9
18
|
*/
|
|
10
19
|
glyph?: React.ReactNode;
|
|
11
20
|
/**
|
|
@@ -37,6 +46,50 @@ export interface LogoProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, "
|
|
|
37
46
|
*/
|
|
38
47
|
label?: string;
|
|
39
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* The band the glyph's ink actually occupies, as the (top edge, bottom edge) of the UNION of every
|
|
51
|
+
* character in the string — `"GX"` is cap→baseline, `"gX"` is cap→descender.
|
|
52
|
+
*/
|
|
53
|
+
export type LogoGlyphInk = "cap-baseline" | "cap-descender" | "x-baseline" | "x-descender";
|
|
54
|
+
/**
|
|
55
|
+
* Classify a glyph for optical centring. Pure, no layout read, no measurement — the component is
|
|
56
|
+
* the only layer that can do this at all, because CSS cannot see which character is in the box.
|
|
57
|
+
* Returns `undefined` for a non-string glyph (an inline `<svg>`, any other node) and for an empty
|
|
58
|
+
* string: those are not classified and keep the neutral default rather than being guessed at.
|
|
59
|
+
*/
|
|
60
|
+
export declare function logoGlyphInk(glyph: React.ReactNode): LogoGlyphInk | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* How much INLINE space the glyph string wants, as counts of the four advance classes — the fact
|
|
63
|
+
* `.ui-logo-glyph`'s fit cap is computed from (gh#377). `fullwidth` is the characters that occupy
|
|
64
|
+
* a whole em; `wide` is the proportional forms that REACH that em (`M W m`); `space` is the runs
|
|
65
|
+
* of collapsible white space the paint keeps; `narrow` is everything else.
|
|
66
|
+
*
|
|
67
|
+
* The contract is that the FITTED font-size times the PAINTED advance stays inside the box, at
|
|
68
|
+
* every tier, on every bundled face. Over-stating an advance only sets the mark a little smaller
|
|
69
|
+
* than it had to be; under-stating it puts the mark outside its own box, which is the defect
|
|
70
|
+
* these classes exist to remove.
|
|
71
|
+
*/
|
|
72
|
+
export interface LogoGlyphAdvance {
|
|
73
|
+
fullwidth: number;
|
|
74
|
+
wide: number;
|
|
75
|
+
narrow: number;
|
|
76
|
+
space: number;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Count a glyph string's characters by advance class. Pure, no layout read, no measurement — the
|
|
80
|
+
* component is the only layer that can do this, because CSS cannot see which characters are in the
|
|
81
|
+
* box. Returns `undefined` for a non-string glyph and for an empty string, exactly like
|
|
82
|
+
* `logoGlyphInk`: those are not classified and keep the neutral default rather than being guessed
|
|
83
|
+
* at. Iterates code points, so a surrogate pair (CJK Extension B) counts once.
|
|
84
|
+
*
|
|
85
|
+
* WHITE SPACE IS COUNTED, NOT DROPPED. `.ui-logo-glyph` sets `white-space: nowrap`, which stops
|
|
86
|
+
* the mark wrapping but still PAINTS an interior space — measured 0.198em (system) to 0.333em
|
|
87
|
+
* (Hiragino Sans) — so a string whose space was dropped from the count was fitted to an advance
|
|
88
|
+
* narrower than the one that reached the screen (`東 京` spilled 2.63px at `md`). CSS's own
|
|
89
|
+
* processing is mirrored exactly: leading and trailing runs are trimmed away, and an interior run
|
|
90
|
+
* of any length collapses to ONE space (both verified in Chromium against the shipped `nowrap`).
|
|
91
|
+
*/
|
|
92
|
+
export declare function logoGlyphAdvance(glyph: React.ReactNode): LogoGlyphAdvance | undefined;
|
|
40
93
|
/**
|
|
41
94
|
* Logo — the product brand-mark box: a glyph on the primary fill, or (with `wordmark`) the full
|
|
42
95
|
* mark + wordmark LOCKUP. Size, radius, per-tier font-size, the wordmark's
|
|
@@ -1,8 +1,60 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { cn } from "../../lib/utils.js";
|
|
4
|
+
const X_HEIGHT_TOP = /^[acegmnopqrsuvwxyz]+$/;
|
|
5
|
+
const LATIN_DESCENDER = /[gjpqy]/;
|
|
6
|
+
function logoGlyphInk(glyph) {
|
|
7
|
+
if (typeof glyph !== "string") return void 0;
|
|
8
|
+
const text = glyph.replace(/\s+/gu, "");
|
|
9
|
+
if (!text) return void 0;
|
|
10
|
+
const top = X_HEIGHT_TOP.test(text) ? "x" : "cap";
|
|
11
|
+
const bottom = LATIN_DESCENDER.test(text) ? "descender" : "baseline";
|
|
12
|
+
return `${top}-${bottom}`;
|
|
13
|
+
}
|
|
14
|
+
const FULL_WIDTH_ADVANCE = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\u{3000}-\u{303F}\u{FF01}-\u{FF60}\u{FFE0}-\u{FFE6}]/u;
|
|
15
|
+
const EM_WIDE_ADVANCE = /[MWm]/;
|
|
16
|
+
const COLLAPSIBLE_SPACE = /[\t\n\f\r ]/;
|
|
17
|
+
function logoGlyphAdvance(glyph) {
|
|
18
|
+
if (typeof glyph !== "string") return void 0;
|
|
19
|
+
const text = glyph.replace(/^[\t\n\f\r ]+|[\t\n\f\r ]+$/g, "");
|
|
20
|
+
if (!text) return void 0;
|
|
21
|
+
const counts = { fullwidth: 0, wide: 0, narrow: 0, space: 0 };
|
|
22
|
+
let inSpaceRun = false;
|
|
23
|
+
for (const character of text) {
|
|
24
|
+
if (FULL_WIDTH_ADVANCE.test(character)) {
|
|
25
|
+
counts.fullwidth += 1;
|
|
26
|
+
} else if (COLLAPSIBLE_SPACE.test(character)) {
|
|
27
|
+
if (!inSpaceRun) counts.space += 1;
|
|
28
|
+
inSpaceRun = true;
|
|
29
|
+
continue;
|
|
30
|
+
} else if (EM_WIDE_ADVANCE.test(character)) {
|
|
31
|
+
counts.wide += 1;
|
|
32
|
+
} else {
|
|
33
|
+
counts.narrow += 1;
|
|
34
|
+
}
|
|
35
|
+
inSpaceRun = false;
|
|
36
|
+
}
|
|
37
|
+
return counts;
|
|
38
|
+
}
|
|
4
39
|
function MarkArtwork({ mark, glyph }) {
|
|
5
|
-
if (mark !== "godx")
|
|
40
|
+
if (mark !== "godx") {
|
|
41
|
+
const advance = logoGlyphAdvance(glyph);
|
|
42
|
+
return /* @__PURE__ */ jsx(
|
|
43
|
+
"span",
|
|
44
|
+
{
|
|
45
|
+
"data-slot": "logo-glyph",
|
|
46
|
+
"data-ink": logoGlyphInk(glyph),
|
|
47
|
+
className: "ui-logo-glyph",
|
|
48
|
+
style: advance && {
|
|
49
|
+
"--logo-glyph-fullwidth-count": advance.fullwidth,
|
|
50
|
+
"--logo-glyph-wide-count": advance.wide,
|
|
51
|
+
"--logo-glyph-narrow-count": advance.narrow,
|
|
52
|
+
"--logo-glyph-space-count": advance.space
|
|
53
|
+
},
|
|
54
|
+
children: glyph
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
}
|
|
6
58
|
return /* @__PURE__ */ jsx(
|
|
7
59
|
"svg",
|
|
8
60
|
{
|
|
@@ -72,5 +124,7 @@ const Logo = React.forwardRef(
|
|
|
72
124
|
);
|
|
73
125
|
Logo.displayName = "Logo";
|
|
74
126
|
export {
|
|
75
|
-
Logo
|
|
127
|
+
Logo,
|
|
128
|
+
logoGlyphAdvance,
|
|
129
|
+
logoGlyphInk
|
|
76
130
|
};
|
|
@@ -5,7 +5,9 @@ export type { TextProp, TextProp as TextProps, HeadingProp, HeadingProp as Headi
|
|
|
5
5
|
* font-medium text-muted-foreground">`.
|
|
6
6
|
*/
|
|
7
7
|
export declare const Text: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLElement>, "color"> & {
|
|
8
|
-
as?: "span" | "p" | "div" | "label" | "strong" | "em" | "small" | "code" | "kbd" | "dt" | "dd" | "caption" | "abbr";
|
|
8
|
+
as?: "span" | "p" | "div" | "a" | "label" | "strong" | "em" | "small" | "code" | "kbd" | "dt" | "dd" | "caption" | "abbr";
|
|
9
|
+
asChild?: import("../../props/index.js").AsChildProp;
|
|
10
|
+
link?: boolean;
|
|
9
11
|
size?: import("../../props/index.js").TextSizeProp;
|
|
10
12
|
tone?: import("../../props/index.js").TextToneProp;
|
|
11
13
|
weight?: import("../../props/index.js").FontWeightProp;
|
|
@@ -15,6 +17,10 @@ export declare const Text: React.ForwardRefExoticComponent<Omit<React.HTMLAttrib
|
|
|
15
17
|
tabular?: boolean;
|
|
16
18
|
mono?: boolean;
|
|
17
19
|
htmlFor?: string;
|
|
20
|
+
href?: string;
|
|
21
|
+
target?: React.HTMLAttributeAnchorTarget;
|
|
22
|
+
rel?: string;
|
|
23
|
+
download?: React.AnchorHTMLAttributes<HTMLAnchorElement>["download"];
|
|
18
24
|
} & React.RefAttributes<HTMLElement>>;
|
|
19
25
|
/**
|
|
20
26
|
* Heading — h1..h4 sized from the `--heading-h*` tokens. `level` sets both the size token and the
|