@godxjp/ui 5.0.2 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -142
- package/package.json +124 -128
- package/scripts/ui-audit.mjs +179 -0
- package/src/app/__tests__/app-provider.test.tsx +232 -0
- package/src/app/__tests__/date-format-labels.test.ts +36 -0
- package/src/app/__tests__/date-formats.test.ts +44 -0
- package/src/app/__tests__/timezones.test.ts +65 -0
- package/src/app/app-provider.tsx +227 -0
- package/src/app/date-format-labels.ts +21 -0
- package/src/app/date-formats.ts +30 -0
- package/src/app/index.ts +40 -0
- package/src/app/locales.ts +32 -0
- package/src/app/request-headers.ts +31 -0
- package/src/app/storage.ts +44 -0
- package/src/app/time-format-labels.ts +19 -0
- package/src/app/time-formats.ts +15 -0
- package/src/app/timezones.ts +208 -0
- package/src/app/types.ts +39 -0
- package/src/app/use-formatting.ts +47 -0
- package/src/components/__tests__/accessibility-primitives.test.tsx +65 -0
- package/src/components/__tests__/docs-parity.test.ts +41 -0
- package/src/components/__tests__/shadcn-release-guardrails.test.ts +71 -0
- package/src/components/__tests__/theme-axes-integration.test.tsx +242 -0
- package/src/components/admin/index.ts +76 -0
- package/src/components/data-display/__tests__/card-table.test.tsx +328 -0
- package/src/components/data-display/__tests__/data-display.test.tsx +73 -0
- package/src/components/data-display/__tests__/data-table.test.tsx +84 -0
- package/src/components/data-display/__tests__/popover.test.tsx +92 -0
- package/src/components/data-display/__tests__/scroll-area-collapsible.test.tsx +66 -0
- package/src/components/data-display/badge.tsx +27 -0
- package/src/components/data-display/card.tsx +194 -0
- package/src/components/data-display/code-badge.tsx +28 -0
- package/src/components/data-display/collapsible.tsx +5 -0
- package/src/components/data-display/data-table.tsx +476 -0
- package/src/components/data-display/empty-state.tsx +22 -0
- package/src/components/data-display/index.ts +41 -0
- package/src/components/data-display/key-value-grid.tsx +46 -0
- package/src/components/data-display/popover.tsx +62 -0
- package/src/components/data-display/progress-meter.tsx +20 -0
- package/src/components/data-display/scan-panel.tsx +16 -0
- package/src/components/data-display/scroll-area.tsx +42 -0
- package/src/components/data-display/status-badge.tsx +83 -0
- package/src/components/data-display/table.tsx +59 -0
- package/src/components/data-display/timeline.tsx +42 -0
- package/src/components/data-display/tree-list.tsx +42 -0
- package/src/components/data-entry/__fixtures__/tree-options.ts +80 -0
- package/src/components/data-entry/__tests__/cascader-tree-transfer.test.tsx +417 -0
- package/src/components/data-entry/__tests__/checkbox-group.test.tsx +40 -0
- package/src/components/data-entry/__tests__/checkbox.test.tsx +20 -0
- package/src/components/data-entry/__tests__/date-autocomplete.test.tsx +94 -0
- package/src/components/data-entry/__tests__/form-field.test.tsx +49 -0
- package/src/components/data-entry/__tests__/input-textarea.test.tsx +38 -0
- package/src/components/data-entry/__tests__/label-select.test.tsx +62 -0
- package/src/components/data-entry/__tests__/pickers.test.tsx +74 -0
- package/src/components/data-entry/__tests__/radio.test.tsx +46 -0
- package/src/components/data-entry/__tests__/search-input.test.tsx +32 -0
- package/src/components/data-entry/__tests__/switch-field.test.tsx +52 -0
- package/src/components/data-entry/__tests__/upload.test.tsx +125 -0
- package/src/components/data-entry/autocomplete.tsx +91 -0
- package/src/components/data-entry/calendar.tsx +90 -0
- package/src/components/data-entry/cascader.tsx +305 -0
- package/src/components/data-entry/checkbox-group.tsx +90 -0
- package/src/components/data-entry/checkbox.tsx +30 -0
- package/src/components/data-entry/choice-field.tsx +27 -0
- package/src/components/data-entry/choice-option.ts +20 -0
- package/src/components/data-entry/color-picker.tsx +75 -0
- package/src/components/data-entry/command.tsx +56 -0
- package/src/components/data-entry/country-select.tsx +88 -0
- package/src/components/data-entry/date-picker.tsx +69 -0
- package/src/components/data-entry/date-range-picker.tsx +75 -0
- package/src/components/data-entry/form-field.tsx +59 -0
- package/src/components/data-entry/index.ts +62 -0
- package/src/components/data-entry/input.tsx +26 -0
- package/src/components/data-entry/label.tsx +25 -0
- package/src/components/data-entry/radio.tsx +109 -0
- package/src/components/data-entry/search-input.tsx +103 -0
- package/src/components/data-entry/select.tsx +149 -0
- package/src/components/data-entry/slider.tsx +38 -0
- package/src/components/data-entry/switch-field.tsx +91 -0
- package/src/components/data-entry/switch.tsx +24 -0
- package/src/components/data-entry/textarea.tsx +12 -0
- package/src/components/data-entry/time-picker.tsx +214 -0
- package/src/components/data-entry/transfer.tsx +231 -0
- package/src/components/data-entry/tree-select-strategy.ts +6 -0
- package/src/components/data-entry/tree-select.tsx +279 -0
- package/src/components/data-entry/tree-utils.ts +221 -0
- package/src/components/data-entry/upload-crop-dialog.tsx +109 -0
- package/src/components/data-entry/upload-types.ts +86 -0
- package/src/components/data-entry/upload.tsx +498 -0
- package/src/components/data-entry/use-upload-draft.ts +93 -0
- package/src/components/feedback/__tests__/alert.test.tsx +127 -0
- package/src/components/feedback/__tests__/dialog.test.tsx +290 -0
- package/src/components/feedback/__tests__/sheet.test.tsx +94 -0
- package/src/components/feedback/__tests__/skeleton.test.tsx +25 -0
- package/src/components/feedback/__tests__/toast.test.tsx +52 -0
- package/src/components/feedback/alert.tsx +167 -0
- package/src/components/feedback/dialog.tsx +325 -0
- package/src/components/feedback/index.ts +53 -0
- package/src/components/feedback/sheet.tsx +130 -0
- package/src/components/feedback/skeleton.tsx +95 -0
- package/src/components/feedback/sonner.tsx +54 -0
- package/src/components/feedback/toaster.tsx +1 -0
- package/src/components/feedback/use-toast.ts +62 -0
- package/src/components/general/__tests__/button.test.tsx +71 -0
- package/src/components/general/button.tsx +61 -0
- package/src/components/general/index.ts +2 -0
- package/src/components/layout/__tests__/page-container.test.tsx +69 -0
- package/src/components/layout/__tests__/page-inset.test.tsx +14 -0
- package/src/components/layout/__tests__/stack-inline.test.tsx +39 -0
- package/src/components/layout/app-shell.tsx +42 -0
- package/src/components/layout/breadcrumb.tsx +35 -0
- package/src/components/layout/index.ts +31 -0
- package/src/components/layout/inline.tsx +13 -0
- package/src/components/layout/menu.tsx +34 -0
- package/src/components/layout/mobile-frame.tsx +57 -0
- package/src/components/layout/page-container.tsx +81 -0
- package/src/components/layout/page-inset.tsx +16 -0
- package/src/components/layout/responsive-grid.tsx +14 -0
- package/src/components/layout/shell-app.tsx +30 -0
- package/src/components/layout/sidebar.tsx +98 -0
- package/src/components/layout/split-pane.tsx +16 -0
- package/src/components/layout/stack.tsx +13 -0
- package/src/components/layout/topbar.tsx +108 -0
- package/src/components/navigation/__tests__/app-pickers.test.tsx +118 -0
- package/src/components/navigation/__tests__/dropdown-menu.test.tsx +104 -0
- package/src/components/navigation/__tests__/navigation.test.tsx +61 -0
- package/src/components/navigation/__tests__/pagination-steps-tabs.test.tsx +76 -0
- package/src/components/navigation/date-format-picker.tsx +55 -0
- package/src/components/navigation/dropdown-menu.tsx +190 -0
- package/src/components/navigation/filter-bar.tsx +38 -0
- package/src/components/navigation/index.ts +28 -0
- package/src/components/navigation/locale-picker.tsx +49 -0
- package/src/components/navigation/page-header.tsx +50 -0
- package/src/components/navigation/pagination-utils.ts +35 -0
- package/src/components/navigation/pagination.tsx +168 -0
- package/src/components/navigation/steps.tsx +163 -0
- package/src/components/navigation/tabs-items.tsx +69 -0
- package/src/components/navigation/tabs.tsx +67 -0
- package/src/components/navigation/time-format-picker.tsx +55 -0
- package/src/components/navigation/timezone-picker.tsx +63 -0
- package/src/components/query/__tests__/data-state.test.tsx +214 -0
- package/src/components/query/__tests__/infinite-prefetch.test.tsx +105 -0
- package/src/components/query/__tests__/query-helpers.test.tsx +61 -0
- package/src/components/query/data-state.tsx +58 -0
- package/src/components/query/index.ts +10 -0
- package/src/components/query/infinite-query-state.tsx +99 -0
- package/src/components/query/mutation-feedback.tsx +31 -0
- package/src/components/query/prefetch-link.tsx +45 -0
- package/src/components/query/query-refetch-button.tsx +41 -0
- package/src/components/ui/alert-dialog.tsx +1 -0
- package/src/components/ui/alert.tsx +1 -0
- package/src/components/ui/autocomplete.tsx +1 -0
- package/src/components/ui/badge.tsx +1 -0
- package/src/components/ui/button.tsx +1 -0
- package/src/components/ui/calendar.tsx +1 -0
- package/src/components/ui/card.tsx +1 -0
- package/src/components/ui/checkbox.tsx +1 -0
- package/src/components/ui/color-picker.tsx +1 -0
- package/src/components/ui/command.tsx +1 -0
- package/src/components/ui/date-picker.tsx +1 -0
- package/src/components/ui/date-range-picker.tsx +1 -0
- package/src/components/ui/dialog.tsx +1 -0
- package/src/components/ui/dropdown-menu.tsx +1 -0
- package/src/components/ui/index.tsx +31 -0
- package/src/components/ui/input.tsx +1 -0
- package/src/components/ui/label.tsx +1 -0
- package/src/components/ui/pagination.tsx +1 -0
- package/src/components/ui/popover.tsx +1 -0
- package/src/components/ui/radio.tsx +1 -0
- package/src/components/ui/scroll-area.tsx +1 -0
- package/src/components/ui/select.tsx +1 -0
- package/src/components/ui/sheet.tsx +1 -0
- package/src/components/ui/slider.tsx +1 -0
- package/src/components/ui/sonner.tsx +1 -0
- package/src/components/ui/switch.tsx +1 -0
- package/src/components/ui/table.tsx +1 -0
- package/src/components/ui/tabs-items.tsx +1 -0
- package/src/components/ui/tabs.tsx +1 -0
- package/src/components/ui/textarea.tsx +1 -0
- package/src/components/ui/time-picker.tsx +1 -0
- package/src/components/ui/upload.tsx +1 -0
- package/src/form/__tests__/use-zod-form.test.tsx +97 -0
- package/src/form/form-field-control.tsx +44 -0
- package/src/form/form-root.tsx +29 -0
- package/src/form/index.ts +7 -0
- package/src/form/use-zod-form.ts +29 -0
- package/src/i18n/__tests__/translate.test.ts +23 -0
- package/src/i18n/index.ts +9 -0
- package/src/i18n/messages/en.json +171 -0
- package/src/i18n/messages/ja.json +171 -0
- package/src/i18n/messages/vi.json +171 -0
- package/src/i18n/translate.ts +74 -0
- package/src/i18n/use-translation.ts +53 -0
- package/src/index.ts +3 -0
- package/src/lib/__tests__/control-styles.test.ts +78 -0
- package/src/lib/__tests__/datetime.test.ts +77 -0
- package/src/lib/__tests__/format-date.test.ts +97 -0
- package/src/lib/__tests__/format.test.ts +62 -0
- package/src/lib/__tests__/theme-tokens-audit.test.ts +176 -0
- package/src/lib/__tests__/theme-tokens-css.test.ts +118 -0
- package/src/lib/__tests__/token-governance.test.ts +191 -0
- package/src/lib/__tests__/variants.test.ts +18 -0
- package/src/lib/control-styles.ts +33 -0
- package/src/lib/datetime/detect.ts +25 -0
- package/src/lib/datetime/format-date.ts +100 -0
- package/src/lib/datetime/format.ts +140 -0
- package/src/lib/datetime/index.ts +25 -0
- package/src/lib/datetime/parse.ts +51 -0
- package/src/lib/datetime/sync.ts +48 -0
- package/src/lib/format.ts +114 -0
- package/src/lib/hooks.ts +54 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/variants.ts +40 -0
- package/src/props/components/app.prop.ts +99 -0
- package/src/props/components/data-display.prop.ts +73 -0
- package/src/props/components/data-entry.prop.ts +334 -0
- package/src/props/components/feedback.prop.ts +80 -0
- package/src/props/components/form.prop.ts +46 -0
- package/src/props/components/general.prop.ts +18 -0
- package/src/props/components/index.ts +99 -0
- package/src/props/components/layout.prop.ts +130 -0
- package/src/props/components/navigation.prop.ts +88 -0
- package/src/props/components/query.prop.ts +94 -0
- package/src/props/index.ts +17 -0
- package/src/props/registry.ts +603 -0
- package/src/props/vocabulary/content.prop.ts +35 -0
- package/src/props/vocabulary/data.prop.ts +46 -0
- package/src/props/vocabulary/index.ts +73 -0
- package/src/props/vocabulary/interaction.prop.ts +42 -0
- package/src/props/vocabulary/layout.prop.ts +25 -0
- package/src/props/vocabulary/navigation.prop.ts +19 -0
- package/src/props/vocabulary/shared.prop.ts +59 -0
- package/src/styles/alert-layout.css +191 -0
- package/src/styles/badge-layout.css +22 -0
- package/src/styles/card-layout.css +373 -0
- package/src/styles/control.css +504 -0
- package/src/styles/data-display-layout.css +246 -0
- package/src/styles/density.css +43 -0
- package/src/styles/dialog-layout.css +84 -0
- package/src/styles/index.css +105 -0
- package/src/styles/layout.css +479 -0
- package/src/styles/shell-layout.css +604 -0
- package/src/styles/table-layout.css +109 -0
- package/src/test/__tests__/render-loop-guard.test.tsx +38 -0
- package/src/test/jest-dom.d.ts +4 -0
- package/src/test/render-loop-guard.tsx +50 -0
- package/src/test/render.tsx +29 -0
- package/src/test/theme-globals.test.ts +77 -0
- package/src/test/theme-globals.ts +134 -0
- package/src/test/theme-test-utils.tsx +67 -0
- package/src/theme/example.service.css +37 -0
- package/src/tokens/base.css +13 -0
- package/src/tokens/foundation.css +151 -0
- package/src/tokens/primitives/badge.css +13 -0
- package/src/tokens/primitives/card.css +29 -0
- package/src/tokens/primitives/control.css +55 -0
- package/src/tokens/primitives/feedback.css +17 -0
- package/src/tokens/primitives/layout.css +20 -0
- package/src/tokens/primitives/navigation.css +13 -0
- package/src/tokens/primitives/table.css +10 -0
- package/BRAND.md +0 -296
- package/CHANGELOG.md +0 -668
- package/config/eslint.js +0 -54
- package/config/prettier.cjs +0 -20
- package/config/tsconfig.base.json +0 -22
- package/config/vitest.base.ts +0 -26
- package/dist/MiniMonth-YAmPGEpC.d.ts +0 -143
- package/dist/Table.types-BbsxoIYE.d.ts +0 -352
- package/dist/color-DO0qqUAb.d.ts +0 -38
- package/dist/components/composites.d.ts +0 -963
- package/dist/components/composites.js +0 -7343
- package/dist/components/composites.js.map +0 -1
- package/dist/components/primitives.d.ts +0 -2744
- package/dist/components/primitives.js +0 -7356
- package/dist/components/primitives.js.map +0 -1
- package/dist/components/shell.d.ts +0 -182
- package/dist/components/shell.js +0 -774
- package/dist/components/shell.js.map +0 -1
- package/dist/hooks.d.ts +0 -100
- package/dist/hooks.js +0 -558
- package/dist/hooks.js.map +0 -1
- package/dist/i18n.d.ts +0 -61
- package/dist/i18n.js +0 -860
- package/dist/i18n.js.map +0 -1
- package/dist/index.d.ts +0 -33
- package/dist/index.js +0 -13062
- package/dist/index.js.map +0 -1
- package/dist/padding-DY0JV5Ja.d.ts +0 -16
- package/dist/preferences.d.ts +0 -132
- package/dist/preferences.js +0 -262
- package/dist/preferences.js.map +0 -1
- package/dist/props.d.ts +0 -86
- package/dist/props.js +0 -16
- package/dist/props.js.map +0 -1
- package/dist/size-CQwNvOWd.d.ts +0 -19
- package/dist/types-LTj-2bl-.d.ts +0 -30
- package/dist/useTableViews-D5NIAJ7h.d.ts +0 -154
- package/src/tokens/tailwind.css +0 -158
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { RenderLoopGuard, withRenderLoopGuard } from "../render-loop-guard";
|
|
5
|
+
|
|
6
|
+
function BadCounter() {
|
|
7
|
+
const [, bump] = React.useState(0);
|
|
8
|
+
bump(1);
|
|
9
|
+
return <div>loop</div>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe("RenderLoopGuard", () => {
|
|
13
|
+
it("passes for stable components", () => {
|
|
14
|
+
render(
|
|
15
|
+
<RenderLoopGuard maxRenders={10} label="Stable">
|
|
16
|
+
<p>ok</p>
|
|
17
|
+
</RenderLoopGuard>,
|
|
18
|
+
);
|
|
19
|
+
expect(screen.getByText("ok")).toBeInTheDocument();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("throws when render count exceeds maxRenders", () => {
|
|
23
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
24
|
+
expect(() =>
|
|
25
|
+
render(
|
|
26
|
+
<RenderLoopGuard maxRenders={5} label="BadCounter">
|
|
27
|
+
<BadCounter />
|
|
28
|
+
</RenderLoopGuard>,
|
|
29
|
+
),
|
|
30
|
+
).toThrow(/Possible infinite render loop|Too many re-renders/i);
|
|
31
|
+
errorSpy.mockRestore();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("withRenderLoopGuard wraps StrictMode", () => {
|
|
35
|
+
render(withRenderLoopGuard(<span data-testid="child">child</span>, { maxRenders: 10 }));
|
|
36
|
+
expect(screen.getByTestId("child")).toBeInTheDocument();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Loads @testing-library/jest-dom's matcher augmentation onto vitest's `expect`
|
|
2
|
+
// (toBeInTheDocument, toHaveAttribute, …) for type-checking. Picked up via the
|
|
3
|
+
// tsconfig `include: ["src"]` glob. Self-contained — see docs/DEVELOPMENT.md §5.
|
|
4
|
+
import "@testing-library/jest-dom/vitest";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test-only guard that surfaces runaway re-render loops as a clear error instead
|
|
5
|
+
* of a hung test. Counts its own renders; once they exceed `maxRenders` it throws,
|
|
6
|
+
* so an effect/state loop in the tree under test fails fast and legibly.
|
|
7
|
+
*
|
|
8
|
+
* Self-contained (no external tooling dependency) — see docs/DEVELOPMENT.md §5.
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_MAX_RENDERS = 50;
|
|
11
|
+
|
|
12
|
+
export type RenderLoopGuardProps = {
|
|
13
|
+
children: React.ReactNode;
|
|
14
|
+
/** Render budget before the guard throws. */
|
|
15
|
+
maxRenders?: number;
|
|
16
|
+
/** Optional label included in the error for easier triage. */
|
|
17
|
+
label?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function RenderLoopGuard({
|
|
21
|
+
children,
|
|
22
|
+
maxRenders = DEFAULT_MAX_RENDERS,
|
|
23
|
+
label,
|
|
24
|
+
}: RenderLoopGuardProps): React.ReactElement {
|
|
25
|
+
const renders = React.useRef(0);
|
|
26
|
+
renders.current += 1;
|
|
27
|
+
|
|
28
|
+
if (renders.current > maxRenders) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Possible infinite render loop${label ? ` in "${label}"` : ""}: ` +
|
|
31
|
+
`exceeded ${maxRenders} renders.`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return <>{children}</>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type RenderLoopGuardOptions = Pick<RenderLoopGuardProps, "maxRenders" | "label">;
|
|
39
|
+
|
|
40
|
+
/** Wrap a UI element in the guard (inside StrictMode, to surface loops sooner). */
|
|
41
|
+
export function withRenderLoopGuard(
|
|
42
|
+
ui: React.ReactElement,
|
|
43
|
+
options?: RenderLoopGuardOptions,
|
|
44
|
+
): React.ReactElement {
|
|
45
|
+
return (
|
|
46
|
+
<React.StrictMode>
|
|
47
|
+
<RenderLoopGuard {...options}>{ui}</RenderLoopGuard>
|
|
48
|
+
</React.StrictMode>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { render, type RenderOptions, type RenderResult } from "@testing-library/react";
|
|
3
|
+
import { MemoryRouter } from "react-router-dom";
|
|
4
|
+
import { withRenderLoopGuard } from "./render-loop-guard";
|
|
5
|
+
import { AppProvider } from "../app/app-provider";
|
|
6
|
+
|
|
7
|
+
function AllProviders({ children }: { children: React.ReactNode }) {
|
|
8
|
+
return (
|
|
9
|
+
<AppProvider persist={false} defaultLocale="vi" fallbackLocale="en">
|
|
10
|
+
<MemoryRouter>{children}</MemoryRouter>
|
|
11
|
+
</AppProvider>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type RenderWithUiOptions = Omit<RenderOptions, "wrapper"> & {
|
|
16
|
+
loopGuard?: boolean | { maxRenders?: number; label?: string };
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function renderWithUi(ui: React.ReactElement, options?: RenderWithUiOptions): RenderResult {
|
|
20
|
+
const { loopGuard = true, ...renderOptions } = options ?? {};
|
|
21
|
+
const guardedUi =
|
|
22
|
+
loopGuard === false ? ui : withRenderLoopGuard(ui, loopGuard === true ? undefined : loopGuard);
|
|
23
|
+
|
|
24
|
+
return render(guardedUi, { wrapper: AllProviders, ...renderOptions });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export * from "@testing-library/react";
|
|
28
|
+
export { default as userEvent } from "@testing-library/user-event";
|
|
29
|
+
export { RenderLoopGuard, withRenderLoopGuard } from "./render-loop-guard";
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
DENSITY_CLASS,
|
|
4
|
+
DENSITY_GLOBALS,
|
|
5
|
+
FONT_SIZE_GLOBALS,
|
|
6
|
+
PRIMARY_COLOR_GLOBALS,
|
|
7
|
+
PRIMARY_COLOR_VARS,
|
|
8
|
+
themeGlobalsToClassName,
|
|
9
|
+
themeGlobalsToCssVars,
|
|
10
|
+
} from "./theme-globals";
|
|
11
|
+
|
|
12
|
+
describe("themeGlobalsToCssVars", () => {
|
|
13
|
+
describe("primaryColor presets", () => {
|
|
14
|
+
it.each(PRIMARY_COLOR_GLOBALS)(
|
|
15
|
+
"preset %s defines primary, ring, accent, foreground",
|
|
16
|
+
(preset) => {
|
|
17
|
+
const vars = PRIMARY_COLOR_VARS[preset];
|
|
18
|
+
expect(vars["--primary"]).toMatch(/^\d+ \d+% \d+%$/);
|
|
19
|
+
expect(vars["--ring"]).toMatch(/^\d+ \d+% \d+%$/);
|
|
20
|
+
expect(vars["--accent"]).toMatch(/^\d+ \d+% \d+%$/);
|
|
21
|
+
expect(vars["--accent-foreground"]).toMatch(/^\d+ \d+% \d+%$/);
|
|
22
|
+
expect(vars["--primary-foreground"]).toBe("0 0% 100%");
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
it("maps partner orange to Tailwind hsl aliases", () => {
|
|
27
|
+
expect(themeGlobalsToCssVars({ primaryColor: "partner" })).toMatchObject({
|
|
28
|
+
"--primary": "24 95% 53%",
|
|
29
|
+
"--accent": "24 95% 95%",
|
|
30
|
+
"--color-primary": "hsl(24 95% 53%)",
|
|
31
|
+
"--color-ring": "hsl(24 95% 53%)",
|
|
32
|
+
"--color-accent": "hsl(24 95% 95%)",
|
|
33
|
+
"--color-accent-foreground": "hsl(24 95% 28%)",
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("defaults to GodX navy when primaryColor omitted", () => {
|
|
38
|
+
const vars = themeGlobalsToCssVars({});
|
|
39
|
+
expect(vars["--color-primary"]).toBe("hsl(211 73% 15%)");
|
|
40
|
+
expect(vars["--color-ring"]).toBe("hsl(24 99% 46%)");
|
|
41
|
+
expect(vars["--accent"]).toBe("24 99% 95%");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("fontSize scale", () => {
|
|
46
|
+
it.each(FONT_SIZE_GLOBALS)("fontSize=%s produces stable output", (fontSize) => {
|
|
47
|
+
const vars = themeGlobalsToCssVars({ fontSize });
|
|
48
|
+
expect(vars).toBeTypeOf("object");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("scales typography for sm and lg", () => {
|
|
52
|
+
expect(themeGlobalsToCssVars({ fontSize: "sm" })["--font-size-sm"]).toBe("0.8125rem");
|
|
53
|
+
expect(themeGlobalsToCssVars({ fontSize: "lg" })["--font-size-sm"]).toBe("0.9375rem");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("default fontSize does not override --font-size-sm", () => {
|
|
57
|
+
expect(themeGlobalsToCssVars({ fontSize: "default" })).not.toHaveProperty("--font-size-sm");
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("merges fontSize + primaryColor in one object", () => {
|
|
62
|
+
const vars = themeGlobalsToCssVars({ fontSize: "lg", primaryColor: "crm" });
|
|
63
|
+
expect(vars["--font-size-base"]).toBe("1.0625rem");
|
|
64
|
+
expect(vars["--primary"]).toBe("262 83% 58%");
|
|
65
|
+
expect(vars["--color-primary"]).toBe("hsl(262 83% 58%)");
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("themeGlobalsToClassName", () => {
|
|
70
|
+
it.each(DENSITY_GLOBALS)("density %s maps to ui-density-* class", (density) => {
|
|
71
|
+
expect(themeGlobalsToClassName({ density })).toBe(DENSITY_CLASS[density]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("defaults density to ui-density-default", () => {
|
|
75
|
+
expect(themeGlobalsToClassName({})).toBe(DENSITY_CLASS.default);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
2
|
+
import type { PageDensityProp } from "../props/vocabulary/layout.prop";
|
|
3
|
+
|
|
4
|
+
/** Density toolbar → `ui-density-*` class (same as PageContainer density). */
|
|
5
|
+
export const DENSITY_GLOBALS = [
|
|
6
|
+
"compact",
|
|
7
|
+
"default",
|
|
8
|
+
"comfortable",
|
|
9
|
+
] as const satisfies readonly PageDensityProp[];
|
|
10
|
+
|
|
11
|
+
export type DensityGlobal = (typeof DENSITY_GLOBALS)[number];
|
|
12
|
+
|
|
13
|
+
export const DENSITY_CLASS: Record<DensityGlobal, string> = {
|
|
14
|
+
compact: "ui-density-compact",
|
|
15
|
+
default: "ui-density-default",
|
|
16
|
+
comfortable: "ui-density-comfortable",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const DENSITY_CLASS_NAMES = Object.values(DENSITY_CLASS);
|
|
20
|
+
|
|
21
|
+
/** Scales typography tokens — mirrors app `theme.css` overrides. */
|
|
22
|
+
export const FONT_SIZE_GLOBALS = ["sm", "default", "lg"] as const;
|
|
23
|
+
export type FontSizeGlobal = (typeof FONT_SIZE_GLOBALS)[number];
|
|
24
|
+
|
|
25
|
+
const FONT_SIZE_VARS: Record<FontSizeGlobal, Record<string, string>> = {
|
|
26
|
+
sm: {
|
|
27
|
+
"--font-size-xs": "0.6875rem",
|
|
28
|
+
"--font-size-sm": "0.8125rem",
|
|
29
|
+
"--font-size-base": "0.9375rem",
|
|
30
|
+
"--font-size-lg": "1rem",
|
|
31
|
+
"--font-size-xl": "1.125rem",
|
|
32
|
+
"--font-size-2xl": "1.25rem",
|
|
33
|
+
},
|
|
34
|
+
default: {},
|
|
35
|
+
lg: {
|
|
36
|
+
"--font-size-xs": "0.8125rem",
|
|
37
|
+
"--font-size-sm": "0.9375rem",
|
|
38
|
+
"--font-size-base": "1.0625rem",
|
|
39
|
+
"--font-size-lg": "1.1875rem",
|
|
40
|
+
"--font-size-xl": "1.3125rem",
|
|
41
|
+
"--font-size-2xl": "1.625rem",
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** Brand primary presets — HSL components (no `hsl()` wrapper), like `theme.css`. */
|
|
46
|
+
export const PRIMARY_COLOR_GLOBALS = ["brand", "crm", "logistics", "partner", "slate"] as const;
|
|
47
|
+
export type PrimaryColorGlobal = (typeof PRIMARY_COLOR_GLOBALS)[number];
|
|
48
|
+
|
|
49
|
+
export const PRIMARY_COLOR_LABELS: Record<PrimaryColorGlobal, string> = {
|
|
50
|
+
brand: "GodX Navy",
|
|
51
|
+
crm: "CRM Violet",
|
|
52
|
+
logistics: "Logistics Teal",
|
|
53
|
+
partner: "Partner Orange",
|
|
54
|
+
slate: "HQ Slate",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Primary, focus ring, and tinted accent mirror service theme.css overrides. */
|
|
58
|
+
export const PRIMARY_COLOR_VARS: Record<PrimaryColorGlobal, Record<string, string>> = {
|
|
59
|
+
brand: {
|
|
60
|
+
"--primary": "211 73% 15%",
|
|
61
|
+
"--primary-foreground": "0 0% 100%",
|
|
62
|
+
"--ring": "24 99% 46%",
|
|
63
|
+
"--accent": "24 99% 95%",
|
|
64
|
+
"--accent-foreground": "24 99% 28%",
|
|
65
|
+
},
|
|
66
|
+
crm: {
|
|
67
|
+
"--primary": "262 83% 58%",
|
|
68
|
+
"--primary-foreground": "0 0% 100%",
|
|
69
|
+
"--ring": "262 83% 58%",
|
|
70
|
+
"--accent": "262 83% 96%",
|
|
71
|
+
"--accent-foreground": "262 83% 28%",
|
|
72
|
+
},
|
|
73
|
+
logistics: {
|
|
74
|
+
"--primary": "173 80% 36%",
|
|
75
|
+
"--primary-foreground": "0 0% 100%",
|
|
76
|
+
"--ring": "173 80% 36%",
|
|
77
|
+
"--accent": "173 80% 94%",
|
|
78
|
+
"--accent-foreground": "173 80% 22%",
|
|
79
|
+
},
|
|
80
|
+
partner: {
|
|
81
|
+
"--primary": "24 95% 53%",
|
|
82
|
+
"--primary-foreground": "0 0% 100%",
|
|
83
|
+
"--ring": "24 95% 53%",
|
|
84
|
+
"--accent": "24 95% 95%",
|
|
85
|
+
"--accent-foreground": "24 95% 28%",
|
|
86
|
+
},
|
|
87
|
+
slate: {
|
|
88
|
+
"--primary": "215 25% 27%",
|
|
89
|
+
"--primary-foreground": "0 0% 100%",
|
|
90
|
+
"--ring": "215 25% 27%",
|
|
91
|
+
"--accent": "215 25% 94%",
|
|
92
|
+
"--accent-foreground": "215 25% 20%",
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export type ThemeGlobals = {
|
|
97
|
+
density?: DensityGlobal;
|
|
98
|
+
fontSize?: FontSizeGlobal;
|
|
99
|
+
primaryColor?: PrimaryColorGlobal;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
function hslFromComponents(components: string): string {
|
|
103
|
+
return `hsl(${components})`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** CSS vars for :root — includes Tailwind @theme `--color-*` aliases. */
|
|
107
|
+
export function themeGlobalsToCssVars(globals: ThemeGlobals): Record<string, string> {
|
|
108
|
+
const fontSize = globals.fontSize ?? "default";
|
|
109
|
+
const primaryColor = globals.primaryColor ?? "brand";
|
|
110
|
+
const primary = PRIMARY_COLOR_VARS[primaryColor];
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
...FONT_SIZE_VARS[fontSize],
|
|
114
|
+
"--primary": primary["--primary"],
|
|
115
|
+
"--primary-foreground": primary["--primary-foreground"],
|
|
116
|
+
"--ring": primary["--ring"],
|
|
117
|
+
"--accent": primary["--accent"],
|
|
118
|
+
"--accent-foreground": primary["--accent-foreground"],
|
|
119
|
+
"--color-primary": hslFromComponents(primary["--primary"]),
|
|
120
|
+
"--color-primary-foreground": hslFromComponents(primary["--primary-foreground"]),
|
|
121
|
+
"--color-ring": hslFromComponents(primary["--ring"]),
|
|
122
|
+
"--color-accent": hslFromComponents(primary["--accent"]),
|
|
123
|
+
"--color-accent-foreground": hslFromComponents(primary["--accent-foreground"]),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function themeGlobalsToStyle(globals: ThemeGlobals): CSSProperties {
|
|
128
|
+
return themeGlobalsToCssVars(globals);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function themeGlobalsToClassName(globals: ThemeGlobals): string {
|
|
132
|
+
const density = globals.density ?? "default";
|
|
133
|
+
return DENSITY_CLASS[density];
|
|
134
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { render, type RenderOptions, type RenderResult } from "@testing-library/react";
|
|
3
|
+
import { MemoryRouter } from "react-router-dom";
|
|
4
|
+
import { AppProvider } from "../app/app-provider";
|
|
5
|
+
import type { AppProviderProp } from "../props/components/app.prop";
|
|
6
|
+
import {
|
|
7
|
+
DENSITY_CLASS,
|
|
8
|
+
themeGlobalsToCssVars,
|
|
9
|
+
type FontSizeGlobal,
|
|
10
|
+
type PrimaryColorGlobal,
|
|
11
|
+
} from "./theme-globals";
|
|
12
|
+
import { densityClass } from "../lib/variants";
|
|
13
|
+
import type { PageDensityProp } from "../props/vocabulary/layout.prop";
|
|
14
|
+
|
|
15
|
+
export type ThemeTestOptions = {
|
|
16
|
+
density?: PageDensityProp;
|
|
17
|
+
fontSize?: FontSizeGlobal;
|
|
18
|
+
primaryColor?: PrimaryColorGlobal;
|
|
19
|
+
app?: Partial<AppProviderProp>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type RenderWithThemeOptions = Omit<RenderOptions, "wrapper"> &
|
|
23
|
+
ThemeTestOptions & {
|
|
24
|
+
initialEntries?: string[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Renders UI under density + brand tokens — mirrors preview theme globals. */
|
|
28
|
+
export function ThemeTestRoot({
|
|
29
|
+
children,
|
|
30
|
+
density = "default",
|
|
31
|
+
fontSize = "default",
|
|
32
|
+
primaryColor = "brand",
|
|
33
|
+
app,
|
|
34
|
+
}: React.PropsWithChildren<ThemeTestOptions>) {
|
|
35
|
+
const style = themeGlobalsToCssVars({ density, fontSize, primaryColor }) as React.CSSProperties;
|
|
36
|
+
return (
|
|
37
|
+
<div className={densityClass[density]} style={style}>
|
|
38
|
+
<AppProvider persist={false} defaultLocale="vi" fallbackLocale="en" {...app}>
|
|
39
|
+
<MemoryRouter>{children}</MemoryRouter>
|
|
40
|
+
</AppProvider>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function renderWithTheme(
|
|
46
|
+
ui: React.ReactElement,
|
|
47
|
+
options?: RenderWithThemeOptions,
|
|
48
|
+
): RenderResult {
|
|
49
|
+
const {
|
|
50
|
+
density = "default",
|
|
51
|
+
fontSize = "default",
|
|
52
|
+
primaryColor = "brand",
|
|
53
|
+
app,
|
|
54
|
+
...renderOptions
|
|
55
|
+
} = options ?? {};
|
|
56
|
+
|
|
57
|
+
return render(ui, {
|
|
58
|
+
wrapper: ({ children }) => (
|
|
59
|
+
<ThemeTestRoot density={density} fontSize={fontSize} primaryColor={primaryColor} app={app}>
|
|
60
|
+
{children}
|
|
61
|
+
</ThemeTestRoot>
|
|
62
|
+
),
|
|
63
|
+
...renderOptions,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { DENSITY_CLASS, densityClass };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/* Service theme — the ONLY file apps may use to customize design tokens.
|
|
2
|
+
* Copy to: apps/<your-app>/src/theme.css
|
|
3
|
+
* Import this file once in main.tsx (instead of @godxjp/ui/styles directly). */
|
|
4
|
+
|
|
5
|
+
@import "@godxjp/ui/styles";
|
|
6
|
+
|
|
7
|
+
/* Override tokens below. Do NOT add component styles or Tailwind utilities here. */
|
|
8
|
+
:root {
|
|
9
|
+
/* Brand — pair primary with a focus ring and tinted accent: */
|
|
10
|
+
/* --primary: 211 73% 15%; */
|
|
11
|
+
/* --primary-foreground: 0 0% 100%; */
|
|
12
|
+
/* --ring: 24 99% 46%; */
|
|
13
|
+
/* --accent: 24 99% 95%; */
|
|
14
|
+
/* --accent-foreground: 24 99% 28%; */
|
|
15
|
+
|
|
16
|
+
/* Semantic status (optional — defaults in base.css): */
|
|
17
|
+
/* --success: 146 40% 58%; */
|
|
18
|
+
/* --warning: 44 100% 49%; */
|
|
19
|
+
/* --info: 223 42% 50%; */
|
|
20
|
+
/* --attention: 24 99% 46%; */
|
|
21
|
+
|
|
22
|
+
/* Tracking-code identity (optional — do not reuse for status): */
|
|
23
|
+
/* --tracking-internal: 211 73% 15%; */
|
|
24
|
+
/* --tracking-seller: 44 5% 42%; */
|
|
25
|
+
/* --tracking-yamato: 24 99% 46%; */
|
|
26
|
+
|
|
27
|
+
/* Layout density (optional): */
|
|
28
|
+
/* --space-page-x: var(--space-8); */
|
|
29
|
+
|
|
30
|
+
/* Primitive component tokens — override tokens only, no selector CSS: */
|
|
31
|
+
/* --card-space-inset: var(--space-section-active); */
|
|
32
|
+
/* --card-space-header-y: var(--space-stack-sm); */
|
|
33
|
+
/* --card-space-body-y: var(--space-section-active); */
|
|
34
|
+
/* --card-space-footer-y: var(--space-stack-sm); */
|
|
35
|
+
/* --card-title-font-size: var(--font-size-base); */
|
|
36
|
+
/* --card-header-background-alpha: 0.55; */
|
|
37
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* @godxjp/ui — token manifest.
|
|
2
|
+
* Services MUST NOT edit package token files directly.
|
|
3
|
+
* App-specific overrides live in app theme.css after importing @godxjp/ui/styles.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
@import "./foundation.css";
|
|
7
|
+
@import "./primitives/layout.css";
|
|
8
|
+
@import "./primitives/control.css";
|
|
9
|
+
@import "./primitives/card.css";
|
|
10
|
+
@import "./primitives/table.css";
|
|
11
|
+
@import "./primitives/feedback.css";
|
|
12
|
+
@import "./primitives/badge.css";
|
|
13
|
+
@import "./primitives/navigation.css";
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/* Foundation tokens: color, typography, raw spacing, ratio, radius, shadow. */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
/* Color system (HSL components) — dxs-kintai: warm hue-60 neutral spine,
|
|
5
|
+
* SmartHR blue primary, 和色/wa-iro semantics. */
|
|
6
|
+
--background: 60 33% 99%; /* #fdfdfb warm off-white */
|
|
7
|
+
--foreground: 48 8% 13%; /* #23221e SmartHR TEXT_BLACK */
|
|
8
|
+
--card: 60 33% 99%;
|
|
9
|
+
--card-foreground: 48 8% 13%;
|
|
10
|
+
--popover: 60 33% 99%;
|
|
11
|
+
--popover-foreground: 48 8% 13%;
|
|
12
|
+
--primary: 204 100% 39%; /* SmartHR MAIN #0077c7 */
|
|
13
|
+
--primary-foreground: 60 33% 99%;
|
|
14
|
+
--secondary: 45 15% 95%; /* #f4f3f0 */
|
|
15
|
+
--secondary-foreground: 48 8% 13%;
|
|
16
|
+
--muted: 45 15% 95%;
|
|
17
|
+
--muted-foreground: 44 5% 42%; /* #706d65 SmartHR TEXT_GREY */
|
|
18
|
+
--accent: 40 13% 91%; /* #ebe9e5 */
|
|
19
|
+
--accent-foreground: 48 8% 13%;
|
|
20
|
+
--destructive: 357 64% 44%; /* 茜 #b7282e */
|
|
21
|
+
--destructive-foreground: 60 33% 99%;
|
|
22
|
+
--border: 30 7% 83%; /* #d6d3d0 SmartHR BORDER */
|
|
23
|
+
--input: 30 7% 83%;
|
|
24
|
+
--ring: 204 100% 39%;
|
|
25
|
+
--success: 146 40% 58%; /* 若竹 #68be8d */
|
|
26
|
+
--success-foreground: 60 33% 99%;
|
|
27
|
+
--warning: 44 100% 49%; /* 山吹 #f8b500 */
|
|
28
|
+
--warning-foreground: 48 8% 13%;
|
|
29
|
+
--info: 221 40% 50%; /* 群青 #4c6cb3 */
|
|
30
|
+
--info-foreground: 60 33% 99%;
|
|
31
|
+
--attention: 25 99% 46%; /* 朱 #eb6101 */
|
|
32
|
+
--attention-foreground: 60 33% 99%;
|
|
33
|
+
--tracking-internal: 204 100% 39%;
|
|
34
|
+
--tracking-seller: 44 5% 42%;
|
|
35
|
+
--tracking-yamato: 25 99% 46%;
|
|
36
|
+
|
|
37
|
+
/* 和色 (wa-iro) — traditional Japanese accent palette for charts, tags,
|
|
38
|
+
* decoration. Decorative hex (NOT semantic): never remap a wa-iro to a
|
|
39
|
+
* semantic role beyond the five canonical mappings used above. */
|
|
40
|
+
--wa-ai: #165e83; /* 藍 indigo */
|
|
41
|
+
--wa-gunjo: #4c6cb3; /* 群青 ultramarine */
|
|
42
|
+
--wa-ruri: #1e50a2; /* 瑠璃 lapis */
|
|
43
|
+
--wa-kon: #223a70; /* 紺 navy */
|
|
44
|
+
--wa-wakatake: #68be8d; /* 若竹 young bamboo */
|
|
45
|
+
--wa-moegi: #006e54; /* 萌葱 spring green */
|
|
46
|
+
--wa-yamabuki: #f8b500; /* 山吹 mountain yellow */
|
|
47
|
+
--wa-shu: #eb6101; /* 朱 vermilion */
|
|
48
|
+
--wa-akane: #b7282e; /* 茜 madder */
|
|
49
|
+
--wa-enji: #b94047; /* 臙脂 cochineal */
|
|
50
|
+
--wa-sakura: #fef4f4; /* 桜 cherry (soft bg) */
|
|
51
|
+
--wa-sumi: #595857; /* 墨 warm ink */
|
|
52
|
+
--wa-nezu: #949495; /* 鼠 mouse-grey */
|
|
53
|
+
|
|
54
|
+
/* Neutral + blue ramps (data viz, fine UI shading). */
|
|
55
|
+
--gray-50: #f9fafb;
|
|
56
|
+
--gray-100: #f3f4f6;
|
|
57
|
+
--gray-200: #e5e7eb;
|
|
58
|
+
--gray-300: #d1d5db;
|
|
59
|
+
--gray-400: #9ca3af;
|
|
60
|
+
--gray-500: #6b7280;
|
|
61
|
+
--gray-600: #4b5563;
|
|
62
|
+
--gray-700: #374151;
|
|
63
|
+
--gray-800: #1f2937;
|
|
64
|
+
--gray-900: #111827;
|
|
65
|
+
--blue-50: #eff6ff;
|
|
66
|
+
--blue-100: #dbeafe;
|
|
67
|
+
--blue-500: #3b82f6;
|
|
68
|
+
--blue-600: #2563eb;
|
|
69
|
+
--blue-700: #1d4ed8;
|
|
70
|
+
|
|
71
|
+
/* Shape and elevation */
|
|
72
|
+
--radius: 0.375rem;
|
|
73
|
+
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
|
74
|
+
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
|
|
75
|
+
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1);
|
|
76
|
+
|
|
77
|
+
/* Typography scale — M PLUS 2 (JIS L1+L2 kanji, Vietnamese subset), one notch
|
|
78
|
+
* larger than the JP-dense base for comfortable reading (body 14px). */
|
|
79
|
+
--font-family-sans:
|
|
80
|
+
"M PLUS 2", "Be Vietnam Pro", "Hiragino Sans", "Noto Sans JP", -apple-system,
|
|
81
|
+
BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, sans-serif;
|
|
82
|
+
--font-family-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
83
|
+
--font-size-xs: 0.8125rem; /* 13px */
|
|
84
|
+
--font-size-sm: 0.875rem; /* 14px — body */
|
|
85
|
+
--font-size-base: 0.9375rem; /* 15px */
|
|
86
|
+
--font-size-lg: 1.1875rem; /* 19px */
|
|
87
|
+
--font-size-xl: 1.375rem; /* 22px */
|
|
88
|
+
--font-size-2xl: 1.625rem; /* 26px */
|
|
89
|
+
--font-weight-normal: 400;
|
|
90
|
+
--font-weight-medium: 500;
|
|
91
|
+
--font-weight-semibold: 600;
|
|
92
|
+
--line-height-tight: 1.25;
|
|
93
|
+
--line-height-normal: 1.5;
|
|
94
|
+
--line-height-body: 1.7;
|
|
95
|
+
--letter-spacing-tight: 0;
|
|
96
|
+
|
|
97
|
+
/* Raw spacing scale */
|
|
98
|
+
--space-0: 0;
|
|
99
|
+
--space-1: 0.25rem;
|
|
100
|
+
--space-2: 0.5rem;
|
|
101
|
+
--space-3: 0.75rem;
|
|
102
|
+
--space-4: 1rem;
|
|
103
|
+
--space-5: 1.25rem;
|
|
104
|
+
--space-6: 1.5rem;
|
|
105
|
+
--space-8: 2rem;
|
|
106
|
+
--space-10: 2.5rem;
|
|
107
|
+
--space-12: 3rem;
|
|
108
|
+
|
|
109
|
+
/* Golden ratio scale */
|
|
110
|
+
--ratio-phi: 1.6180339887;
|
|
111
|
+
--phi-unit: var(--space-4);
|
|
112
|
+
--phi-n2: calc(var(--phi-unit) / var(--ratio-phi) / var(--ratio-phi));
|
|
113
|
+
--phi-n1: calc(var(--phi-unit) / var(--ratio-phi));
|
|
114
|
+
--phi-0: var(--phi-unit);
|
|
115
|
+
--phi-p1: calc(var(--phi-unit) * var(--ratio-phi));
|
|
116
|
+
--phi-p2: calc(var(--phi-p1) * var(--ratio-phi));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.dark {
|
|
120
|
+
/* Warm-tinted dark spine — keeps the hue-60 character at low lightness. */
|
|
121
|
+
--background: 48 9% 9%;
|
|
122
|
+
--foreground: 60 20% 96%;
|
|
123
|
+
--card: 48 8% 12%;
|
|
124
|
+
--card-foreground: 60 20% 96%;
|
|
125
|
+
--popover: 48 8% 12%;
|
|
126
|
+
--popover-foreground: 60 20% 96%;
|
|
127
|
+
--primary: 204 90% 60%; /* SmartHR blue lifted for dark contrast */
|
|
128
|
+
--primary-foreground: 48 9% 9%;
|
|
129
|
+
--secondary: 45 6% 18%;
|
|
130
|
+
--secondary-foreground: 60 20% 96%;
|
|
131
|
+
--muted: 45 6% 18%;
|
|
132
|
+
--muted-foreground: 44 6% 64%;
|
|
133
|
+
--accent: 45 7% 22%;
|
|
134
|
+
--accent-foreground: 60 20% 96%;
|
|
135
|
+
--destructive: 357 55% 52%;
|
|
136
|
+
--destructive-foreground: 60 20% 96%;
|
|
137
|
+
--border: 45 6% 22%;
|
|
138
|
+
--input: 45 6% 22%;
|
|
139
|
+
--ring: 204 90% 60%;
|
|
140
|
+
--success: 146 45% 55%;
|
|
141
|
+
--success-foreground: 48 9% 9%;
|
|
142
|
+
--warning: 44 95% 55%;
|
|
143
|
+
--warning-foreground: 48 9% 9%;
|
|
144
|
+
--info: 221 55% 62%;
|
|
145
|
+
--info-foreground: 48 9% 9%;
|
|
146
|
+
--attention: 25 95% 56%;
|
|
147
|
+
--attention-foreground: 48 9% 9%;
|
|
148
|
+
--tracking-internal: 204 90% 60%;
|
|
149
|
+
--tracking-seller: 44 6% 64%;
|
|
150
|
+
--tracking-yamato: 25 95% 56%;
|
|
151
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* Badge primitive tokens. */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--space-badge-gap: var(--space-inline-xs);
|
|
5
|
+
--space-badge-x: var(--space-2);
|
|
6
|
+
--space-badge-y: var(--space-1);
|
|
7
|
+
--code-badge-height: 1.75rem;
|
|
8
|
+
--code-badge-gap: var(--space-inline-sm);
|
|
9
|
+
--code-badge-padding-x: var(--space-2);
|
|
10
|
+
--code-badge-bg-alpha: 0.055;
|
|
11
|
+
--code-badge-border-alpha: 0.28;
|
|
12
|
+
--code-badge-separator-alpha: 0.22;
|
|
13
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/* Card primitive tokens: card chrome derives from foundation + layout primitives. */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--card-space-inset: var(--space-section-active);
|
|
5
|
+
--card-space-header-y: var(--space-stack-sm);
|
|
6
|
+
--card-space-body-y: var(--space-section-active);
|
|
7
|
+
--card-space-footer-y: var(--space-stack-sm);
|
|
8
|
+
--card-space-gap: var(--space-stack-xs);
|
|
9
|
+
--card-title-font-size: var(--font-size-base);
|
|
10
|
+
--card-title-line-height: var(--line-height-tight);
|
|
11
|
+
--card-title-font-weight: var(--font-weight-semibold);
|
|
12
|
+
--card-description-font-size: var(--font-size-sm);
|
|
13
|
+
--card-description-line-height: var(--line-height-normal);
|
|
14
|
+
--card-background: var(--card);
|
|
15
|
+
--card-foreground-token: var(--card-foreground);
|
|
16
|
+
--card-border-token: var(--border);
|
|
17
|
+
--card-header-background: var(--muted);
|
|
18
|
+
--card-header-background-alpha: 0.55;
|
|
19
|
+
--card-radius: var(--radius);
|
|
20
|
+
--card-stat-label-font-size: var(--font-size-xs);
|
|
21
|
+
--card-stat-label-font-weight: var(--font-weight-medium);
|
|
22
|
+
--card-stat-label-letter-spacing: 0.04em;
|
|
23
|
+
--card-stat-value-font-size: 1.625rem;
|
|
24
|
+
--card-stat-value-line-height: 1.1;
|
|
25
|
+
--card-stat-value-font-weight: var(--font-weight-semibold);
|
|
26
|
+
--card-stat-hint-font-size: var(--font-size-xs);
|
|
27
|
+
--card-stat-gap: var(--space-stack-xs);
|
|
28
|
+
--card-stat-icon-size: 2.25rem;
|
|
29
|
+
}
|