@godxjp/ui 19.3.1 → 19.4.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/components/data-display/badge.d.ts +9 -1
- package/dist/components/data-display/badge.js +3 -2
- package/dist/components/data-display/index.d.ts +2 -0
- package/dist/components/data-display/index.js +2 -0
- package/dist/components/data-display/popover.d.ts +13 -1
- package/dist/components/data-display/popover.js +3 -1
- package/dist/components/data-display/table.d.ts +19 -2
- package/dist/components/data-display/table.js +4 -1
- package/dist/components/data-display/timeline-grid.d.ts +18 -0
- package/dist/components/data-display/timeline-grid.js +222 -0
- package/dist/components/data-entry/cascader.js +4 -6
- package/dist/components/data-entry/checkbox.js +1 -1
- package/dist/components/data-entry/search-select.js +5 -7
- package/dist/components/data-entry/select.js +32 -28
- package/dist/components/data-entry/tree-select.js +4 -6
- package/dist/components/general/inert-background.d.ts +5 -0
- package/dist/components/general/inert-background.js +48 -0
- package/dist/components/layout/auth-shell.d.ts +4 -2
- package/dist/components/layout/auth-shell.js +7 -1
- package/dist/components/layout/centered-shell.d.ts +2 -2
- package/dist/components/layout/flex.d.ts +1 -1
- package/dist/components/layout/flex.js +2 -1
- package/dist/components/layout/index.d.ts +3 -0
- package/dist/components/layout/index.js +2 -0
- package/dist/components/layout/mobile-shell.d.ts +21 -0
- package/dist/components/layout/mobile-shell.js +43 -0
- package/dist/components/layout/sidebar-link.d.ts +1 -1
- package/dist/components/navigation/context-menu.js +13 -9
- package/dist/components/navigation/dropdown-menu.js +17 -13
- package/dist/inertia/index.d.ts +1 -1
- package/dist/lib/variants.d.ts +3 -3
- package/dist/lib/variants.js +1 -0
- package/dist/props/components/data-display.prop.d.ts +65 -0
- package/dist/props/components/data-entry.prop.d.ts +1 -1
- package/dist/props/components/layout.prop.d.ts +93 -6
- package/dist/props/registry.d.ts +109 -2
- package/dist/props/registry.js +143 -0
- package/dist/props/vocabulary/data.prop.d.ts +6 -0
- package/dist/props/vocabulary/index.d.ts +3 -3
- package/dist/props/vocabulary/layout.prop.d.ts +17 -2
- package/dist/props/vocabulary/shared.prop.d.ts +7 -0
- package/dist/styles/control.css +18 -1
- package/dist/styles/data-display-layout.css +167 -0
- package/dist/styles/layout.css +7 -0
- package/dist/styles/shell-layout.css +148 -0
- package/dist/styles/table-layout.css +23 -4
- package/dist/tokens/components/control.css +1 -0
- package/dist/tokens/components/data-display.css +20 -0
- package/dist/tokens/components/shell.css +22 -0
- package/dist/tokens/components/table.css +2 -0
- package/docs/SPACING.md +19 -6
- package/docs/charts/compact-bar-trend.tsx +5 -6
- package/docs/data-display/avatar.tsx +7 -9
- package/docs/data-display/badge.tsx +1 -1
- package/docs/data-display/card/index.tsx +6 -4
- package/docs/data-display/data-table/examples/approval-queue.tsx +1 -1
- package/docs/data-display/data-table/index.tsx +1 -1
- package/docs/data-display/permission-matrix.tsx +2 -2
- package/docs/data-display/scroll-area.tsx +1 -1
- package/docs/data-display/table.tsx +1 -1
- package/docs/data-display/timeline-grid.tsx +119 -0
- package/docs/data-entry/branch-scope-picker.tsx +1 -1
- package/docs/data-entry/input-otp.tsx +1 -1
- package/docs/data-entry/textarea.tsx +1 -1
- package/docs/data-entry/toggle-count.tsx +1 -1
- package/docs/feedback/alert.tsx +1 -1
- package/docs/feedback/banner.tsx +1 -1
- package/docs/foundation/email-tokens.tsx +1 -1
- package/docs/general/activity.tsx +1 -1
- package/docs/layout/auth-account-summary.tsx +1 -1
- package/docs/layout/auth-recovery/examples/mobile-390.tsx +1 -1
- package/docs/layout/auth-recovery/examples/password-recovery.tsx +0 -2
- package/docs/layout/auth-shell-context.tsx +1 -1
- package/docs/layout/auth-shell-device.tsx +2 -2
- package/docs/layout/auth-shell-registration.tsx +1 -1
- package/docs/layout/auth-shell.tsx +1 -1
- package/docs/layout/master-detail.tsx +2 -2
- package/docs/layout/mobile-shell.tsx +101 -0
- package/docs/layout/page-container.tsx +6 -6
- package/docs/layout/responsive-grid.tsx +2 -2
- package/docs/layout/separator.tsx +2 -2
- package/docs/layout/service-role-panel.tsx +1 -1
- package/docs/layout/sidebar.tsx +6 -6
- package/docs/layout/split-pane.tsx +1 -1
- package/docs/layout/topbar.tsx +2 -8
- package/docs/navigation/filter-bar.tsx +2 -2
- package/docs/navigation/pagination.tsx +2 -2
- package/docs/navigation/steps.tsx +1 -1
- package/docs/navigation/tabs.tsx +1 -1
- package/docs/showcase/acme-portal.tsx +11 -12
- package/docs/showcase/acme-website.tsx +7 -7
- package/docs/showcase/case1-warehouse-dashboard.tsx +11 -12
- package/docs/showcase/case2-employee-me.tsx +10 -16
- package/docs/showcase/case4-login.tsx +144 -156
- package/docs/showcase/case5-shift-calendar.tsx +181 -280
- package/docs/showcase/case6-agency-handy.tsx +446 -443
- package/docs/showcase/futurelastic-web.tsx +9 -8
- package/docs/showcase/org-switcher.tsx +18 -11
- package/docs/showcase/permission-matrix.tsx +13 -13
- package/docs/showcase/public-landing.tsx +1 -1
- package/docs/showcase/settings-account-sections.tsx +2 -2
- package/docs/showcase/settings-security-mfa.tsx +3 -3
- package/docs/showcase/table-approval-queue.tsx +1 -1
- package/docs/showcase/table-bulk-actions.tsx +49 -52
- package/docs/showcase/table-conditional-format.tsx +1 -1
- package/docs/showcase/table-crud-list.tsx +1 -1
- package/docs/showcase/table-expandable-rows.tsx +101 -98
- package/docs/showcase/table-filter-chips.tsx +78 -66
- package/docs/showcase/table-footer-totals.tsx +23 -19
- package/docs/showcase/table-grouped-subtotals.tsx +7 -4
- package/docs/showcase/table-master-detail.tsx +12 -14
- package/docs/showcase/table-states.tsx +34 -29
- package/docs/showcase/table-sticky-columns.tsx +3 -2
- package/docs/showcase/table-tree-rows.tsx +22 -15
- package/docs/showcase/table-view-tabs.tsx +12 -14
- package/package.json +7 -6
|
@@ -14,6 +14,14 @@ declare const badgeVariants: (props?: ({
|
|
|
14
14
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
15
15
|
export interface BadgeProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children">, Omit<VariantProps<typeof badgeVariants>, "variant" | "shape"> {
|
|
16
16
|
variant?: BadgeVariant | null;
|
|
17
|
+
/**
|
|
18
|
+
* Render element — `div` (default) or `span` when the chip sits in a PHRASING context and a
|
|
19
|
+
* `<div>` would be invalid HTML: inside a `TabsTrigger`/`PopoverTrigger`/`Button` (all render a
|
|
20
|
+
* `<button>`, whose content model is phrasing content only), inside a `<label>` or a `<p>`.
|
|
21
|
+
* It swaps the TAG only — the chip's own `inline-flex` box, icon and label are unchanged, and
|
|
22
|
+
* both the icon `<svg>` and the label `<span>` are already phrasing content (gh#354).
|
|
23
|
+
*/
|
|
24
|
+
as?: "div" | "span";
|
|
17
25
|
/** Corner shape — `default` (badge radius) · `pill` (fully rounded) · `sharp` (square). */
|
|
18
26
|
shape?: ShapeProp | null;
|
|
19
27
|
tone?: BadgeTone | null;
|
|
@@ -39,7 +47,7 @@ export interface BadgeProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "
|
|
|
39
47
|
status?: string;
|
|
40
48
|
children?: React.ReactNode;
|
|
41
49
|
}
|
|
42
|
-
export declare function Badge({ className, variant, shape, tone, color, icon, status, style, children, ...props }: BadgeProps): React.JSX.Element;
|
|
50
|
+
export declare function Badge({ as: Element, className, variant, shape, tone, color, icon, status, style, children, ...props }: BadgeProps): React.JSX.Element;
|
|
43
51
|
/**
|
|
44
52
|
* Status-aware badge with the shared domain-status-to-tone mapping. `Badge` remains the
|
|
45
53
|
* general-purpose primitive.
|
|
@@ -62,7 +62,7 @@ const badgeVariants = cva(
|
|
|
62
62
|
* `BadgeVariant` union: every fill, border and text utility has to stay
|
|
63
63
|
* off the element so badge-layout.css's `[data-tinted]` rule is
|
|
64
64
|
* reachable (a utility beats a components-layer rule whatever the
|
|
65
|
-
* specificity
|
|
65
|
+
* specificity). Selected by the `color` prop, never by hand. */
|
|
66
66
|
tinted: ""
|
|
67
67
|
},
|
|
68
68
|
// Corner shape — default inherits the badge radius token; pill/sharp override via the tokens.
|
|
@@ -86,6 +86,7 @@ const badgeToneClass = {
|
|
|
86
86
|
neutral: cn("border-transparent", toneNeutralClass)
|
|
87
87
|
};
|
|
88
88
|
function Badge({
|
|
89
|
+
as: Element = "div",
|
|
89
90
|
className,
|
|
90
91
|
variant,
|
|
91
92
|
shape,
|
|
@@ -104,7 +105,7 @@ function Badge({
|
|
|
104
105
|
const resolvedChildren = children ?? (status ? status in STATUS_MAP ? t(`status.${status}`) : status : void 0);
|
|
105
106
|
const tinted = color != null && color !== "";
|
|
106
107
|
return /* @__PURE__ */ jsxs(
|
|
107
|
-
|
|
108
|
+
Element,
|
|
108
109
|
{
|
|
109
110
|
"data-slot": "badge",
|
|
110
111
|
"data-tone": tinted ? void 0 : resolvedTone,
|
|
@@ -28,6 +28,8 @@ export { TreeList } from "./tree-list.js";
|
|
|
28
28
|
export type { TreeListItem, TreeListProps } from "./tree-list.js";
|
|
29
29
|
export { Timeline } from "./timeline.js";
|
|
30
30
|
export type { TimelineItem, TimelineProps } from "./timeline.js";
|
|
31
|
+
export { TimelineGrid } from "./timeline-grid.js";
|
|
32
|
+
export type { TimelineGridColumnProp, TimelineGridEventProp, TimelineGridProp, TimelineGridProps, } from "./timeline-grid.js";
|
|
31
33
|
export { Popover, PopoverAnchor, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, } from "./popover.js";
|
|
32
34
|
export { ScrollArea, ScrollBar } from "./scroll-area.js";
|
|
33
35
|
export { Collapsible, CollapsibleTrigger, CollapsibleContent } from "./collapsible.js";
|
|
@@ -29,6 +29,7 @@ import { CodeBlock } from "./code-block.js";
|
|
|
29
29
|
import { Prose } from "./prose.js";
|
|
30
30
|
import { TreeList } from "./tree-list.js";
|
|
31
31
|
import { Timeline } from "./timeline.js";
|
|
32
|
+
import { TimelineGrid } from "./timeline-grid.js";
|
|
32
33
|
import {
|
|
33
34
|
Popover,
|
|
34
35
|
PopoverAnchor,
|
|
@@ -113,6 +114,7 @@ export {
|
|
|
113
114
|
TableHeader,
|
|
114
115
|
TableRow,
|
|
115
116
|
Timeline,
|
|
117
|
+
TimelineGrid,
|
|
116
118
|
TreeList,
|
|
117
119
|
flexRender,
|
|
118
120
|
useCarousel
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
3
|
+
import type { FlushProp } from "../../props/vocabulary/index.js";
|
|
3
4
|
export declare function Popover(props: React.ComponentProps<typeof PopoverPrimitive.Root>): React.JSX.Element;
|
|
4
5
|
export declare function PopoverTrigger(props: React.ComponentProps<typeof PopoverPrimitive.Trigger>): React.JSX.Element;
|
|
5
6
|
export declare function PopoverAnchor(props: React.ComponentProps<typeof PopoverPrimitive.Anchor>): React.JSX.Element;
|
|
6
|
-
|
|
7
|
+
/**
|
|
8
|
+
* The panel's CONTENT owns its inset — a Command list, a menu or a table that must run edge to
|
|
9
|
+
* edge and draw its own separators across the full width. The popover drops its own padding by
|
|
10
|
+
* zeroing `--popover-space-inset` ON THE PANEL, so the inset stays one token (a service that
|
|
11
|
+
* retunes `--popover-space-inset` still owns every padded popover) and no consumer has to reach
|
|
12
|
+
* for a zero-padding utility, which no service theme can reach (gh#354).
|
|
13
|
+
*/
|
|
14
|
+
type PopoverContentFlush = {
|
|
15
|
+
flush?: FlushProp;
|
|
16
|
+
};
|
|
17
|
+
export declare const PopoverContent: React.ForwardRefExoticComponent<Omit<PopoverPrimitive.PopoverContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & PopoverContentFlush & React.RefAttributes<HTMLDivElement>>;
|
|
7
18
|
export declare const PopoverHeader: ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => React.JSX.Element;
|
|
8
19
|
export declare const PopoverTitle: ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => React.JSX.Element;
|
|
9
20
|
export declare const PopoverDescription: ({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) => React.JSX.Element;
|
|
21
|
+
export {};
|
|
@@ -12,13 +12,15 @@ function PopoverTrigger(props) {
|
|
|
12
12
|
function PopoverAnchor(props) {
|
|
13
13
|
return /* @__PURE__ */ jsx(PopoverPrimitive.Anchor, { "data-slot": "popover-anchor", ...props });
|
|
14
14
|
}
|
|
15
|
-
const PopoverContent = React.forwardRef(({ className, align = "center", sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx(
|
|
15
|
+
const PopoverContent = React.forwardRef(({ className, align = "center", sideOffset = 4, flush, style, ...props }, ref) => /* @__PURE__ */ jsx(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx(
|
|
16
16
|
PopoverPrimitive.Content,
|
|
17
17
|
{
|
|
18
18
|
ref,
|
|
19
19
|
"data-slot": "popover-content",
|
|
20
|
+
"data-flush": flush ? "" : void 0,
|
|
20
21
|
align,
|
|
21
22
|
sideOffset,
|
|
23
|
+
style: flush ? { ...style, "--popover-space-inset": "0" } : style,
|
|
22
24
|
className: cn(
|
|
23
25
|
"ui-popover-content origin-[var(--radix-popover-content-transform-origin)]",
|
|
24
26
|
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import type { BreakpointProp, TableColumnPriorityProp, TablePresetProp } from "../../props/vocabulary/index.js";
|
|
2
|
+
import type { BreakpointProp, FlushProp, TableCellIndentProp, TableColumnPriorityProp, TablePresetProp } from "../../props/vocabulary/index.js";
|
|
3
3
|
export type TableProps = React.HTMLAttributes<HTMLTableElement> & {
|
|
4
4
|
/**
|
|
5
5
|
* Whether the Table owns its own horizontal-scroll region (default `true`). When `true` a table
|
|
@@ -62,5 +62,22 @@ type TableCellLabel = {
|
|
|
62
62
|
label?: React.ReactNode;
|
|
63
63
|
};
|
|
64
64
|
export declare const TableHead: React.ForwardRefExoticComponent<React.ThHTMLAttributes<HTMLTableCellElement> & TableCellPriority & React.RefAttributes<HTMLTableCellElement>>;
|
|
65
|
-
|
|
65
|
+
/**
|
|
66
|
+
* The cell's CONTENT owns its inset — an expanded detail panel, a nested table, a full-bleed media
|
|
67
|
+
* strip. The cell drops its own padding so the child reaches the cell edges; without it the only
|
|
68
|
+
* route was a zero-padding utility at the call site, which no service theme can reach (gh#354).
|
|
69
|
+
*/
|
|
70
|
+
type TableCellFlush = {
|
|
71
|
+
flush?: FlushProp;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Hierarchy depth. The indent is `--table-cell-space-x + depth × --table-cell-indent-space-step`,
|
|
75
|
+
* computed in table-layout.css off the level this prop publishes as `--table-cell-indent-level`,
|
|
76
|
+
* so a service retunes (or flattens) the step without touching JSX — the route TreeSelect's
|
|
77
|
+
* `--tree-select-depth` already takes. Logical, so an RTL tree indents from the inline start.
|
|
78
|
+
*/
|
|
79
|
+
type TableCellIndent = {
|
|
80
|
+
indent?: TableCellIndentProp;
|
|
81
|
+
};
|
|
82
|
+
export declare const TableCell: React.ForwardRefExoticComponent<React.TdHTMLAttributes<HTMLTableCellElement> & TableCellPriority & TableCellLabel & TableCellFlush & TableCellIndent & React.RefAttributes<HTMLTableCellElement>>;
|
|
66
83
|
export {};
|
|
@@ -76,13 +76,16 @@ const TableHead = React.forwardRef(({ className, priority, ...props }, ref) => /
|
|
|
76
76
|
}
|
|
77
77
|
));
|
|
78
78
|
TableHead.displayName = "TableHead";
|
|
79
|
-
const TableCell = React.forwardRef(({ className, priority, label, children, ...props }, ref) => /* @__PURE__ */ jsxs(
|
|
79
|
+
const TableCell = React.forwardRef(({ className, priority, label, flush, indent, children, style, ...props }, ref) => /* @__PURE__ */ jsxs(
|
|
80
80
|
"td",
|
|
81
81
|
{
|
|
82
82
|
ref,
|
|
83
83
|
"data-slot": "table-cell",
|
|
84
84
|
"data-priority": priority,
|
|
85
|
+
"data-flush": flush ? "" : void 0,
|
|
86
|
+
"data-indent": indent === void 0 ? void 0 : indent,
|
|
85
87
|
className: cn(className),
|
|
88
|
+
style: indent === void 0 ? style : { ...style, "--table-cell-indent-level": indent },
|
|
86
89
|
...props,
|
|
87
90
|
children: [
|
|
88
91
|
label !== void 0 ? /* @__PURE__ */ jsx("span", { className: "ui-table-stacked-collection-label", "aria-hidden": "true", children: label }) : null,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { TimelineGridProp } from "../../props/components/data-display.prop.js";
|
|
3
|
+
export type { TimelineGridColumnProp, TimelineGridEventProp, TimelineGridProp, TimelineGridProp as TimelineGridProps, } from "../../props/components/data-display.prop.js";
|
|
4
|
+
/**
|
|
5
|
+
* TimelineGrid — the time-axis half of the Timeline family: a vertical hour axis, one column per
|
|
6
|
+
* day (or room, or machine) and event blocks placed by start time and duration.
|
|
7
|
+
*
|
|
8
|
+
* WHAT IT IS NOT: a calendar. There is no month view, no navigation, no drag-to-create, no
|
|
9
|
+
* recurrence and no timezone conversion. `start`/`end` are clock times in the column's own day;
|
|
10
|
+
* which day a column stands for is the consumer's business.
|
|
11
|
+
*
|
|
12
|
+
* SEMANTICS: each column is a `ul` named by its own head, and each event is an `li` that PRINTS
|
|
13
|
+
* its time range as text. A screen reader therefore reads "木 14, list, 3 items — 09:00–17:30
|
|
14
|
+
* 早番 田中 …" in time order, which is the same information the sighted reader takes from the
|
|
15
|
+
* block's position. Nothing depends on the pixel offset. The hour rail is decorative
|
|
16
|
+
* (`aria-hidden`) because it repeats what every block already says.
|
|
17
|
+
*/
|
|
18
|
+
export declare const TimelineGrid: React.ForwardRefExoticComponent<TimelineGridProp & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../../lib/utils.js";
|
|
5
|
+
const MINUTES_PER_DAY = 24 * 60;
|
|
6
|
+
const CLOCK = /^(\d{1,2}):(\d{2})$/;
|
|
7
|
+
function toMinutes(value) {
|
|
8
|
+
const match = CLOCK.exec(value.trim());
|
|
9
|
+
if (!match) return null;
|
|
10
|
+
const hours = Number(match[1]);
|
|
11
|
+
const minutes = Number(match[2]);
|
|
12
|
+
if (!Number.isInteger(hours) || !Number.isInteger(minutes)) return null;
|
|
13
|
+
if (hours > 24 || minutes > 59 || hours === 24 && minutes !== 0) return null;
|
|
14
|
+
return hours * 60 + minutes;
|
|
15
|
+
}
|
|
16
|
+
function toClock(minutes) {
|
|
17
|
+
const total = (Math.round(minutes) % MINUTES_PER_DAY + MINUTES_PER_DAY) % MINUTES_PER_DAY;
|
|
18
|
+
const hours = Math.floor(total / 60);
|
|
19
|
+
const rest = total % 60;
|
|
20
|
+
return `${String(hours).padStart(2, "0")}:${String(rest).padStart(2, "0")}`;
|
|
21
|
+
}
|
|
22
|
+
function resolveEvent(event) {
|
|
23
|
+
const startMin = toMinutes(event.start);
|
|
24
|
+
const endMin = toMinutes(event.end);
|
|
25
|
+
if (startMin === null || endMin === null) return null;
|
|
26
|
+
return { event, startMin, endMin: endMin > startMin ? endMin : endMin + MINUTES_PER_DAY };
|
|
27
|
+
}
|
|
28
|
+
function placeColumn(events) {
|
|
29
|
+
const sorted = [...events].sort((a, b) => a.startMin - b.startMin || a.endMin - b.endMin);
|
|
30
|
+
const placed = [];
|
|
31
|
+
let cluster = [];
|
|
32
|
+
let clusterEnd = Number.NEGATIVE_INFINITY;
|
|
33
|
+
let laneEnds = [];
|
|
34
|
+
const closeCluster = () => {
|
|
35
|
+
const lanes = Math.max(laneEnds.length, 1);
|
|
36
|
+
for (const entry of cluster) placed.push({ ...entry, lanes });
|
|
37
|
+
cluster = [];
|
|
38
|
+
laneEnds = [];
|
|
39
|
+
clusterEnd = Number.NEGATIVE_INFINITY;
|
|
40
|
+
};
|
|
41
|
+
for (const entry of sorted) {
|
|
42
|
+
if (entry.startMin >= clusterEnd) closeCluster();
|
|
43
|
+
let lane = laneEnds.findIndex((end) => end <= entry.startMin);
|
|
44
|
+
if (lane === -1) lane = laneEnds.length;
|
|
45
|
+
laneEnds[lane] = entry.endMin;
|
|
46
|
+
clusterEnd = Math.max(clusterEnd, entry.endMin);
|
|
47
|
+
cluster.push({ ...entry, lane, lanes: 1 });
|
|
48
|
+
}
|
|
49
|
+
closeCluster();
|
|
50
|
+
return placed;
|
|
51
|
+
}
|
|
52
|
+
function resolveWindow(start, end, events) {
|
|
53
|
+
const pinnedStart = start === void 0 ? null : toMinutes(start);
|
|
54
|
+
const pinnedEnd = end === void 0 ? null : toMinutes(end);
|
|
55
|
+
let windowStart = pinnedStart;
|
|
56
|
+
let windowEnd = pinnedEnd;
|
|
57
|
+
if (windowStart === null || windowEnd === null) {
|
|
58
|
+
let earliest = Number.POSITIVE_INFINITY;
|
|
59
|
+
let latest = Number.NEGATIVE_INFINITY;
|
|
60
|
+
for (const entry of events) {
|
|
61
|
+
earliest = Math.min(earliest, entry.startMin);
|
|
62
|
+
latest = Math.max(latest, Math.min(entry.endMin, MINUTES_PER_DAY));
|
|
63
|
+
}
|
|
64
|
+
if (Number.isFinite(earliest)) {
|
|
65
|
+
windowStart ??= Math.floor(earliest / 60) * 60;
|
|
66
|
+
windowEnd ??= Math.ceil(latest / 60) * 60;
|
|
67
|
+
} else {
|
|
68
|
+
windowStart ??= 0;
|
|
69
|
+
windowEnd ??= MINUTES_PER_DAY;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (windowEnd <= windowStart) windowEnd = windowStart + 60;
|
|
73
|
+
return { startMin: windowStart, endMin: windowEnd };
|
|
74
|
+
}
|
|
75
|
+
const TimelineGrid = React.forwardRef(
|
|
76
|
+
function TimelineGrid2({ label, columns, events, start, end, interval = 1, now, onEventSelect, className, id }, ref) {
|
|
77
|
+
const uid = React.useId();
|
|
78
|
+
const columnIndex = React.useMemo(() => {
|
|
79
|
+
const map = /* @__PURE__ */ new Map();
|
|
80
|
+
columns.forEach((column, index) => map.set(column.id, index));
|
|
81
|
+
return map;
|
|
82
|
+
}, [columns]);
|
|
83
|
+
const resolved = React.useMemo(() => {
|
|
84
|
+
const out = [];
|
|
85
|
+
for (const event of events) {
|
|
86
|
+
const index = columnIndex.get(event.columnId);
|
|
87
|
+
if (index === void 0) continue;
|
|
88
|
+
const parsed = resolveEvent(event);
|
|
89
|
+
if (parsed) out.push({ ...parsed, columnIndex: index });
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}, [events, columnIndex]);
|
|
93
|
+
const axis = React.useMemo(() => resolveWindow(start, end, resolved), [start, end, resolved]);
|
|
94
|
+
const span = axis.endMin - axis.startMin;
|
|
95
|
+
const hours = span / 60;
|
|
96
|
+
const step = Number.isFinite(interval) && interval > 0 ? interval : 1;
|
|
97
|
+
const byColumn = React.useMemo(() => {
|
|
98
|
+
const buckets = columns.map(() => []);
|
|
99
|
+
for (const entry of resolved) {
|
|
100
|
+
if (entry.endMin <= axis.startMin || entry.startMin >= axis.endMin) continue;
|
|
101
|
+
buckets[entry.columnIndex].push(entry);
|
|
102
|
+
}
|
|
103
|
+
return buckets.map(
|
|
104
|
+
(bucket) => [...placeColumn(bucket)].sort((a, b) => a.startMin - b.startMin || a.lane - b.lane)
|
|
105
|
+
);
|
|
106
|
+
}, [columns, resolved, axis.startMin, axis.endMin]);
|
|
107
|
+
const ticks = React.useMemo(() => {
|
|
108
|
+
const out = [];
|
|
109
|
+
for (let minutes = axis.startMin; minutes <= axis.endMin; minutes += step * 60) {
|
|
110
|
+
out.push({ minutes, offset: (minutes - axis.startMin) / 60 });
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}, [axis.startMin, axis.endMin, step]);
|
|
114
|
+
const nowMin = now === void 0 ? null : toMinutes(now);
|
|
115
|
+
const nowOffset = nowMin !== null && nowMin >= axis.startMin && nowMin <= axis.endMin ? (nowMin - axis.startMin) / 60 : null;
|
|
116
|
+
return /* @__PURE__ */ jsx(
|
|
117
|
+
"div",
|
|
118
|
+
{
|
|
119
|
+
ref,
|
|
120
|
+
id,
|
|
121
|
+
"data-slot": "timeline-grid",
|
|
122
|
+
className: cn("ui-timeline-grid ui-focus-ring", className),
|
|
123
|
+
tabIndex: 0,
|
|
124
|
+
role: "group",
|
|
125
|
+
"aria-label": label,
|
|
126
|
+
style: {
|
|
127
|
+
"--timeline-grid-hours": hours,
|
|
128
|
+
"--timeline-grid-columns": columns.length,
|
|
129
|
+
"--timeline-grid-interval": step
|
|
130
|
+
},
|
|
131
|
+
children: /* @__PURE__ */ jsxs("div", { className: "ui-timeline-grid-frame", children: [
|
|
132
|
+
/* @__PURE__ */ jsx("div", { className: "ui-timeline-grid-corner", "aria-hidden": "true" }),
|
|
133
|
+
columns.map((column, index) => /* @__PURE__ */ jsxs(
|
|
134
|
+
"div",
|
|
135
|
+
{
|
|
136
|
+
id: `${uid}-head-${index}`,
|
|
137
|
+
className: "ui-timeline-grid-head",
|
|
138
|
+
"data-current": column.current ? "true" : void 0,
|
|
139
|
+
children: [
|
|
140
|
+
/* @__PURE__ */ jsx("span", { className: "ui-timeline-grid-head-label", children: column.label }),
|
|
141
|
+
column.description === void 0 ? null : /* @__PURE__ */ jsx("span", { className: "ui-timeline-grid-head-description", children: column.description })
|
|
142
|
+
]
|
|
143
|
+
},
|
|
144
|
+
column.id
|
|
145
|
+
)),
|
|
146
|
+
/* @__PURE__ */ jsx("div", { className: "ui-timeline-grid-axis", "aria-hidden": "true", children: ticks.map((tick) => /* @__PURE__ */ jsx(
|
|
147
|
+
"span",
|
|
148
|
+
{
|
|
149
|
+
className: "ui-timeline-grid-tick",
|
|
150
|
+
style: { "--timeline-grid-tick-offset": tick.offset },
|
|
151
|
+
children: toClock(tick.minutes)
|
|
152
|
+
},
|
|
153
|
+
tick.minutes
|
|
154
|
+
)) }),
|
|
155
|
+
columns.map((column, index) => /* @__PURE__ */ jsxs(
|
|
156
|
+
"ul",
|
|
157
|
+
{
|
|
158
|
+
className: "ui-timeline-grid-column",
|
|
159
|
+
"aria-labelledby": `${uid}-head-${index}`,
|
|
160
|
+
"data-current": column.current ? "true" : void 0,
|
|
161
|
+
children: [
|
|
162
|
+
byColumn[index].map((entry) => renderEvent(entry, axis, onEventSelect)),
|
|
163
|
+
column.current && nowOffset !== null ? /* @__PURE__ */ jsx(
|
|
164
|
+
"li",
|
|
165
|
+
{
|
|
166
|
+
"aria-hidden": "true",
|
|
167
|
+
className: "ui-timeline-grid-now",
|
|
168
|
+
style: { "--timeline-grid-now-offset": nowOffset }
|
|
169
|
+
}
|
|
170
|
+
) : null
|
|
171
|
+
]
|
|
172
|
+
},
|
|
173
|
+
column.id
|
|
174
|
+
))
|
|
175
|
+
] })
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
TimelineGrid.displayName = "TimelineGrid";
|
|
181
|
+
function renderEvent(entry, axis, onEventSelect) {
|
|
182
|
+
const { event } = entry;
|
|
183
|
+
const from = Math.max(entry.startMin, axis.startMin);
|
|
184
|
+
const to = Math.min(entry.endMin, axis.endMin);
|
|
185
|
+
const clippedStart = entry.startMin < axis.startMin;
|
|
186
|
+
const clippedEnd = entry.endMin > axis.endMin;
|
|
187
|
+
const clipped = clippedStart && clippedEnd ? "both" : clippedStart ? "start" : "end";
|
|
188
|
+
const range = `${toClock(entry.startMin)}\u2013${toClock(entry.endMin)}`;
|
|
189
|
+
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
190
|
+
/* @__PURE__ */ jsx("span", { className: "ui-timeline-grid-event-time", children: range }),
|
|
191
|
+
/* @__PURE__ */ jsx("span", { className: "ui-timeline-grid-event-title", children: event.title }),
|
|
192
|
+
event.description === void 0 ? null : /* @__PURE__ */ jsx("span", { className: "ui-timeline-grid-event-description", children: event.description })
|
|
193
|
+
] });
|
|
194
|
+
return /* @__PURE__ */ jsx(
|
|
195
|
+
"li",
|
|
196
|
+
{
|
|
197
|
+
"data-event-id": event.id,
|
|
198
|
+
"data-clipped": clippedStart || clippedEnd ? clipped : void 0,
|
|
199
|
+
className: "ui-timeline-grid-event",
|
|
200
|
+
style: {
|
|
201
|
+
"--timeline-grid-event-offset": (from - axis.startMin) / 60,
|
|
202
|
+
"--timeline-grid-event-span": (to - from) / 60,
|
|
203
|
+
"--timeline-grid-event-lane": entry.lane,
|
|
204
|
+
"--timeline-grid-event-lanes": entry.lanes,
|
|
205
|
+
...event.color === void 0 || event.color === "" ? null : { "--timeline-grid-event-color": event.color }
|
|
206
|
+
},
|
|
207
|
+
children: onEventSelect ? /* @__PURE__ */ jsx(
|
|
208
|
+
"button",
|
|
209
|
+
{
|
|
210
|
+
type: "button",
|
|
211
|
+
className: "ui-timeline-grid-event-body ui-focus-ring",
|
|
212
|
+
onClick: () => onEventSelect(event),
|
|
213
|
+
children: body
|
|
214
|
+
}
|
|
215
|
+
) : /* @__PURE__ */ jsx("span", { className: "ui-timeline-grid-event-body", children: body })
|
|
216
|
+
},
|
|
217
|
+
event.id
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
export {
|
|
221
|
+
TimelineGrid
|
|
222
|
+
};
|
|
@@ -5,8 +5,7 @@ import { Check, ChevronRight, ChevronsUpDown, Minus, X } from "lucide-react";
|
|
|
5
5
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
7
|
import { pickFieldA11y, useFieldIdentity } from "../../lib/field-a11y.js";
|
|
8
|
-
import {
|
|
9
|
-
import { Button } from "../general/button.js";
|
|
8
|
+
import { controlTriggerClass } from "../../lib/control-styles.js";
|
|
10
9
|
import { Popover, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
|
|
11
10
|
import { ScrollArea, ScrollBar } from "../data-display/scroll-area.js";
|
|
12
11
|
import { Command, CommandInput } from "./command.js";
|
|
@@ -238,12 +237,11 @@ function Cascader({
|
|
|
238
237
|
return /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
|
|
239
238
|
/* @__PURE__ */ jsxs(Popover, { open, onOpenChange: handleOpenChange, children: [
|
|
240
239
|
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
241
|
-
|
|
240
|
+
"button",
|
|
242
241
|
{
|
|
243
242
|
id,
|
|
244
243
|
"data-field": fieldA11y["data-field"] ?? identity["data-field"],
|
|
245
244
|
type: "button",
|
|
246
|
-
variant: "outline",
|
|
247
245
|
role: "combobox",
|
|
248
246
|
"aria-expanded": open,
|
|
249
247
|
"aria-haspopup": "listbox",
|
|
@@ -251,8 +249,8 @@ function Cascader({
|
|
|
251
249
|
...fieldA11y,
|
|
252
250
|
disabled,
|
|
253
251
|
className: cn(
|
|
254
|
-
|
|
255
|
-
|
|
252
|
+
controlTriggerClass,
|
|
253
|
+
"w-full justify-start",
|
|
256
254
|
// Reserve trailing room for the single clear-or-chevron overlay rendered below.
|
|
257
255
|
"ui-control-trigger-affixed",
|
|
258
256
|
!displayLabel && "text-muted-foreground"
|
|
@@ -19,7 +19,7 @@ const CheckboxRoot = React.forwardRef(({ className, ...props }, ref) => {
|
|
|
19
19
|
"data-slot": "checkbox",
|
|
20
20
|
className: cn(
|
|
21
21
|
// `disabled:cursor-not-allowed disabled:opacity-50` and the checked fill are DELETED, not
|
|
22
|
-
// moved
|
|
22
|
+
// moved: `.ui-checkbox:disabled, .ui-checkbox[data-disabled]` and
|
|
23
23
|
// `.ui-checkbox[data-state="checked"]` in styles/control.css already declare both, reading
|
|
24
24
|
// --disabled-opacity and --checkbox-checked-background. Utilities are layered AFTER
|
|
25
25
|
// components in Tailwind v4, so these literals were silently OUTRANKING those knobs — a
|
|
@@ -5,8 +5,7 @@ import { ChevronsUpDown, Loader2, X } from "lucide-react";
|
|
|
5
5
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { useFieldIdentity, useFieldNameFallback } from "../../lib/field-a11y.js";
|
|
7
7
|
import { cn } from "../../lib/utils.js";
|
|
8
|
-
import {
|
|
9
|
-
import { Button } from "../general/button.js";
|
|
8
|
+
import { controlTriggerClass } from "../../lib/control-styles.js";
|
|
10
9
|
import { Popover, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
|
|
11
10
|
import { Command, CommandGroup } from "./command.js";
|
|
12
11
|
import { Input } from "./input.js";
|
|
@@ -240,13 +239,12 @@ function SearchSelect({
|
|
|
240
239
|
},
|
|
241
240
|
children: [
|
|
242
241
|
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
243
|
-
|
|
242
|
+
"button",
|
|
244
243
|
{
|
|
245
244
|
id,
|
|
246
245
|
type: "button",
|
|
247
|
-
variant: "outline",
|
|
248
246
|
role: "combobox",
|
|
249
|
-
size,
|
|
247
|
+
"data-size": size === "md" ? void 0 : size,
|
|
250
248
|
"aria-expanded": open,
|
|
251
249
|
"aria-controls": open ? listId : void 0,
|
|
252
250
|
"aria-label": triggerAriaLabel,
|
|
@@ -261,8 +259,8 @@ function SearchSelect({
|
|
|
261
259
|
"data-field": resolvedField,
|
|
262
260
|
"data-value": value || void 0,
|
|
263
261
|
className: cn(
|
|
264
|
-
|
|
265
|
-
|
|
262
|
+
controlTriggerClass,
|
|
263
|
+
"w-full justify-start",
|
|
266
264
|
// Reserve trailing room for the single clear-or-chevron overlay rendered below.
|
|
267
265
|
"ui-control-trigger-affixed"
|
|
268
266
|
),
|
|
@@ -5,6 +5,7 @@ import * as SelectPrimitive from "@radix-ui/react-select";
|
|
|
5
5
|
import { ChevronDown, ChevronUp, X } from "lucide-react";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
7
|
import { controlTriggerClass } from "../../lib/control-styles.js";
|
|
8
|
+
import { useInertHiddenBackground } from "../general/inert-background.js";
|
|
8
9
|
import {
|
|
9
10
|
mergeAriaIds,
|
|
10
11
|
useFieldIdentity,
|
|
@@ -141,35 +142,38 @@ const SelectScrollDownButton = React.forwardRef(({ className, ...props }, ref) =
|
|
|
141
142
|
}
|
|
142
143
|
));
|
|
143
144
|
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
|
|
144
|
-
const SelectContent = React.forwardRef(({ className, children, position = "popper", ...props }, ref) =>
|
|
145
|
-
|
|
146
|
-
{
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
"
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
...props,
|
|
156
|
-
children: [
|
|
157
|
-
/* @__PURE__ */ jsx(SelectScrollUpButton, {}),
|
|
158
|
-
/* @__PURE__ */ jsx(
|
|
159
|
-
SelectPrimitive.Viewport,
|
|
160
|
-
{
|
|
161
|
-
"data-slot": "select-viewport",
|
|
162
|
-
className: cn(
|
|
163
|
-
"ui-select-viewport",
|
|
164
|
-
position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
|
165
|
-
),
|
|
166
|
-
children
|
|
167
|
-
}
|
|
145
|
+
const SelectContent = React.forwardRef(({ className, children, position = "popper", ...props }, ref) => {
|
|
146
|
+
useInertHiddenBackground();
|
|
147
|
+
return /* @__PURE__ */ jsx(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs(
|
|
148
|
+
SelectPrimitive.Content,
|
|
149
|
+
{
|
|
150
|
+
ref,
|
|
151
|
+
"data-slot": "select-content",
|
|
152
|
+
className: cn(
|
|
153
|
+
"ui-select-content data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
154
|
+
position === "popper" && "translate-y-1",
|
|
155
|
+
className
|
|
168
156
|
),
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
157
|
+
position,
|
|
158
|
+
...props,
|
|
159
|
+
children: [
|
|
160
|
+
/* @__PURE__ */ jsx(SelectScrollUpButton, {}),
|
|
161
|
+
/* @__PURE__ */ jsx(
|
|
162
|
+
SelectPrimitive.Viewport,
|
|
163
|
+
{
|
|
164
|
+
"data-slot": "select-viewport",
|
|
165
|
+
className: cn(
|
|
166
|
+
"ui-select-viewport",
|
|
167
|
+
position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
|
168
|
+
),
|
|
169
|
+
children
|
|
170
|
+
}
|
|
171
|
+
),
|
|
172
|
+
/* @__PURE__ */ jsx(SelectScrollDownButton, {})
|
|
173
|
+
]
|
|
174
|
+
}
|
|
175
|
+
) });
|
|
176
|
+
});
|
|
173
177
|
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
|
174
178
|
const SelectLabel = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
175
179
|
SelectPrimitive.Label,
|
|
@@ -5,8 +5,7 @@ import { ChevronDown, ChevronRight, ChevronsUpDown, X } from "lucide-react";
|
|
|
5
5
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
7
|
import { pickFieldA11y, useFieldIdentity } from "../../lib/field-a11y.js";
|
|
8
|
-
import {
|
|
9
|
-
import { Button } from "../general/button.js";
|
|
8
|
+
import { controlTriggerClass } from "../../lib/control-styles.js";
|
|
10
9
|
import { Popover, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
|
|
11
10
|
import { ScrollArea } from "../data-display/scroll-area.js";
|
|
12
11
|
import { Checkbox } from "./checkbox.js";
|
|
@@ -177,12 +176,11 @@ function TreeSelectRoot({
|
|
|
177
176
|
return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
|
|
178
177
|
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
179
178
|
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
180
|
-
|
|
179
|
+
"button",
|
|
181
180
|
{
|
|
182
181
|
id,
|
|
183
182
|
"data-field": fieldA11y["data-field"] ?? identity["data-field"],
|
|
184
183
|
type: "button",
|
|
185
|
-
variant: "outline",
|
|
186
184
|
role: "combobox",
|
|
187
185
|
"aria-expanded": open,
|
|
188
186
|
"aria-haspopup": "tree",
|
|
@@ -190,9 +188,9 @@ function TreeSelectRoot({
|
|
|
190
188
|
...fieldA11y,
|
|
191
189
|
disabled,
|
|
192
190
|
className: cn(
|
|
193
|
-
|
|
191
|
+
controlTriggerClass,
|
|
192
|
+
"w-full justify-between",
|
|
194
193
|
allowClear && displayKeys.length > 0 && !disabled && "ui-tree-select-trigger-affixed",
|
|
195
|
-
controlOpenRingClass,
|
|
196
194
|
!displayKeys.length && "text-muted-foreground",
|
|
197
195
|
className
|
|
198
196
|
),
|