@godxjp/ui 20.1.0 → 20.2.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 +37 -21
- package/dist/app/index.d.ts +2 -0
- package/dist/app/index.js +2 -0
- package/dist/app/storage.d.ts +17 -0
- package/dist/app/storage.js +28 -0
- package/dist/components/data-display/popover.d.ts +7 -1
- package/dist/components/data-display/popover.js +4 -0
- package/dist/components/data-display/service-launcher-card.js +4 -2
- package/dist/components/data-display/table.d.ts +60 -0
- package/dist/components/data-display/table.js +8 -0
- package/dist/components/data-entry/calendar.d.ts +1 -1
- package/dist/components/data-entry/calendar.js +11 -2
- package/dist/components/feedback/dialog.js +3 -0
- package/dist/components/feedback/sheet.js +3 -0
- package/dist/components/feedback/tooltip.js +3 -0
- package/dist/components/general/button.d.ts +1 -0
- package/dist/components/general/button.js +2 -0
- package/dist/components/layout/app-launcher.d.ts +1 -1
- package/dist/components/layout/app-launcher.js +43 -10
- package/dist/components/layout/app-shell.d.ts +1 -1
- package/dist/components/layout/app-shell.js +14 -7
- package/dist/components/layout/auth-shell.d.ts +1 -1
- package/dist/components/layout/auth-shell.js +2 -0
- package/dist/components/layout/sidebar.js +2 -1
- package/dist/components/navigation/dropdown-menu.js +5 -0
- package/dist/components/ui/hover-card.js +3 -0
- package/dist/i18n/index.d.ts +1 -1
- package/dist/i18n/index.js +3 -1
- package/dist/i18n/messages/en.json +3 -1
- package/dist/i18n/messages/ja.json +3 -1
- package/dist/i18n/messages/vi.json +3 -1
- package/dist/i18n/translate.d.ts +19 -0
- package/dist/i18n/translate.js +24 -0
- package/dist/lib/overlay-portal.d.ts +18 -0
- package/dist/lib/overlay-portal.js +17 -0
- package/dist/props/components/app.prop.d.ts +13 -2
- package/dist/props/components/general.prop.d.ts +13 -0
- package/dist/props/components/layout.prop.d.ts +95 -5
- package/dist/styles/card-layout.css +10 -1
- package/dist/styles/chart-layout.css +2 -1
- package/dist/styles/control.css +5 -0
- package/dist/styles/data-display-layout.css +7 -1
- package/dist/styles/navigation-layout.css +3 -1
- package/dist/styles/shell-layout.css +465 -20
- package/dist/tokens/components/control.css +2 -1
- package/dist/tokens/components/shell.css +50 -3
- package/dist/tokens/foundation.css +2 -0
- package/docs/data-display/list-row.tsx +6 -6
- package/docs/layout/app-launcher.tsx +44 -0
- package/docs/layout/app-shell-arrangements.tsx +5 -5
- package/docs/showcase/settings-security-mfa.tsx +3 -3
- package/package.json +7 -4
- package/scripts/_agent-setup.mjs +57 -7
- package/scripts/visual-audit.mjs +45 -7
|
@@ -4,9 +4,9 @@ import * as React from "react";
|
|
|
4
4
|
import { Menu } from "lucide-react";
|
|
5
5
|
import { useMediaQuery } from "../../lib/hooks.js";
|
|
6
6
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
7
|
-
import { Button } from "../general/button.js";
|
|
8
7
|
import { Sheet, SheetBody, SheetContent, SheetHeader, SheetTrigger } from "../feedback/sheet.js";
|
|
9
8
|
import { NavSurfaceProvider } from "./nav-surface.js";
|
|
9
|
+
import { TopbarItem } from "./topbar-item.js";
|
|
10
10
|
function useAppShellNavigationMode() {
|
|
11
11
|
return useMediaQuery("(width <= 56.25rem)") ? "drawer" : "docked";
|
|
12
12
|
}
|
|
@@ -23,6 +23,8 @@ function AppShell({
|
|
|
23
23
|
responsiveNavigation = "drawer",
|
|
24
24
|
topbarSpan = "content",
|
|
25
25
|
navRail,
|
|
26
|
+
navRailPosition = "start",
|
|
27
|
+
navRailEnd,
|
|
26
28
|
navRailLabel,
|
|
27
29
|
mobileNav,
|
|
28
30
|
mobileNavLabel,
|
|
@@ -32,8 +34,12 @@ function AppShell({
|
|
|
32
34
|
const { t } = useTranslation();
|
|
33
35
|
const navigationMode = useAppShellNavigationMode();
|
|
34
36
|
const hasSidebar = sidebar !== void 0 && sidebar !== null && sidebar !== false;
|
|
35
|
-
const
|
|
37
|
+
const railContent = navRail === void 0 ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
36
38
|
navRail,
|
|
39
|
+
navRailEnd !== void 0 ? /* @__PURE__ */ jsx("div", { className: "app-nav-rail-end", children: navRailEnd }) : null
|
|
40
|
+
] });
|
|
41
|
+
const drawerNav = mobileNav !== void 0 ? mobileNav : navRail !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
42
|
+
railContent,
|
|
37
43
|
sidebar
|
|
38
44
|
] }) : sidebar;
|
|
39
45
|
const hasDrawer = responsiveNavigation === "drawer" && drawerNav != null && drawerNav !== false;
|
|
@@ -82,18 +88,18 @@ function AppShell({
|
|
|
82
88
|
"aside",
|
|
83
89
|
{
|
|
84
90
|
className: "app-nav-rail",
|
|
91
|
+
"data-orientation": navRailPosition === "top" || navRailPosition === "bottom" ? "horizontal" : "vertical",
|
|
92
|
+
"data-edge": navRailPosition,
|
|
85
93
|
"aria-label": navRailLabel ?? t("layout.appShell.navRailLabel"),
|
|
86
|
-
children:
|
|
94
|
+
children: railContent
|
|
87
95
|
}
|
|
88
96
|
);
|
|
89
97
|
const bar = !hasTopbarContent && !hasDrawer ? null : /* @__PURE__ */ jsxs("header", { className: "app-topbar ui-scale-fixed", "aria-label": t("layout.appShell.headerLabel"), children: [
|
|
90
98
|
hasDrawer && /* @__PURE__ */ jsxs(Sheet, { open: drawerOpen, onOpenChange: setDrawerOpen, children: [
|
|
91
99
|
/* @__PURE__ */ jsx(SheetTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
92
|
-
|
|
100
|
+
TopbarItem,
|
|
93
101
|
{
|
|
94
102
|
type: "button",
|
|
95
|
-
variant: "ghost",
|
|
96
|
-
size: "sm",
|
|
97
103
|
className: "app-mobile-nav-trigger",
|
|
98
104
|
"aria-label": t("layout.appShell.openNav"),
|
|
99
105
|
"aria-haspopup": "dialog",
|
|
@@ -115,7 +121,7 @@ function AppShell({
|
|
|
115
121
|
className: "app-mobile-nav-body px-[var(--app-shell-mobile-nav-inset)]",
|
|
116
122
|
onClick: handleDrawerClick,
|
|
117
123
|
children: railInDrawer ? /* @__PURE__ */ jsxs("div", { className: "app-mobile-nav-columns", children: [
|
|
118
|
-
/* @__PURE__ */ jsx("div", { className: "app-mobile-nav-rail", children:
|
|
124
|
+
/* @__PURE__ */ jsx("div", { className: "app-mobile-nav-rail", children: railContent }),
|
|
119
125
|
/* @__PURE__ */ jsx(NavSurfaceProvider, { surface: "drawer", children: /* @__PURE__ */ jsx("div", { className: "app-mobile-nav-sections", children: sidebar }) })
|
|
120
126
|
] }) : /* @__PURE__ */ jsx(NavSurfaceProvider, { surface: "drawer", children: drawerNav })
|
|
121
127
|
}
|
|
@@ -137,6 +143,7 @@ function AppShell({
|
|
|
137
143
|
"data-topbar": hasTopbarContent ? void 0 : "none",
|
|
138
144
|
"data-topbar-span": topbarSpan === "full" ? "full" : void 0,
|
|
139
145
|
"data-nav-rail": navRail !== void 0 ? "" : void 0,
|
|
146
|
+
"data-nav-rail-position": navRail !== void 0 ? navRailPosition : void 0,
|
|
140
147
|
children: [
|
|
141
148
|
topbarSpan === "full" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
142
149
|
bar,
|
|
@@ -7,4 +7,4 @@ export type { AuthShellProp, AuthShellProp as AuthShellProps, } from "../../prop
|
|
|
7
7
|
* locale picker, a theme toggle), and `measure="wide"` opens the content slot to the split login
|
|
8
8
|
* (a brand panel beside the card) that used to have no shell of its own.
|
|
9
9
|
*/
|
|
10
|
-
export declare function AuthShell({ brand, actions, footer, children, variant, preset, measure, density, className, }: AuthShellProp): import("react").JSX.Element;
|
|
10
|
+
export declare function AuthShell({ brand, actions, footer, children, variant, preset, measure, align, density, className, }: AuthShellProp): import("react").JSX.Element;
|
|
@@ -10,6 +10,7 @@ function AuthShell({
|
|
|
10
10
|
variant = "default",
|
|
11
11
|
preset = "default",
|
|
12
12
|
measure = "default",
|
|
13
|
+
align,
|
|
13
14
|
density,
|
|
14
15
|
className
|
|
15
16
|
}) {
|
|
@@ -22,6 +23,7 @@ function AuthShell({
|
|
|
22
23
|
"data-variant": variant,
|
|
23
24
|
"data-preset": preset === "default" ? void 0 : preset,
|
|
24
25
|
"data-measure": measure === "default" ? void 0 : measure,
|
|
26
|
+
"data-align": align,
|
|
25
27
|
"data-density": resolvedDensity,
|
|
26
28
|
className: cn("ui-auth-shell", className),
|
|
27
29
|
children: [
|
|
@@ -304,8 +304,9 @@ function Sidebar({
|
|
|
304
304
|
const resolvedSections = sections ?? [];
|
|
305
305
|
const surface = useNavSurface();
|
|
306
306
|
collapsed = surface === "drawer" ? false : collapsed;
|
|
307
|
+
const brandNode = typeof brand === "function" ? brand(collapsed) : brand;
|
|
307
308
|
return /* @__PURE__ */ jsxs("div", { className: "sb-root", "data-collapsed": collapsed ? "true" : void 0, children: [
|
|
308
|
-
brand !== void 0 ? /* @__PURE__ */ jsx(SidebarHeader, { children:
|
|
309
|
+
brand !== void 0 ? /* @__PURE__ */ jsx(SidebarHeader, { children: brandNode }) : product ? (() => {
|
|
309
310
|
const interactive = onProductClick != null;
|
|
310
311
|
const mark = /* @__PURE__ */ jsx(
|
|
311
312
|
"span",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useOverlayPortalContainer } from "../../lib/overlay-portal.js";
|
|
3
4
|
import * as React from "react";
|
|
4
5
|
import {
|
|
5
6
|
Header,
|
|
@@ -145,9 +146,11 @@ function DropdownMenuContent({
|
|
|
145
146
|
void forceMount;
|
|
146
147
|
const { modal } = React.useContext(DropdownMenuModalContext);
|
|
147
148
|
const anchor = placement ? DROPDOWN_MENU_PLACEMENT[placement] : void 0;
|
|
149
|
+
const overlayPortalContainer = useOverlayPortalContainer();
|
|
148
150
|
return /* @__PURE__ */ jsx(DropdownMenuPortal, { children: /* @__PURE__ */ jsxs(
|
|
149
151
|
Popover,
|
|
150
152
|
{
|
|
153
|
+
UNSTABLE_portalContainer: overlayPortalContainer,
|
|
151
154
|
"data-slot": "dropdown-menu-content",
|
|
152
155
|
isNonModal: !modal,
|
|
153
156
|
placement: toPlacement(side ?? anchor?.side, align ?? anchor?.align),
|
|
@@ -384,9 +387,11 @@ function DropdownMenuSubContent({
|
|
|
384
387
|
void forceMount;
|
|
385
388
|
void hideWhenDetached;
|
|
386
389
|
void sticky;
|
|
390
|
+
const overlayPortalContainer = useOverlayPortalContainer();
|
|
387
391
|
return /* @__PURE__ */ jsx(
|
|
388
392
|
Popover,
|
|
389
393
|
{
|
|
394
|
+
UNSTABLE_portalContainer: overlayPortalContainer,
|
|
390
395
|
"data-slot": "dropdown-menu-sub-content",
|
|
391
396
|
placement: align ? toPlacement("right", align) : void 0,
|
|
392
397
|
offset: sideOffset,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useOverlayPortalContainer } from "../../lib/overlay-portal.js";
|
|
3
4
|
import * as React from "react";
|
|
4
5
|
import { chain, mergeRefs } from "@react-aria/utils";
|
|
5
6
|
import { Popover as AriaPopover } from "react-aria-components";
|
|
@@ -142,9 +143,11 @@ function HoverCardContent({
|
|
|
142
143
|
...props
|
|
143
144
|
}) {
|
|
144
145
|
const card = useHoverCard("HoverCardContent");
|
|
146
|
+
const overlayPortalContainer = useOverlayPortalContainer();
|
|
145
147
|
return /* @__PURE__ */ jsx(
|
|
146
148
|
AriaPopover,
|
|
147
149
|
{
|
|
150
|
+
UNSTABLE_portalContainer: overlayPortalContainer,
|
|
148
151
|
isOpen: card.open,
|
|
149
152
|
onOpenChange: card.setOpenImmediately,
|
|
150
153
|
isNonModal: true,
|
package/dist/i18n/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { translate, translateCurrent, syncI18nLocale, resetI18nLocale, MESSAGE_CATALOG, } from "./translate.js";
|
|
1
|
+
export { translate, translateCurrent, syncI18nLocale, resetI18nLocale, MESSAGE_CATALOG, registerMessages, } from "./translate.js";
|
|
2
2
|
export type { Messages, MessageKey, TranslateParams } from "./translate.js";
|
|
3
3
|
export { useTranslation, usePickerLocales } from "./use-translation.js";
|
package/dist/i18n/index.js
CHANGED
|
@@ -3,11 +3,13 @@ import {
|
|
|
3
3
|
translateCurrent,
|
|
4
4
|
syncI18nLocale,
|
|
5
5
|
resetI18nLocale,
|
|
6
|
-
MESSAGE_CATALOG
|
|
6
|
+
MESSAGE_CATALOG,
|
|
7
|
+
registerMessages
|
|
7
8
|
} from "./translate.js";
|
|
8
9
|
import { useTranslation, usePickerLocales } from "./use-translation.js";
|
|
9
10
|
export {
|
|
10
11
|
MESSAGE_CATALOG,
|
|
12
|
+
registerMessages,
|
|
11
13
|
resetI18nLocale,
|
|
12
14
|
syncI18nLocale,
|
|
13
15
|
translate,
|
package/dist/i18n/translate.d.ts
CHANGED
|
@@ -3,6 +3,25 @@ import vi from "./messages/vi.json" with { type: "json" };
|
|
|
3
3
|
export type Messages = typeof vi;
|
|
4
4
|
export declare const MESSAGE_CATALOG: Record<AppLocale, Record<string, unknown>>;
|
|
5
5
|
export type MessageKey = string;
|
|
6
|
+
/**
|
|
7
|
+
* ADD AN APPLICATION'S OWN STRINGS TO THE LIBRARY'S CATALOG.
|
|
8
|
+
*
|
|
9
|
+
* The catalog used to be closed: `MESSAGE_CATALOG` was exported but there was no way to extend it,
|
|
10
|
+
* so an application had to run a SECOND translation system beside this one — two lookups, two
|
|
11
|
+
* fallback chains, and two places a missing key can hide. That is a real cost for a product whose
|
|
12
|
+
* pages mix library chrome and application copy in the same sentence.
|
|
13
|
+
*
|
|
14
|
+
* MERGES, never replaces: a partial tree is layered onto what is already there, so registering
|
|
15
|
+
* `{ myApp: { title: "…" } }` cannot delete `dataEntry.calendar.today`. Registering the same key
|
|
16
|
+
* twice is last-write-wins, which is what a hot reload needs and what a second call with corrected
|
|
17
|
+
* copy expects.
|
|
18
|
+
*
|
|
19
|
+
* A LIBRARY KEY CANNOT BE SILENTLY REPLACED. Overwriting `dataEntry.*` or any other namespace this
|
|
20
|
+
* package ships would let one application change what a shared component says — invisibly, from a
|
|
21
|
+
* distance, and only in the build where that call ran. Reach for the component's own labels prop
|
|
22
|
+
* instead; every string this library renders has one.
|
|
23
|
+
*/
|
|
24
|
+
export declare function registerMessages(locale: AppLocale, messages: Record<string, unknown>): void;
|
|
6
25
|
export type TranslateParams = Record<string, string | number>;
|
|
7
26
|
/** Resolve a dot-path message with fallback locale chain, CLDR plurals, and locale number formatting. */
|
|
8
27
|
export declare function translate(locale: AppLocale, fallbackLocale: AppLocale, key: MessageKey, params?: TranslateParams): string;
|
package/dist/i18n/translate.js
CHANGED
|
@@ -6,6 +6,29 @@ const MESSAGE_CATALOG = {
|
|
|
6
6
|
en,
|
|
7
7
|
ja
|
|
8
8
|
};
|
|
9
|
+
const LIBRARY_NAMESPACES = new Set(
|
|
10
|
+
Object.values(MESSAGE_CATALOG).flatMap((messages) => Object.keys(messages))
|
|
11
|
+
);
|
|
12
|
+
function registerMessages(locale, messages) {
|
|
13
|
+
const reserved = [...LIBRARY_NAMESPACES].filter((key) => Object.hasOwn(messages, key));
|
|
14
|
+
if (reserved.length > 0) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`@godxjp/ui i18n: [${reserved.join(", ")}] ${reserved.length === 1 ? "is a" : "are"} reserved top-level namespace(s) owned by the library. Register your strings under a namespace of your own, and change what a component says through its \`labels\` prop.`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
MESSAGE_CATALOG[locale] = mergeDeep(MESSAGE_CATALOG[locale] ?? {}, messages);
|
|
20
|
+
}
|
|
21
|
+
function mergeDeep(target, source) {
|
|
22
|
+
const merged = { ...target };
|
|
23
|
+
for (const [key, value] of Object.entries(source)) {
|
|
24
|
+
const existing = merged[key];
|
|
25
|
+
merged[key] = isPlainObject(existing) && isPlainObject(value) ? mergeDeep(existing, value) : value;
|
|
26
|
+
}
|
|
27
|
+
return merged;
|
|
28
|
+
}
|
|
29
|
+
function isPlainObject(value) {
|
|
30
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
31
|
+
}
|
|
9
32
|
function getNested(obj, path) {
|
|
10
33
|
const parts = path.split(".");
|
|
11
34
|
let current = obj;
|
|
@@ -75,6 +98,7 @@ export {
|
|
|
75
98
|
MESSAGE_CATALOG,
|
|
76
99
|
getSyncedFallbackLocale,
|
|
77
100
|
getSyncedLocale,
|
|
101
|
+
registerMessages,
|
|
78
102
|
resetI18nLocale,
|
|
79
103
|
syncI18nLocale,
|
|
80
104
|
translate,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface OverlayPortalProviderProps {
|
|
3
|
+
/**
|
|
4
|
+
* The element overlays are rendered into. Pass the shadow root, or any element inside it.
|
|
5
|
+
* `undefined` restores the default, so a subtree can opt back out.
|
|
6
|
+
*/
|
|
7
|
+
container: Element | undefined;
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
}
|
|
10
|
+
export declare function OverlayPortalProvider({ container, children }: OverlayPortalProviderProps): React.JSX.Element;
|
|
11
|
+
/**
|
|
12
|
+
* The container this overlay should render into. An explicit prop wins, so one panel can be placed
|
|
13
|
+
* elsewhere without unmounting the provider around it.
|
|
14
|
+
*
|
|
15
|
+
* Not exported from the public barrel: this is a contract between the provider and the overlays
|
|
16
|
+
* this library ships, not a knob for consumers — a consumer that needs the value has a provider.
|
|
17
|
+
*/
|
|
18
|
+
export declare function useOverlayPortalContainer(override?: Element): Element | undefined;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { enableShadowDOM } from "react-stately/private/flags/flags";
|
|
5
|
+
const OverlayPortalContext = React.createContext(void 0);
|
|
6
|
+
enableShadowDOM();
|
|
7
|
+
function OverlayPortalProvider({ container, children }) {
|
|
8
|
+
return /* @__PURE__ */ jsx(OverlayPortalContext.Provider, { value: container, children });
|
|
9
|
+
}
|
|
10
|
+
function useOverlayPortalContainer(override) {
|
|
11
|
+
const fromContext = React.useContext(OverlayPortalContext);
|
|
12
|
+
return override ?? fromContext;
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
OverlayPortalProvider,
|
|
16
|
+
useOverlayPortalContainer
|
|
17
|
+
};
|
|
@@ -3,6 +3,7 @@ import type { Locale } from "date-fns";
|
|
|
3
3
|
import type { DayPickerProps } from "react-day-picker";
|
|
4
4
|
import type { AppLocale, AppRequestHeaders, AppTimeFormat, AppTimezone, AppTimezoneDefault, AppDateFormat } from "../../app/types.js";
|
|
5
5
|
import type { AppBrand, AppDensity, AppFontSize, AppTheme } from "../../app/theme-axes.js";
|
|
6
|
+
import type { AppPreferenceAxis } from "../../app/storage.js";
|
|
6
7
|
import type { AppSettingPickerAppearanceProp, AppSettingToggleAppearanceProp, ChildrenProp, ClassNameProp, DisabledProp, IdProp, NameProp, OnValueChangeProp, ValueProp } from "../vocabulary/index.js";
|
|
7
8
|
/** @see AppProvider */
|
|
8
9
|
export type AppProviderProp = {
|
|
@@ -26,8 +27,18 @@ export type AppProviderProp = {
|
|
|
26
27
|
timezoneOptions?: readonly AppTimezone[];
|
|
27
28
|
/** localStorage key. Default: `godxjp.app`. */
|
|
28
29
|
storageKey?: string;
|
|
29
|
-
/**
|
|
30
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Which viewer preferences survive a reload. `true` (default) every axis, `false` none, or a
|
|
32
|
+
* LIST of axes — `["theme", "density", "fontSize"]`.
|
|
33
|
+
*
|
|
34
|
+
* The list exists because the axes do not share an owner. `theme` / `brand` / `density` /
|
|
35
|
+
* `fontSize` / `scaling` are the VIEWER's and belong in this browser. `locale` / `timezone` /
|
|
36
|
+
* `timeFormat` / `dateFormat` are frequently the SERVER's, resolved per request from a cookie,
|
|
37
|
+
* an account row or a header — and a stored copy then WINS over the value the server just sent,
|
|
38
|
+
* because storage is read after the props. Faced with one all-or-nothing flag, that consumer
|
|
39
|
+
* sets `persist={false}` and loses the viewer's theme along with it; naming the axes keeps both.
|
|
40
|
+
*/
|
|
41
|
+
persist?: boolean | readonly AppPreferenceAxis[];
|
|
31
42
|
/**
|
|
32
43
|
* Initial theme choice. `"light"` / `"dark"` are written straight to `<html data-theme>`;
|
|
33
44
|
* `"system"` defers to `prefers-color-scheme` and is re-resolved whenever the OS changes.
|
|
@@ -89,6 +89,19 @@ export type ButtonProp = React.ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
|
89
89
|
/** Corner shape — `default` (control radius) · `pill` (fully rounded) · `sharp` (square). */
|
|
90
90
|
shape?: ShapeProp;
|
|
91
91
|
fullWidth?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Take the space the siblings leave, and let a long label ELLIPSE instead of widening the row —
|
|
94
|
+
* the same axis `Flex` calls `fill`, for the same reason.
|
|
95
|
+
*
|
|
96
|
+
* Button ships `flex-shrink: 0`, right almost everywhere and wrong in a constrained bar: an
|
|
97
|
+
* account menu holding an avatar plus a person's name keeps its full width while the cluster
|
|
98
|
+
* clips it, so a keyboard user tabs to a control they cannot see (SC 2.4.7). Until this axis
|
|
99
|
+
* existed the only move was `className="min-w-0 flex-1"`, which ui-audit blocks — and which this
|
|
100
|
+
* package's own Topbar guidance recommended, so the docs prescribed the utility the audit forbids.
|
|
101
|
+
*
|
|
102
|
+
* Sets `flex: 1 1 auto` and `min-inline-size: 0`; pair it with a `<Text truncate>` label.
|
|
103
|
+
*/
|
|
104
|
+
fill?: boolean;
|
|
92
105
|
/** Allow a text button to grow vertically for multi-line labels. */
|
|
93
106
|
wrap?: boolean;
|
|
94
107
|
/** Logical content alignment, especially for full-width collection actions. */
|
|
@@ -336,6 +336,30 @@ export type AppShellProp = {
|
|
|
336
336
|
* to be tellable apart by name; the shell always supplies both defaults rather than requiring
|
|
337
337
|
* this prop, so the two columns of equal rank behave the same way.
|
|
338
338
|
*/
|
|
339
|
+
/**
|
|
340
|
+
* WHICH EDGE the rail sits on. `start` (default) and `end` are the INLINE edges — logical, so an
|
|
341
|
+
* RTL document mirrors them without a `[dir]` rule; `top` and `bottom` are the block edges, where
|
|
342
|
+
* the rail becomes a full-measure horizontal strip and the shell grows a ROW instead of a column.
|
|
343
|
+
*
|
|
344
|
+
* The scope contract does not move with it: wherever it sits, the rail is PLATFORM scope. The
|
|
345
|
+
* edge is a presentation choice — a docked column reads as permanent chrome (Slack), a bottom
|
|
346
|
+
* strip reads as the phone/tab-bar shape, a top strip as a platform band above the app's own bar.
|
|
347
|
+
* Thickness follows the orientation: `--app-shell-nav-rail-width` as a column,
|
|
348
|
+
* `--app-shell-nav-rail-height` as a strip.
|
|
349
|
+
*
|
|
350
|
+
* Collapsing the sidebar folds the sidebar track only, at every position.
|
|
351
|
+
*/
|
|
352
|
+
navRailPosition?: "start" | "end" | "top" | "bottom";
|
|
353
|
+
/**
|
|
354
|
+
* Rail content pinned to its FAR end — the counterpart of `Sidebar`'s `footer`, and the tray end
|
|
355
|
+
* of a taskbar: settings, appearance, the account glyph. It follows the orientation, so it is the
|
|
356
|
+
* bottom of a column and the inline-end of a strip, and it stays put while `navRail` scrolls.
|
|
357
|
+
*
|
|
358
|
+
* A slot rather than "whatever you put last", because pinning it needs an auto margin on the
|
|
359
|
+
* right axis — geometry that would otherwise land in consumer CSS, which this library does not
|
|
360
|
+
* accept. Ignored when `navRail` is not passed: there is no rail to pin anything to.
|
|
361
|
+
*/
|
|
362
|
+
navRailEnd?: ReactNode;
|
|
339
363
|
navRailLabel?: string;
|
|
340
364
|
/**
|
|
341
365
|
* Navigation shown in the mobile drawer at the DXS 900px breakpoint, where the docked sidebar is
|
|
@@ -398,6 +422,22 @@ export type AuthShellProp = {
|
|
|
398
422
|
* flow geometry.
|
|
399
423
|
*/
|
|
400
424
|
measure?: "default" | "wide";
|
|
425
|
+
/**
|
|
426
|
+
* Block-axis placement of the auth column, ORTHOGONAL to `preset` the way `variant` is: a preset
|
|
427
|
+
* owns the page MEASURE (card width, inline gutters, section rhythm), `align` owns where that
|
|
428
|
+
* column sits vertically.
|
|
429
|
+
*
|
|
430
|
+
* Omit it to keep the preset's own choice — `"login"` and `"registration"` anchor so a
|
|
431
|
+
* requester/identity line that wraps to two lines cannot move the card, every other preset
|
|
432
|
+
* centres. Pass `"center"` for a vertically centred column (the block-start inset collapses to
|
|
433
|
+
* the preset's block-end one, desktop and mobile, so the padding is symmetric) or `"anchored"`
|
|
434
|
+
* for a top-anchored one. This replaces re-declaring a preset's offset tokens from consumer CSS.
|
|
435
|
+
*
|
|
436
|
+
* HAZARD on a tall flow: a vertically centred tall card overflows ABOVE the scroll origin on a
|
|
437
|
+
* short viewport, putting its first field out of reach. That is why `"registration"` anchors by
|
|
438
|
+
* default; `"center"` is legal there but is the caller's judgement.
|
|
439
|
+
*/
|
|
440
|
+
align?: "anchored" | "center";
|
|
401
441
|
/**
|
|
402
442
|
* Vertical density scoped to auth-card descendants. The canonical variant defaults to
|
|
403
443
|
* `"compact"`; the default variant defaults to `"comfortable"`.
|
|
@@ -988,6 +1028,11 @@ export type AppLauncherProp = {
|
|
|
988
1028
|
* Grid column count. Omit it and the panel keeps the stylesheet's own `--app-launcher-columns`
|
|
989
1029
|
* (3, the Google-launcher shape, declared on `.ui-app-launcher-panel`): the default is where a
|
|
990
1030
|
* theme can reach it, and this prop is the per-instance override written inline on top.
|
|
1031
|
+
*
|
|
1032
|
+
* `responsive="fullscreen"` steps that DEFAULT up with the surface — 3 · 4 · 5 · 6 on the house
|
|
1033
|
+
* container ladder — because a fixed three columns on a full viewport is three columns of tiles
|
|
1034
|
+
* and a screen of nothing. Passing `columns` still wins everywhere: an inline custom property
|
|
1035
|
+
* beats every stylesheet rule, so a stated count is a stated count on both surfaces.
|
|
991
1036
|
*/
|
|
992
1037
|
columns?: number;
|
|
993
1038
|
/**
|
|
@@ -1008,11 +1053,42 @@ export type AppLauncherProp = {
|
|
|
1008
1053
|
* responsive="auto"`, resolved through the shared `useSheetResponsiveMode()` hook, so a service
|
|
1009
1054
|
* moves the drawer line once for every overlay instead of per component.
|
|
1010
1055
|
*
|
|
1011
|
-
*
|
|
1012
|
-
*
|
|
1013
|
-
*
|
|
1056
|
+
* `"fullscreen"` is the LAUNCHPAD: one full-viewport surface, the page behind it blurred, the
|
|
1057
|
+
* grid floating on that ground at tile size — the macOS Launchpad / Windows Start shape. It is
|
|
1058
|
+
* pinned at every width, because a start surface that becomes a popover on a wide screen is two
|
|
1059
|
+
* different products.
|
|
1060
|
+
*
|
|
1061
|
+
* WHEN IT IS RIGHT, AND WHEN IT IS NOT. This prop used to say a modal was always wrong here:
|
|
1062
|
+
* "a launcher grid is a jump table, and a modal that takes over the screen to offer nine links
|
|
1063
|
+
* is heavier than the errand." That reasoning is sound for a launcher in ONE application's
|
|
1064
|
+
* topbar, where the grid is a shortcut away from the work on screen and the work should stay
|
|
1065
|
+
* visible. It does not hold for a PLATFORM start bar — a strip that is present in every service,
|
|
1066
|
+
* whose launcher is the primary way to move between products rather than a shortcut. There the
|
|
1067
|
+
* grid IS the errand, the page behind it is the thing being left, and the interruption is the
|
|
1068
|
+
* point. Keep `"auto"` for a topbar launcher; reach for `"fullscreen"` for a dock.
|
|
1069
|
+
*/
|
|
1070
|
+
responsive?: "auto" | "popover" | "sheet" | "fullscreen";
|
|
1071
|
+
/**
|
|
1072
|
+
* The BOX the trigger takes — the same split `AppSettingToggle` draws, and for the same reason.
|
|
1073
|
+
* `bar` (default) is a `TopbarItem`: a cell as tall as the bar, whose hover is the bar's own
|
|
1074
|
+
* surface. `icon` is a square ghost `Button`, for chrome that is NOT a bar — a nav rail, a card
|
|
1075
|
+
* header, a toolbar. A `TopbarItem` outside a bar has nothing to bleed to: it stretches to a
|
|
1076
|
+
* container that never set a band height, and its squared corners and full-bleed hover read as a
|
|
1077
|
+
* broken cell. The panel, the grid and the responsive contract are identical either way.
|
|
1078
|
+
*/
|
|
1079
|
+
appearance?: "bar" | "icon";
|
|
1080
|
+
/**
|
|
1081
|
+
* Which side of the trigger the panel opens on, and how it aligns to it. Both default from
|
|
1082
|
+
* `appearance` — a bar drops the grid below and aligns to the bar's end; anything else opens to
|
|
1083
|
+
* the inline-end aligned to the trigger's start.
|
|
1084
|
+
*
|
|
1085
|
+
* State them when the chrome can be RE-DOCKED. `appearance` says the trigger is not in a bar; it
|
|
1086
|
+
* cannot say which way is out, and a rail pinned to the top edge still opens downward. Measured
|
|
1087
|
+
* without this: a launcher in a top strip opened sideways and left the panel 440px from the
|
|
1088
|
+
* trigger it belonged to.
|
|
1014
1089
|
*/
|
|
1015
|
-
|
|
1090
|
+
side?: "top" | "right" | "bottom" | "left";
|
|
1091
|
+
align?: "start" | "center" | "end";
|
|
1016
1092
|
open?: boolean;
|
|
1017
1093
|
onOpenChange?: (open: boolean) => void;
|
|
1018
1094
|
className?: ClassNameProp;
|
|
@@ -1026,7 +1102,21 @@ export type SidebarProp = {
|
|
|
1026
1102
|
sections?: SidebarSectionProp[];
|
|
1027
1103
|
product?: SidebarProductProp;
|
|
1028
1104
|
onProductClick?: () => void;
|
|
1029
|
-
|
|
1105
|
+
/**
|
|
1106
|
+
* Header slot ABOVE the navigation, replacing `product`.
|
|
1107
|
+
*
|
|
1108
|
+
* Pass a FUNCTION to follow the EFFECTIVE collapsed state. A plain node cannot: `AppShell` hands
|
|
1109
|
+
* the same Sidebar to the drawer and the drawer un-collapses the rows (see `NavSurface`), so a
|
|
1110
|
+
* node built from the consumer's own `collapsed` boolean renders a glyph-only lockup inside a
|
|
1111
|
+
* full-width drawer. The escape hatch consumers reach for is a SECOND hand-built `Sidebar` in
|
|
1112
|
+
* `AppShell.mobileNav` — and that override is precisely what switches off `railInDrawer`, so the
|
|
1113
|
+
* `navRail` silently stops reaching mobile. The function is called with the surface-effective
|
|
1114
|
+
* value, which removes the reason to build the second node at all.
|
|
1115
|
+
*
|
|
1116
|
+
* SCOPE: this is the APP's brand lockup. A PLATFORM switch (which organization, which app) does
|
|
1117
|
+
* not belong here — see AppShell's `navRail` for where it goes and why.
|
|
1118
|
+
*/
|
|
1119
|
+
brand?: ReactNode | ((collapsed: boolean) => ReactNode);
|
|
1030
1120
|
collapsed?: boolean;
|
|
1031
1121
|
children?: ChildrenProp;
|
|
1032
1122
|
/**
|
|
@@ -458,6 +458,7 @@
|
|
|
458
458
|
display: flex;
|
|
459
459
|
min-width: 0;
|
|
460
460
|
align-items: center;
|
|
461
|
+
flex-wrap: wrap;
|
|
461
462
|
gap: var(--card-service-launcher-space-gap);
|
|
462
463
|
}
|
|
463
464
|
|
|
@@ -483,9 +484,17 @@
|
|
|
483
484
|
height: var(--card-service-launcher-icon-glyph-size);
|
|
484
485
|
}
|
|
485
486
|
|
|
487
|
+
[data-slot="service-launcher-identity"] {
|
|
488
|
+
display: flex;
|
|
489
|
+
min-width: 0;
|
|
490
|
+
flex: 1 1 auto;
|
|
491
|
+
align-items: center;
|
|
492
|
+
gap: var(--card-service-launcher-space-gap);
|
|
493
|
+
}
|
|
494
|
+
|
|
486
495
|
[data-slot="service-launcher-title"] {
|
|
487
496
|
min-width: 0;
|
|
488
|
-
flex: 1;
|
|
497
|
+
flex: 1 1 auto;
|
|
489
498
|
overflow-wrap: anywhere;
|
|
490
499
|
font-size: var(--card-title-font-size);
|
|
491
500
|
font-weight: var(--card-title-font-weight);
|
|
@@ -113,7 +113,8 @@
|
|
|
113
113
|
min-inline-size: 0;
|
|
114
114
|
overflow: hidden;
|
|
115
115
|
font-size: var(--chart-trend-tick-font-size);
|
|
116
|
-
|
|
116
|
+
|
|
117
|
+
line-height: var(--line-height-normal);
|
|
117
118
|
color: hsl(var(--muted-foreground));
|
|
118
119
|
text-align: center;
|
|
119
120
|
text-overflow: ellipsis;
|
package/dist/styles/control.css
CHANGED
|
@@ -647,6 +647,11 @@
|
|
|
647
647
|
justify-content: center;
|
|
648
648
|
color: hsl(var(--muted-foreground));
|
|
649
649
|
}
|
|
650
|
+
|
|
651
|
+
[data-slot="list-row-leading"] > svg:not([class*="size-"]) {
|
|
652
|
+
width: var(--list-row-leading-icon-size, var(--control-icon-size));
|
|
653
|
+
height: var(--list-row-leading-icon-size, var(--control-icon-size));
|
|
654
|
+
}
|
|
650
655
|
[data-slot="list-row-body"] {
|
|
651
656
|
display: flex;
|
|
652
657
|
min-inline-size: min(var(--list-row-body-min-width, 12rem), 100%);
|
|
@@ -1086,7 +1091,8 @@
|
|
|
1086
1091
|
);
|
|
1087
1092
|
color: hsl(var(--foreground));
|
|
1088
1093
|
font-size: var(--timeline-grid-event-font-size);
|
|
1089
|
-
|
|
1094
|
+
|
|
1095
|
+
line-height: var(--line-height-normal);
|
|
1090
1096
|
text-align: start;
|
|
1091
1097
|
}
|
|
1092
1098
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
[data-slot="auth-legal-footer"] .ui-app-setting-picker-inline {
|
|
16
16
|
width: auto;
|
|
17
|
-
min-
|
|
17
|
+
min-block-size: var(--auth-footer-target-min-size);
|
|
18
18
|
height: auto;
|
|
19
19
|
border-width: 0;
|
|
20
20
|
border-radius: 0;
|
|
@@ -961,6 +961,8 @@
|
|
|
961
961
|
}
|
|
962
962
|
|
|
963
963
|
.ui-app-setting-picker-icon[data-appearance="bar"] {
|
|
964
|
+
min-inline-size: var(--topbar-item-min-width);
|
|
965
|
+
padding-inline: var(--topbar-item-padding-inline);
|
|
964
966
|
border-width: var(--app-setting-picker-bar-border-width);
|
|
965
967
|
box-shadow: var(--app-setting-picker-bar-shadow);
|
|
966
968
|
}
|