@godxjp/ui 19.4.1 → 19.5.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-display/list-row.d.ts +2 -0
- package/dist/components/data-display/list-row.js +16 -12
- package/dist/components/data-display/timeline-grid.js +5 -3
- package/dist/components/data-entry/checkbox.d.ts +12 -0
- package/dist/components/data-entry/checkbox.js +35 -4
- package/dist/components/data-entry/form-field.js +5 -5
- package/dist/components/data-entry/index.d.ts +2 -0
- package/dist/components/data-entry/index.js +2 -0
- package/dist/components/data-entry/input.js +1 -1
- package/dist/components/data-entry/radio.js +1 -1
- package/dist/components/data-entry/search-input.d.ts +2 -18
- package/dist/components/data-entry/search-input.js +3 -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 +9 -4
- 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.d.ts +2 -0
- package/dist/components/general/button.js +11 -2
- package/dist/components/general/inert-background.d.ts +8 -3
- package/dist/components/general/inert-background.js +28 -3
- package/dist/components/general/logo.d.ts +53 -0
- package/dist/components/general/logo.js +57 -3
- package/dist/components/layout/app-shell.js +1 -1
- package/dist/components/layout/index.d.ts +4 -0
- package/dist/components/layout/index.js +4 -0
- package/dist/components/layout/master-detail.d.ts +1 -1
- package/dist/components/layout/master-detail.js +47 -3
- package/dist/components/layout/nav-list.d.ts +26 -0
- package/dist/components/layout/nav-list.js +21 -0
- package/dist/components/layout/responsive-grid.js +7 -4
- 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 +31 -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 +4 -4
- package/dist/email/tokens.generated.js +4 -4
- 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/data-entry.prop.d.ts +10 -5
- package/dist/props/components/general.prop.d.ts +4 -0
- package/dist/props/components/layout.prop.d.ts +54 -2
- package/dist/props/registry.d.ts +21 -2
- package/dist/props/registry.js +29 -1
- 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 +123 -26
- package/dist/styles/data-display-layout.css +24 -2
- package/dist/styles/dialog-layout.css +2 -1
- package/dist/styles/focus-ring.css +84 -22
- package/dist/styles/form-layout.css +6 -0
- package/dist/styles/layout.css +38 -2
- package/dist/styles/logo-layout.css +56 -0
- package/dist/styles/navigation-layout.css +17 -1
- package/dist/styles/shell-layout.css +64 -10
- package/dist/tokens/antd.generated.css +40 -0
- package/dist/tokens/axes.css +6 -0
- package/dist/tokens/base.css +3 -0
- package/dist/tokens/components/control.css +5 -1
- package/dist/tokens/components/data-display.css +2 -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/foundation.css +21 -13
- package/docs/DESIGN-AUTHORITY.md +346 -0
- package/docs/FRAME-COVERAGE-REPORT.md +5 -2
- package/docs/README.md +14 -13
- package/docs/data-display/list-row.tsx +21 -0
- package/docs/data-entry/search-input.tsx +1 -0
- package/docs/data-entry/segmented.tsx +124 -0
- package/docs/general/button/index.md +4 -0
- package/docs/layout/master-detail.tsx +3 -0
- package/docs/layout/nav-list.tsx +63 -0
- package/docs/layout/responsive-grid.tsx +19 -2
- package/docs/layout/topbar-item.tsx +157 -0
- package/package.json +8 -2
|
@@ -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,12 +2,24 @@
|
|
|
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
|
-
if (el.
|
|
21
|
+
if (el.matches("[data-radix-focus-guard]")) continue;
|
|
22
|
+
if (el.matches(TABBABLE) || el.querySelector(TABBABLE)) {
|
|
11
23
|
el.setAttribute("inert", "");
|
|
12
24
|
owned.add(el);
|
|
13
25
|
}
|
|
@@ -22,7 +34,7 @@ function release() {
|
|
|
22
34
|
for (const el of owned) el.removeAttribute("inert");
|
|
23
35
|
owned.clear();
|
|
24
36
|
}
|
|
25
|
-
function useInertHiddenBackground() {
|
|
37
|
+
function useInertHiddenBackground(forwarded) {
|
|
26
38
|
React.useEffect(() => {
|
|
27
39
|
mounted += 1;
|
|
28
40
|
if (!observer) {
|
|
@@ -30,7 +42,8 @@ function useInertHiddenBackground() {
|
|
|
30
42
|
observer.observe(document.body, {
|
|
31
43
|
subtree: true,
|
|
32
44
|
attributes: true,
|
|
33
|
-
|
|
45
|
+
// `data-state` is the close-intent signal; `data-aria-hidden` is the what-to-hide signal.
|
|
46
|
+
attributeFilter: ["data-aria-hidden", "data-state"]
|
|
34
47
|
});
|
|
35
48
|
}
|
|
36
49
|
sync();
|
|
@@ -42,6 +55,18 @@ function useInertHiddenBackground() {
|
|
|
42
55
|
release();
|
|
43
56
|
};
|
|
44
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
|
+
}, []);
|
|
45
70
|
}
|
|
46
71
|
export {
|
|
47
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
|
};
|
|
@@ -51,7 +51,7 @@ function AppShell({
|
|
|
51
51
|
type: "button",
|
|
52
52
|
variant: "ghost",
|
|
53
53
|
size: "sm",
|
|
54
|
-
className: "app-mobile-nav-trigger
|
|
54
|
+
className: "app-mobile-nav-trigger",
|
|
55
55
|
"aria-label": t("layout.appShell.openNav"),
|
|
56
56
|
"aria-haspopup": "dialog",
|
|
57
57
|
children: /* @__PURE__ */ jsx(Menu, { className: "size-[var(--app-shell-mobile-nav-icon-size)]", "aria-hidden": "true" })
|
|
@@ -3,6 +3,8 @@ export type { PageContainerProp, PageContainerProps, BreadcrumbItem, BreadcrumbI
|
|
|
3
3
|
export { Flex } from "./flex.js";
|
|
4
4
|
export type { FlexAlignProp, FlexDirectionProp, FlexJustifyProp, FlexProp, FlexProps, } from "./flex.js";
|
|
5
5
|
export { ResizablePanel, ResizablePanelGroup, ResizableHandle } from "./resizable.js";
|
|
6
|
+
export { NavList } from "./nav-list.js";
|
|
7
|
+
export type { NavListProp, NavListProps } from "./nav-list.js";
|
|
6
8
|
export { AppShell } from "./app-shell.js";
|
|
7
9
|
export type { AppShellProps } from "./app-shell.js";
|
|
8
10
|
export { OrgSwitcher } from "./org-switcher.js";
|
|
@@ -37,6 +39,8 @@ export { createSidebarLink, Sidebar, SidebarHeader, SidebarItem, SidebarSection
|
|
|
37
39
|
export type { SidebarItemData, SidebarLinkComponentProp, SidebarLinkProp, SidebarProductProp as SidebarProduct, SidebarProp, SidebarProp as SidebarProps, SidebarRenderItemProp, SidebarSectionProp, } from "../../props/components/layout.prop.js";
|
|
38
40
|
export { Topbar } from "./topbar.js";
|
|
39
41
|
export type { TopbarProp, TopbarProps } from "./topbar.js";
|
|
42
|
+
export { TopbarItem } from "./topbar-item.js";
|
|
43
|
+
export type { TopbarItemProp, TopbarItemProps } from "./topbar-item.js";
|
|
40
44
|
export { ResponsiveGrid } from "./responsive-grid.js";
|
|
41
45
|
export type { ResponsiveGridProps } from "./responsive-grid.js";
|
|
42
46
|
export { MasterDetail } from "./master-detail.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { PageContainer } from "./page-container.js";
|
|
2
2
|
import { Flex } from "./flex.js";
|
|
3
3
|
import { ResizablePanel, ResizablePanelGroup, ResizableHandle } from "./resizable.js";
|
|
4
|
+
import { NavList } from "./nav-list.js";
|
|
4
5
|
import { AppShell } from "./app-shell.js";
|
|
5
6
|
import { OrgSwitcher } from "./org-switcher.js";
|
|
6
7
|
import { AuthShell } from "./auth-shell.js";
|
|
@@ -16,6 +17,7 @@ import { ErrorSurface } from "./error-surface.js";
|
|
|
16
17
|
import { Breadcrumb } from "./breadcrumb.js";
|
|
17
18
|
import { createSidebarLink, Sidebar, SidebarHeader, SidebarItem, SidebarSection } from "./sidebar.js";
|
|
18
19
|
import { Topbar } from "./topbar.js";
|
|
20
|
+
import { TopbarItem } from "./topbar-item.js";
|
|
19
21
|
import { ResponsiveGrid } from "./responsive-grid.js";
|
|
20
22
|
import { MasterDetail } from "./master-detail.js";
|
|
21
23
|
import { SplitPane } from "./split-pane.js";
|
|
@@ -40,6 +42,7 @@ export {
|
|
|
40
42
|
LegalDocumentShell,
|
|
41
43
|
MasterDetail,
|
|
42
44
|
MobileShell,
|
|
45
|
+
NavList,
|
|
43
46
|
OrgSwitcher,
|
|
44
47
|
PageContainer,
|
|
45
48
|
ResizableHandle,
|
|
@@ -54,5 +57,6 @@ export {
|
|
|
54
57
|
SidebarSection,
|
|
55
58
|
SplitPane,
|
|
56
59
|
Topbar,
|
|
60
|
+
TopbarItem,
|
|
57
61
|
createSidebarLink
|
|
58
62
|
};
|
|
@@ -5,4 +5,4 @@ export type MasterDetailProps = MasterDetailProp;
|
|
|
5
5
|
* drives. Either way `master` comes first in DOM order, so below the threshold the regions stack
|
|
6
6
|
* list-then-detail.
|
|
7
7
|
*/
|
|
8
|
-
export declare function MasterDetail({ master, children, rail, railWidth, masterViewport, collapseBelow, masterLabel, detailLabel, detailId, }: MasterDetailProps): import("react").JSX.Element;
|
|
8
|
+
export declare function MasterDetail({ master, mobilePane, detailBack, children, rail, railWidth, masterViewport, collapseBelow, masterLabel, detailLabel, detailId, }: MasterDetailProps): import("react").JSX.Element;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
2
4
|
function MasterDetail({
|
|
3
5
|
master,
|
|
6
|
+
mobilePane,
|
|
7
|
+
detailBack,
|
|
4
8
|
children,
|
|
5
9
|
rail = "detail",
|
|
6
10
|
railWidth = "standard",
|
|
@@ -10,11 +14,45 @@ function MasterDetail({
|
|
|
10
14
|
detailLabel,
|
|
11
15
|
detailId
|
|
12
16
|
}) {
|
|
17
|
+
const rootRef = useRef(null);
|
|
18
|
+
const thresholdRef = useRef(null);
|
|
19
|
+
const [compact, setCompact] = useState(false);
|
|
20
|
+
const navigable = mobilePane !== void 0;
|
|
21
|
+
useLayoutEffect(() => {
|
|
22
|
+
if (!navigable || !rootRef.current || !thresholdRef.current) return;
|
|
23
|
+
const root = rootRef.current;
|
|
24
|
+
const threshold = thresholdRef.current;
|
|
25
|
+
const measure = () => setCompact(root.getBoundingClientRect().width < threshold.getBoundingClientRect().width);
|
|
26
|
+
measure();
|
|
27
|
+
const observer = new ResizeObserver(measure);
|
|
28
|
+
observer.observe(root);
|
|
29
|
+
observer.observe(threshold);
|
|
30
|
+
return () => observer.disconnect();
|
|
31
|
+
}, [navigable, collapseBelow]);
|
|
32
|
+
const masterRef = useRef(null);
|
|
33
|
+
const detailRef = useRef(null);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (!mobilePane) return;
|
|
36
|
+
if (mobilePane === "detail" && masterRef.current?.getClientRects().length === 0) {
|
|
37
|
+
const heading = detailRef.current?.querySelector("h1, h2, h3");
|
|
38
|
+
if (heading) {
|
|
39
|
+
heading.tabIndex = -1;
|
|
40
|
+
heading.focus();
|
|
41
|
+
} else {
|
|
42
|
+
detailRef.current?.focus();
|
|
43
|
+
}
|
|
44
|
+
} else if (mobilePane === "master" && detailRef.current?.getClientRects().length === 0) {
|
|
45
|
+
masterRef.current?.querySelector("a[aria-current], a[href]")?.focus({ preventScroll: true });
|
|
46
|
+
}
|
|
47
|
+
}, [mobilePane, detailLabel, compact]);
|
|
13
48
|
const bounded = masterViewport !== "auto";
|
|
14
49
|
return /* @__PURE__ */ jsxs(
|
|
15
50
|
"div",
|
|
16
51
|
{
|
|
52
|
+
ref: rootRef,
|
|
17
53
|
className: "ui-master-detail",
|
|
54
|
+
"data-mobile-compact": navigable && compact ? "" : void 0,
|
|
55
|
+
"data-mobile-pane": mobilePane,
|
|
18
56
|
"data-rail": rail,
|
|
19
57
|
"data-rail-width": railWidth,
|
|
20
58
|
"data-master-viewport": masterViewport,
|
|
@@ -23,22 +61,28 @@ function MasterDetail({
|
|
|
23
61
|
/* @__PURE__ */ jsx(
|
|
24
62
|
"section",
|
|
25
63
|
{
|
|
64
|
+
ref: masterRef,
|
|
26
65
|
className: "ui-master-detail-master",
|
|
27
66
|
"aria-label": masterLabel,
|
|
28
67
|
tabIndex: bounded ? 0 : void 0,
|
|
29
68
|
children: master
|
|
30
69
|
}
|
|
31
70
|
),
|
|
32
|
-
/* @__PURE__ */
|
|
71
|
+
/* @__PURE__ */ jsxs(
|
|
33
72
|
"section",
|
|
34
73
|
{
|
|
74
|
+
ref: detailRef,
|
|
35
75
|
className: "ui-master-detail-detail",
|
|
36
76
|
id: detailId,
|
|
37
77
|
"aria-label": detailLabel,
|
|
38
78
|
tabIndex: -1,
|
|
39
|
-
children
|
|
79
|
+
children: [
|
|
80
|
+
mobilePane && detailBack ? /* @__PURE__ */ jsx("div", { className: "ui-master-detail-back", children: detailBack }) : null,
|
|
81
|
+
children
|
|
82
|
+
]
|
|
40
83
|
}
|
|
41
|
-
)
|
|
84
|
+
),
|
|
85
|
+
navigable ? /* @__PURE__ */ jsx("span", { className: "ui-master-detail-measure", "aria-hidden": "true", children: /* @__PURE__ */ jsx("span", { ref: thresholdRef }) }) : null
|
|
42
86
|
]
|
|
43
87
|
}
|
|
44
88
|
);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { NavListProp } from "../../props/components/layout.prop.js";
|
|
3
|
+
export type { NavListProp };
|
|
4
|
+
export type NavListProps = NavListProp;
|
|
5
|
+
/**
|
|
6
|
+
* NavList — a vertical route navigation that is NOT shell furniture (gh#374).
|
|
7
|
+
*
|
|
8
|
+
* `Sidebar` owns the app's primary rail and renders into `AppShell`'s grid, so it cannot be nested
|
|
9
|
+
* in a page. The settings-nav shape — a column of links beside the pane they drive, one of them
|
|
10
|
+
* current — had no primitive, which left every consumer hand-rolling rows out of `Button`s with the
|
|
11
|
+
* current one encoded as a variant swap: no `aria-current`, no icon column, a different invention
|
|
12
|
+
* per app.
|
|
13
|
+
*
|
|
14
|
+
* This is the SAME row as the rail, deliberately: it composes `SidebarItem`, so icon slot, label,
|
|
15
|
+
* badge, active tokens and `aria-current="page"` are one implementation shared by both, and a
|
|
16
|
+
* consumer that already learnt `SidebarItemProp` / `linkComponent` for the rail knows this too.
|
|
17
|
+
* Only the container differs — a `<nav>` landmark instead of the shell's grid area, and no
|
|
18
|
+
* collapsed rail (a page-level nav has no rail to collapse into).
|
|
19
|
+
*/
|
|
20
|
+
export declare const NavList: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLElement>, "onSelect"> & {
|
|
21
|
+
items: import("../../props/index.js").SidebarItemProp[];
|
|
22
|
+
activeId?: string;
|
|
23
|
+
label: string;
|
|
24
|
+
linkComponent?: import("./sidebar.js").SidebarLinkComponentProp;
|
|
25
|
+
onSelect?: (id: string) => void;
|
|
26
|
+
} & React.RefAttributes<HTMLElement>>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../../lib/utils.js";
|
|
5
|
+
import { SidebarItem } from "./sidebar.js";
|
|
6
|
+
const NavList = React.forwardRef(
|
|
7
|
+
({ items, activeId, label, linkComponent, onSelect, className, ...props }, ref) => /* @__PURE__ */ jsx("nav", { ref, "aria-label": label, className: cn("ui-nav-list", className), ...props, children: items.map((item) => /* @__PURE__ */ jsx(
|
|
8
|
+
SidebarItem,
|
|
9
|
+
{
|
|
10
|
+
item,
|
|
11
|
+
active: item.id === activeId,
|
|
12
|
+
linkComponent,
|
|
13
|
+
onActivate: onSelect
|
|
14
|
+
},
|
|
15
|
+
item.id
|
|
16
|
+
)) })
|
|
17
|
+
);
|
|
18
|
+
NavList.displayName = "NavList";
|
|
19
|
+
export {
|
|
20
|
+
NavList
|
|
21
|
+
};
|
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../../lib/utils.js";
|
|
3
3
|
const RESPONSIVE_GRID_PRESET_COLUMNS = {
|
|
4
|
-
"pricing-plans": { sm: 3, md: 3, lg: 3 }
|
|
4
|
+
"pricing-plans": { base: 1, sm: 3, md: 3, lg: 3 }
|
|
5
5
|
};
|
|
6
6
|
function resolveColumns(columns) {
|
|
7
7
|
if (typeof columns === "number") {
|
|
8
8
|
return {
|
|
9
|
+
base: 1,
|
|
9
10
|
sm: Math.min(columns, 2),
|
|
10
11
|
md: Math.min(columns, 3),
|
|
11
12
|
lg: columns
|
|
12
13
|
};
|
|
13
14
|
}
|
|
14
15
|
return {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
base: columns.base ?? 1,
|
|
17
|
+
sm: columns.sm ?? columns.base ?? 1,
|
|
18
|
+
md: columns.md ?? columns.sm ?? columns.base ?? 1,
|
|
19
|
+
lg: columns.lg ?? columns.md ?? columns.sm ?? columns.base ?? 1
|
|
18
20
|
};
|
|
19
21
|
}
|
|
20
22
|
function toStyle(resolved) {
|
|
21
23
|
return {
|
|
24
|
+
"--responsive-grid-base": resolved.base,
|
|
22
25
|
"--responsive-grid-sm": resolved.sm,
|
|
23
26
|
"--responsive-grid-md": resolved.md,
|
|
24
27
|
"--responsive-grid-lg": resolved.lg
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type { TopbarItemProp, TopbarItemProp as TopbarItemProps, } from "../../props/components/layout.prop.js";
|
|
3
|
+
/**
|
|
4
|
+
* TopbarItem — one interactive cell of a {@link Topbar} slot, shaped like part of the bar.
|
|
5
|
+
*
|
|
6
|
+
* Full bar height, the bar's own hover surface, and the focus mark hosted INSIDE the cell (a
|
|
7
|
+
* full-bleed cell has nothing outside itself to ring, and the bar clips its own overflow). All of
|
|
8
|
+
* that lives in `.ui-topbar-item`; this component only picks the element and the classes.
|
|
9
|
+
*
|
|
10
|
+
* `ui-focus-ring` is the ONE focus source (styles/focus-ring.css) — the cell hosts whatever that
|
|
11
|
+
* file resolves, including nothing at all while the `--focus-outline` switch ships off.
|
|
12
|
+
*/
|
|
13
|
+
export declare const TopbarItem: React.ForwardRefExoticComponent<Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "children"> & {
|
|
14
|
+
asChild?: boolean;
|
|
15
|
+
children?: React.ReactNode;
|
|
16
|
+
className?: import("../../props/index.js").ClassNameProp;
|
|
17
|
+
} & React.RefAttributes<HTMLButtonElement>>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
5
|
+
import { cn } from "../../lib/utils.js";
|
|
6
|
+
const TopbarItem = React.forwardRef(function TopbarItem2({ asChild = false, className, type, ...props }, ref) {
|
|
7
|
+
const Comp = asChild ? Slot : "button";
|
|
8
|
+
return /* @__PURE__ */ jsx(
|
|
9
|
+
Comp,
|
|
10
|
+
{
|
|
11
|
+
ref,
|
|
12
|
+
"data-slot": "topbar-item",
|
|
13
|
+
className: cn("ui-topbar-item ui-focus-ring", className),
|
|
14
|
+
type: asChild ? void 0 : type ?? "button",
|
|
15
|
+
...props
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
export {
|
|
20
|
+
TopbarItem
|
|
21
|
+
};
|
|
@@ -135,11 +135,37 @@ const AppSettingPicker = React.forwardRef(
|
|
|
135
135
|
// sits among ghost icon buttons (the topbar's sidebar-toggle/notifications/account
|
|
136
136
|
// triggers), so it drops controlTriggerClass's form-input chrome (border/bg/shadow)
|
|
137
137
|
// at rest and adopts the same ghost hover — a resting border here read as visually
|
|
138
|
-
// inconsistent next to its borderless topbar siblings. The open-state
|
|
139
|
-
//
|
|
140
|
-
// focus-
|
|
141
|
-
//
|
|
142
|
-
|
|
138
|
+
// inconsistent next to its borderless topbar siblings. The open-state and
|
|
139
|
+
// focus-visible rings are untouched, so keyboard and "is this open" affordance
|
|
140
|
+
// still hold: both come from styles/focus-ring.css, which also excludes this
|
|
141
|
+
// variant from the bordered-field rebind precisely BECAUSE it has no resting
|
|
142
|
+
// border to recolour, so it keeps the opaque ring form.
|
|
143
|
+
// Bề ngang phải là UTILITY, không phải luật class. SelectTrigger phát `w-full`,
|
|
144
|
+
// mà utility nằm sau components trong thứ tự layer nên `inline-size` khai trong
|
|
145
|
+
// .ui-app-setting-picker-icon luôn thua. Trong một khe co theo nội dung của
|
|
146
|
+
// topbar, `width: 100%` co lại bằng chính nội dung, tức 18px thay vì ô vuông
|
|
147
|
+
// --control-height. Nhánh có nhãn ngay dưới đã phải tự vệ đúng như vậy bằng
|
|
148
|
+
// `w-auto`. Giá trị vẫn đọc token chứ không phải một con số.
|
|
149
|
+
// `justify-content` đi cùng lý do đó: SelectTrigger phát `justify-between`,
|
|
150
|
+
// với một con duy nhất thì nó dồn glyph sát viền trái (khe 1px/15px trong ô
|
|
151
|
+
// 32px). `justify-content: center` khai trong .ui-app-setting-picker-icon
|
|
152
|
+
// cũng nằm ở @layer components nên cũng thua. Cả hai trục của cái hộp này
|
|
153
|
+
// phải do lớp utility sở hữu, không được để sót nửa nào lại trong luật class.
|
|
154
|
+
// `shrink-0` là NỬA CÒN LẠI của trục ngang, không phải trang trí. `w-*` chỉ khai
|
|
155
|
+
// kích thước MONG MUỐN; trong một flex row chật, `flex-shrink: 1` mặc định vẫn
|
|
156
|
+
// ép nó nhỏ hơn. Đo thật trong Chromium ở chính frame tài liệu của component:
|
|
157
|
+
// `.ui-topbar-end` rộng 82px, gap 8px, hai trigger cùng co → ô vuông ra
|
|
158
|
+
// 21,28 × 32px thay vì 32 × 32. Đây không phải gh#366 quay lại: `inline-size`
|
|
159
|
+
// vẫn giải đúng --control-height; cái hỏng là co giãn. Một tap target vuông
|
|
160
|
+
// biến thành hình chữ nhật 21px mỗi khi topbar chật là lỗi thật, và nó kéo
|
|
161
|
+
// theo cả vùng chạm. Nhánh CÓ NHÃN cố tình KHÔNG nhận `shrink-0`: nó rộng theo
|
|
162
|
+
// nội dung và mang sẵn `max-w-full` để được phép thu lại.
|
|
163
|
+
// Vẫn phải là utility vì cùng lý do với `w-*`: luật trong @layer components
|
|
164
|
+
// thua lớp utilities mà SelectTrigger phát ra.
|
|
165
|
+
cn(
|
|
166
|
+
"ui-app-setting-picker-icon hover:bg-accent hover:text-accent-foreground",
|
|
167
|
+
"w-[length:var(--control-height)] shrink-0 justify-center"
|
|
168
|
+
)
|
|
143
169
|
) : (
|
|
144
170
|
// Labeled: sized to a per-kind width from `sm` up; below `sm` it hugs its content and
|
|
145
171
|
// A form field that wants a full-width control passes
|
|
@@ -11,11 +11,11 @@ function ContextMenuTrigger(props) {
|
|
|
11
11
|
}
|
|
12
12
|
const ContextMenuPortal = ContextMenuPrimitive.Portal;
|
|
13
13
|
const ContextMenuContent = React.forwardRef(({ className, ...props }, ref) => {
|
|
14
|
-
useInertHiddenBackground();
|
|
14
|
+
const contentRef = useInertHiddenBackground(ref);
|
|
15
15
|
return /* @__PURE__ */ jsx(ContextMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx(
|
|
16
16
|
ContextMenuPrimitive.Content,
|
|
17
17
|
{
|
|
18
|
-
ref,
|
|
18
|
+
ref: contentRef,
|
|
19
19
|
"data-slot": "context-menu-content",
|
|
20
20
|
className: cn("ui-context-menu-content", className),
|
|
21
21
|
...props
|
|
@@ -24,11 +24,11 @@ function DropdownMenuSub(props) {
|
|
|
24
24
|
return /* @__PURE__ */ jsx(DropdownMenuPrimitive.Sub, { "data-slot": "dropdown-menu-sub", ...props });
|
|
25
25
|
}
|
|
26
26
|
const DropdownMenuContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => {
|
|
27
|
-
useInertHiddenBackground();
|
|
27
|
+
const contentRef = useInertHiddenBackground(ref);
|
|
28
28
|
return /* @__PURE__ */ jsx(DropdownMenuPortal, { children: /* @__PURE__ */ jsx(
|
|
29
29
|
DropdownMenuPrimitive.Content,
|
|
30
30
|
{
|
|
31
|
-
ref,
|
|
31
|
+
ref: contentRef,
|
|
32
32
|
"data-slot": "dropdown-menu-content",
|
|
33
33
|
sideOffset,
|
|
34
34
|
className: cn(
|