@stll/ui 0.61.0 → 0.64.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/calendar/day-calendar.logic.js +2 -2
- package/dist/components/accordion.js +1 -1
- package/dist/components/bidi-text.d.ts +7 -0
- package/dist/components/bidi-text.js +22 -9
- package/dist/components/button.d.ts +2 -1
- package/dist/components/button.js +57 -3
- package/dist/components/button.native-classes.js +5 -0
- package/dist/components/button.shared.d.ts +1 -1
- package/dist/components/button.shared.js +1 -0
- package/dist/components/checkbox.js +2 -0
- package/dist/components/court-filter.d.ts +42 -0
- package/dist/components/court-filter.js +210 -0
- package/dist/components/identity-text.js +4 -2
- package/dist/components/landing.js +1 -1
- package/dist/components/middle-truncate.d.ts +19 -0
- package/dist/components/middle-truncate.js +112 -0
- package/dist/components/middle-truncate.logic.d.ts +11 -0
- package/dist/components/middle-truncate.logic.js +63 -0
- package/dist/components/separator.d.ts +26 -1
- package/dist/components/separator.js +52 -1
- package/dist/components/sidebar.d.ts +8 -1
- package/dist/components/sidebar.js +21 -1
- package/dist/components/tabs.js +12 -1
- package/dist/components/text.js +1 -1
- package/dist/components/text.native.js +3 -11
- package/dist/components/text.shared.d.ts +0 -29
- package/dist/components/text.shared.js +3 -33
- package/dist/icons.d.ts +2 -2
- package/dist/icons.js +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/inspector/chrome.js +2 -1
- package/dist/inspector/facet-bar.js +14 -8
- package/dist/lib/inline-reorder.js +3 -3
- package/dist/lib/tab-order.d.ts +19 -1
- package/dist/lib/tab-order.js +12 -10
- package/dist/review/review-comment-card.js +1 -1
- package/package.json +14 -1
|
@@ -146,7 +146,7 @@ const slotAt = (ms, options) => rangeFromDrag({
|
|
|
146
146
|
* member of a cluster shares the cluster's lane count, so their widths match.
|
|
147
147
|
*/
|
|
148
148
|
const layoutOverlaps = (ranges) => {
|
|
149
|
-
const sorted = ranges.
|
|
149
|
+
const sorted = [...ranges].sort((left, right) => left.startMs - right.startMs || right.endMs - left.endMs || compareIds(left.id, right.id));
|
|
150
150
|
const placements = /* @__PURE__ */ new Map();
|
|
151
151
|
const seen = /* @__PURE__ */ new Set();
|
|
152
152
|
let cluster = [];
|
|
@@ -186,7 +186,7 @@ const layoutOverlaps = (ranges) => {
|
|
|
186
186
|
* an earlier entry already uses it.
|
|
187
187
|
*/
|
|
188
188
|
const mergeShortSegments = (segments, { thresholdMs }) => {
|
|
189
|
-
const sorted = segments.
|
|
189
|
+
const sorted = [...segments].sort((left, right) => left.startMs - right.startMs || left.endMs - right.endMs || compareIds(left.id, right.id));
|
|
190
190
|
const merged = [];
|
|
191
191
|
const takenIds = new Set(segments.map(({ id }) => id));
|
|
192
192
|
let run = [];
|
|
@@ -14,7 +14,7 @@ const AccordionItem = ({ className, ...props }) => /* @__PURE__ */ jsx(Accordion
|
|
|
14
14
|
...props
|
|
15
15
|
});
|
|
16
16
|
const TRIGGER_SIZE_CLASS_NAME = {
|
|
17
|
-
compact: "py-
|
|
17
|
+
compact: "py-1.5 text-xs",
|
|
18
18
|
default: "py-4 text-sm",
|
|
19
19
|
sm: "min-h-11 items-center py-2 text-sm"
|
|
20
20
|
};
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import * as React$1 from "react";
|
|
2
2
|
//#region src/components/bidi-text.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Isolation shields the text around a value, not the value itself: a
|
|
5
|
+
* directional control inside it still reorders its letters, so stored text
|
|
6
|
+
* loses those controls before it renders.
|
|
7
|
+
*/
|
|
8
|
+
export declare const stripDirectionalControls: (text: string) => string;
|
|
9
|
+
export declare const withoutDirectionalControls: (children: React$1.ReactNode) => React$1.ReactNode;
|
|
3
10
|
export declare const BidiText: (props: BidiTextProps) => React$1.JSX.Element;
|
|
4
11
|
export declare const UserText: typeof BidiText;
|
|
5
12
|
type BidiDirection = "auto" | "ltr" | "rtl";
|
|
@@ -2,37 +2,50 @@
|
|
|
2
2
|
import { cn } from "../lib/utils.js";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
//#region src/components/bidi-text.tsx
|
|
5
|
+
/** Explicit embeddings, overrides and isolates; implicit marks stay. */
|
|
6
|
+
const DIRECTIONAL_CONTROLS = /[\u202A-\u202E\u2066-\u2069]/gu;
|
|
7
|
+
/**
|
|
8
|
+
* Isolation shields the text around a value, not the value itself: a
|
|
9
|
+
* directional control inside it still reorders its letters, so stored text
|
|
10
|
+
* loses those controls before it renders.
|
|
11
|
+
*/
|
|
12
|
+
const stripDirectionalControls = (text) => text.replaceAll(DIRECTIONAL_CONTROLS, "");
|
|
13
|
+
const withoutDirectionalControls = (children) => typeof children === "string" ? stripDirectionalControls(children) : children;
|
|
5
14
|
const BidiText = (props) => {
|
|
6
15
|
if (props.as === "span") {
|
|
7
|
-
const { as: _as, className, direction = "auto", ...rest } = props;
|
|
16
|
+
const { as: _as, children, className, direction = "auto", ...rest } = props;
|
|
8
17
|
return /* @__PURE__ */ jsx("span", {
|
|
9
18
|
className: cn("[unicode-bidi:isolate]", className),
|
|
10
19
|
dir: direction,
|
|
11
|
-
...rest
|
|
20
|
+
...rest,
|
|
21
|
+
children: withoutDirectionalControls(children)
|
|
12
22
|
});
|
|
13
23
|
}
|
|
14
24
|
if (props.as === "div") {
|
|
15
|
-
const { as: _as, className, direction = "auto", ...rest } = props;
|
|
25
|
+
const { as: _as, children, className, direction = "auto", ...rest } = props;
|
|
16
26
|
return /* @__PURE__ */ jsx("div", {
|
|
17
27
|
className: cn("[unicode-bidi:isolate]", className),
|
|
18
28
|
dir: direction,
|
|
19
|
-
...rest
|
|
29
|
+
...rest,
|
|
30
|
+
children: withoutDirectionalControls(children)
|
|
20
31
|
});
|
|
21
32
|
}
|
|
22
33
|
if (props.as === "p") {
|
|
23
|
-
const { as: _as, className, direction = "auto", ...rest } = props;
|
|
34
|
+
const { as: _as, children, className, direction = "auto", ...rest } = props;
|
|
24
35
|
return /* @__PURE__ */ jsx("p", {
|
|
25
36
|
className: cn("[unicode-bidi:isolate]", className),
|
|
26
37
|
dir: direction,
|
|
27
|
-
...rest
|
|
38
|
+
...rest,
|
|
39
|
+
children: withoutDirectionalControls(children)
|
|
28
40
|
});
|
|
29
41
|
}
|
|
30
|
-
const { as: _as, direction = "auto", ...rest } = props;
|
|
42
|
+
const { as: _as, children, direction = "auto", ...rest } = props;
|
|
31
43
|
return /* @__PURE__ */ jsx("bdi", {
|
|
32
44
|
dir: direction,
|
|
33
|
-
...rest
|
|
45
|
+
...rest,
|
|
46
|
+
children: withoutDirectionalControls(children)
|
|
34
47
|
});
|
|
35
48
|
};
|
|
36
49
|
const UserText = BidiText;
|
|
37
50
|
//#endregion
|
|
38
|
-
export { BidiText, UserText };
|
|
51
|
+
export { BidiText, UserText, stripDirectionalControls, withoutDirectionalControls };
|
|
@@ -16,7 +16,7 @@ declare const BUTTON_LAYOUT_CLASS_NAMES: {
|
|
|
16
16
|
readonly wrap: "h-auto min-h-11 items-start justify-start py-2 text-start whitespace-normal sm:h-auto";
|
|
17
17
|
};
|
|
18
18
|
declare const buttonVariants: (props?: ({
|
|
19
|
-
size?: "chip" | "default" | "icon" | "icon-2xs" | "icon-lg" | "icon-sm" | "icon-xl" | "icon-xs" | "inline" | "lg" | "row" | "sm" | "xl" | "xs" | null | undefined;
|
|
19
|
+
size?: "chip" | "default" | "icon" | "icon-2xs" | "icon-lg" | "icon-sm" | "icon-xl" | "icon-xs" | "inline" | "lg" | "row" | "sm" | "touch" | "xl" | "xs" | null | undefined;
|
|
20
20
|
layout?: "wrap" | null | undefined;
|
|
21
21
|
variant?: "default" | "destructive" | "destructive-ghost" | "destructive-outline" | "ghost" | "link" | "muted" | "muted-destructive" | "outline" | "secondary" | null | undefined;
|
|
22
22
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
@@ -66,6 +66,7 @@ declare const BUTTON_SIZE_CONTENT: {
|
|
|
66
66
|
readonly lg: "text";
|
|
67
67
|
readonly row: "text";
|
|
68
68
|
readonly sm: "text";
|
|
69
|
+
readonly touch: "text";
|
|
69
70
|
readonly xl: "text";
|
|
70
71
|
readonly xs: "text";
|
|
71
72
|
};
|
|
@@ -6,6 +6,7 @@ import { MENU_ROW_CLASS_NAME } from "../lib/menu-row.js";
|
|
|
6
6
|
import { BUTTON_PLAIN_VARIANT, BUTTON_VARIANT_TOKENS } from "./button.shared.js";
|
|
7
7
|
import { Loader } from "./loader.js";
|
|
8
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { Fragment as Fragment$1, isValidElement } from "react";
|
|
9
10
|
import { mergeProps } from "@base-ui/react/merge-props";
|
|
10
11
|
import { useRender } from "@base-ui/react/use-render";
|
|
11
12
|
import { panic } from "better-result";
|
|
@@ -50,6 +51,7 @@ const BUTTON_SIZE_CLASS_NAMES = {
|
|
|
50
51
|
lg: "h-10 px-[calc(--spacing(3.5)-1px)] sm:h-9",
|
|
51
52
|
row: `${MENU_ROW_CLASS_NAME} [&_svg]:mx-0`,
|
|
52
53
|
sm: "h-8 gap-1.5 px-[calc(--spacing(2.5)-1px)] text-sm sm:h-7 sm:text-xs",
|
|
54
|
+
touch: "min-h-[44px] min-w-[44px] px-[calc(--spacing(3.5)-1px)]",
|
|
53
55
|
xl: "h-11 px-[calc(--spacing(4)-1px)] text-lg sm:h-10 sm:text-base [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5",
|
|
54
56
|
xs: "h-7 gap-1 rounded-md px-[calc(--spacing(2)-1px)] text-sm before:rounded-[calc(var(--radius-md)-1px)] sm:h-6 sm:text-xs [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5"
|
|
55
57
|
};
|
|
@@ -65,7 +67,7 @@ const BUTTON_VARIANT_CLASS_NAMES = {
|
|
|
65
67
|
outline: `${tokens("outline")} dark:bg-input/32 data-pressed:border-primary/48 data-pressed:bg-accent dark:data-pressed:bg-input/64 [:hover:not([data-pressed])]:bg-accent/50 dark:[:hover:not([data-pressed])]:bg-input/64 shadow-xs/5 not-dark:bg-clip-padding not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none`,
|
|
66
68
|
secondary: `${tokens("secondary")} data-pressed:bg-background data-pressed:inset-ring-input active:bg-secondary/80 [:hover:not([data-pressed])]:bg-secondary/90 data-pressed:shadow-xs/5 data-pressed:inset-ring`
|
|
67
69
|
};
|
|
68
|
-
const buttonVariants = cva("focus-visible:ring-ring focus-visible:ring-offset-background relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-lg border text-base font-medium whitespace-nowrap outline-none select-none before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] focus-visible:ring-2 focus-visible:ring-offset-1 disabled:pointer-events-none disabled:opacity-64 sm:text-sm pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 [&_svg]:pointer-events-none [&_svg]:-mx-0.5 [&_svg]:shrink-0 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4", {
|
|
70
|
+
const buttonVariants = cva("focus-visible:ring-ring focus-visible:ring-offset-background relative inline-flex max-w-full shrink-0 cursor-pointer items-center justify-center gap-2 rounded-lg border text-base font-medium whitespace-nowrap outline-none select-none before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] focus-visible:ring-2 focus-visible:ring-offset-1 disabled:pointer-events-none disabled:opacity-64 sm:text-sm pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 [&_svg]:pointer-events-none [&_svg]:-mx-0.5 [&_svg]:shrink-0 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4", {
|
|
69
71
|
compoundVariants: [{
|
|
70
72
|
className: "[:hover:not([data-pressed])]:bg-transparent",
|
|
71
73
|
size: "inline",
|
|
@@ -105,7 +107,7 @@ const buttonVariants = cva("focus-visible:ring-ring focus-visible:ring-offset-ba
|
|
|
105
107
|
* inside the box: an outline paints above the content, so a card's own
|
|
106
108
|
* surface cannot cover it the way it would cover an inset shadow.
|
|
107
109
|
*/
|
|
108
|
-
const BUTTON_PLAIN_CLASS = "focus-visible:ring-ring focus-visible:ring-offset-background data-pressed:outline-primary/48 relative inline-block cursor-pointer text-start outline-none focus-visible:ring-2 focus-visible:ring-offset-1 disabled:cursor-default data-pressed:outline-2 data-pressed:-outline-offset-2 data-pressed:outline-solid";
|
|
110
|
+
const BUTTON_PLAIN_CLASS = "focus-visible:ring-ring focus-visible:ring-offset-background data-pressed:outline-primary/48 relative inline-block max-w-full cursor-pointer text-start outline-none focus-visible:ring-2 focus-visible:ring-offset-1 disabled:cursor-default data-pressed:outline-2 data-pressed:-outline-offset-2 data-pressed:outline-solid";
|
|
109
111
|
/**
|
|
110
112
|
* The outline a plain button's focus ring follows, matching its content:
|
|
111
113
|
* `box` for a card, row or block of text (the container radius), `round` for
|
|
@@ -145,9 +147,57 @@ const BUTTON_SIZE_CONTENT = {
|
|
|
145
147
|
lg: BUTTON_CONTENT.text,
|
|
146
148
|
row: BUTTON_CONTENT.text,
|
|
147
149
|
sm: BUTTON_CONTENT.text,
|
|
150
|
+
touch: BUTTON_CONTENT.text,
|
|
148
151
|
xl: BUTTON_CONTENT.text,
|
|
149
152
|
xs: BUTTON_CONTENT.text
|
|
150
153
|
};
|
|
154
|
+
/**
|
|
155
|
+
* A button never grows past its container (`max-w-full` on every variant).
|
|
156
|
+
* A one-line text label is cut to fit with an ellipsis; the button's
|
|
157
|
+
* accessible name keeps the whole text, and `tooltip` can show it. The label
|
|
158
|
+
* is a plain span, not a `TruncatedText`: a tooltip root in every button's
|
|
159
|
+
* label would cost every screen with many buttons. `layout="wrap"` lets a
|
|
160
|
+
* label take several lines instead. An inline button is a word in a sentence
|
|
161
|
+
* and a plain button lays out its own content, so neither has a one-line
|
|
162
|
+
* label to cut.
|
|
163
|
+
*/
|
|
164
|
+
const BUTTON_LABEL_CLASS = "block min-w-0 truncate";
|
|
165
|
+
const cutsLabel = ({ layout, size, variant }) => layout === void 0 && variant !== "plain" && variant !== "link" && size !== "inline" && BUTTON_SIZE_CONTENT[size ?? BUTTON_DEFAULT_SIZE] === BUTTON_CONTENT.text;
|
|
166
|
+
/**
|
|
167
|
+
* Each run of adjacent text among the children, cut to the button's width.
|
|
168
|
+
* Adjacent text (`Save {count} files`) stays one run, as it is one flex item
|
|
169
|
+
* in the button's box; children React renders as nothing (`null`, `false`)
|
|
170
|
+
* do not split it. Every other child keeps its place in a fragment keyed by
|
|
171
|
+
* its own key, or by its position when it has none, so keyed children keep
|
|
172
|
+
* their state when they move.
|
|
173
|
+
*/
|
|
174
|
+
const containedLabel = (children) => {
|
|
175
|
+
const items = Array.isArray(children) ? children : [children];
|
|
176
|
+
const parts = [];
|
|
177
|
+
let run = [];
|
|
178
|
+
const closeRun = () => {
|
|
179
|
+
if (run.length > 0) {
|
|
180
|
+
parts.push(/* @__PURE__ */ jsx("span", {
|
|
181
|
+
className: BUTTON_LABEL_CLASS,
|
|
182
|
+
"data-slot": "button-label",
|
|
183
|
+
children: run
|
|
184
|
+
}, `label:${String(parts.length)}`));
|
|
185
|
+
run = [];
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
for (const child of items) {
|
|
189
|
+
if (child === null || child === void 0 || typeof child === "boolean") continue;
|
|
190
|
+
if (typeof child === "string" || typeof child === "number") {
|
|
191
|
+
run.push(child);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
closeRun();
|
|
195
|
+
const key = isValidElement(child) && child.key !== null ? `key:${child.key}` : `part:${String(parts.length)}`;
|
|
196
|
+
parts.push(/* @__PURE__ */ jsx(Fragment$1, { children: child }, key));
|
|
197
|
+
}
|
|
198
|
+
closeRun();
|
|
199
|
+
return parts;
|
|
200
|
+
};
|
|
151
201
|
const implicitTooltip = (size, accessibleName) => {
|
|
152
202
|
const content = BUTTON_SIZE_CONTENT[size ?? BUTTON_DEFAULT_SIZE];
|
|
153
203
|
switch (content) {
|
|
@@ -174,7 +224,11 @@ function Button({ className, variant: variantProp, size, render, layout, loading
|
|
|
174
224
|
"data-slot": "button-loader",
|
|
175
225
|
size: "sm",
|
|
176
226
|
variant: "decorative"
|
|
177
|
-
}) : icon,
|
|
227
|
+
}) : icon, cutsLabel({
|
|
228
|
+
layout,
|
|
229
|
+
size,
|
|
230
|
+
variant
|
|
231
|
+
}) ? containedLabel(children) : children] }),
|
|
178
232
|
className: cn(variant === "plain" ? cn(BUTTON_PLAIN_CLASS, BUTTON_PLAIN_SHAPE_CLASS[shape], className) : cn(disposition === BUTTON_DISPOSITION.enabled && !isActivationPreservingDisabled && size !== "row" && size !== "inline" && variant !== "link" && buttonPressClass, buttonVariants({
|
|
179
233
|
className,
|
|
180
234
|
size,
|
|
@@ -59,7 +59,7 @@ export type ButtonTokenVariant = keyof typeof BUTTON_VARIANT_TOKENS;
|
|
|
59
59
|
export declare const BUTTON_PLAIN_VARIANT = "plain";
|
|
60
60
|
export type ButtonVariant = ButtonTokenVariant | typeof BUTTON_PLAIN_VARIANT;
|
|
61
61
|
/** Every size a button comes in, on every platform. */
|
|
62
|
-
export declare const BUTTON_SIZES: readonly ["chip", "default", "icon", "icon-lg", "icon-sm", "icon-xl", "icon-2xs", "icon-xs", "inline", "lg", "row", "sm", "xl", "xs"];
|
|
62
|
+
export declare const BUTTON_SIZES: readonly ["chip", "default", "icon", "icon-lg", "icon-sm", "icon-xl", "icon-2xs", "icon-xs", "inline", "lg", "row", "sm", "touch", "xl", "xs"];
|
|
63
63
|
export type ButtonSize = (typeof BUTTON_SIZES)[number];
|
|
64
64
|
/**
|
|
65
65
|
* The props every button renderer accepts, with one meaning. A renderer adds
|
|
@@ -13,6 +13,7 @@ const Checkbox = ({ className, ...props }) => /* @__PURE__ */ jsx(Checkbox$1.Roo
|
|
|
13
13
|
render: (indicatorProps, state) => /* @__PURE__ */ jsx("span", {
|
|
14
14
|
...indicatorProps,
|
|
15
15
|
children: state.indeterminate ? /* @__PURE__ */ jsxs("svg", {
|
|
16
|
+
"aria-hidden": "true",
|
|
16
17
|
className: "size-3.5 sm:size-3",
|
|
17
18
|
fill: "none",
|
|
18
19
|
height: "24",
|
|
@@ -25,6 +26,7 @@ const Checkbox = ({ className, ...props }) => /* @__PURE__ */ jsx(Checkbox$1.Roo
|
|
|
25
26
|
xmlns: "http://www.w3.org/2000/svg",
|
|
26
27
|
children: [/* @__PURE__ */ jsx("title", { children: "Indeterminate" }), /* @__PURE__ */ jsx("path", { d: "M5.252 12h13.496" })]
|
|
27
28
|
}) : /* @__PURE__ */ jsxs("svg", {
|
|
29
|
+
"aria-hidden": "true",
|
|
28
30
|
className: "size-3.5 sm:size-3",
|
|
29
31
|
fill: "none",
|
|
30
32
|
height: "24",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//#region src/components/court-filter.d.ts
|
|
2
|
+
type CourtOption = {
|
|
3
|
+
value: string;
|
|
4
|
+
label?: string;
|
|
5
|
+
badgeText?: string;
|
|
6
|
+
};
|
|
7
|
+
type CourtFilterGroup = {
|
|
8
|
+
value: string;
|
|
9
|
+
label: string | null;
|
|
10
|
+
courts: readonly CourtOption[];
|
|
11
|
+
};
|
|
12
|
+
type CourtFilterBaseProps = {
|
|
13
|
+
groups: readonly CourtFilterGroup[];
|
|
14
|
+
label: string;
|
|
15
|
+
};
|
|
16
|
+
type SingleCourtFilterProps = CourtFilterBaseProps & {
|
|
17
|
+
selected: string | undefined;
|
|
18
|
+
onChange: (value: string | undefined) => void;
|
|
19
|
+
collapsedGroups: readonly string[];
|
|
20
|
+
visibleRowLimit: number;
|
|
21
|
+
showAllLabel: string;
|
|
22
|
+
};
|
|
23
|
+
type MultipleCourtFilterProps = CourtFilterBaseProps & {
|
|
24
|
+
selected: readonly string[];
|
|
25
|
+
onChange: (values: string[]) => void;
|
|
26
|
+
maxSelections: number;
|
|
27
|
+
selectionHint: string;
|
|
28
|
+
searchLabel: string;
|
|
29
|
+
clearLabel: string;
|
|
30
|
+
emptyLabel: string;
|
|
31
|
+
matchText: (value: string) => string;
|
|
32
|
+
};
|
|
33
|
+
export declare const toggleCourtSelection: (selected: readonly string[], values: readonly string[]) => string[];
|
|
34
|
+
/**
|
|
35
|
+
* Court filtering shares an owner while each surface keeps its interaction
|
|
36
|
+
* model. Each model is its own export, so a bundle carries only the one it
|
|
37
|
+
* renders.
|
|
38
|
+
*/
|
|
39
|
+
export declare const MultipleCourtFilter: (options: MultipleCourtFilterProps) => import("react").JSX.Element;
|
|
40
|
+
export declare const SingleCourtFilter: (props: SingleCourtFilterProps) => import("react").JSX.Element;
|
|
41
|
+
//#endregion
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { ChevronRightIcon } from "../icons.js";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
import { BidiText } from "./bidi-text.js";
|
|
5
|
+
import { ScrollArea } from "./scroll-area.js";
|
|
6
|
+
import { DirectionalIcon } from "./directional-icon.js";
|
|
7
|
+
import { Checkbox } from "./checkbox.js";
|
|
8
|
+
import { Field, FieldLabel } from "./field.js";
|
|
9
|
+
import { SearchField } from "./search-field.js";
|
|
10
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { useId, useState } from "react";
|
|
12
|
+
//#region src/components/court-filter.tsx
|
|
13
|
+
const toggleCourtSelection = (selected, values) => {
|
|
14
|
+
return values.every((value) => selected.includes(value)) ? selected.filter((value) => !values.includes(value)) : [.../* @__PURE__ */ new Set([...selected, ...values])];
|
|
15
|
+
};
|
|
16
|
+
const SingleCourtGroup = ({ group, options }) => {
|
|
17
|
+
const panelId = useId();
|
|
18
|
+
const [open, setOpen] = useState(!options.collapsedGroups.includes(group.value));
|
|
19
|
+
const [showAll, setShowAll] = useState(false);
|
|
20
|
+
const selectedHere = group.courts.find(({ value }) => value === options.selected);
|
|
21
|
+
const expanded = open || selectedHere !== void 0;
|
|
22
|
+
const items = showAll ? [...group.courts] : group.courts.slice(0, options.visibleRowLimit);
|
|
23
|
+
if (selectedHere !== void 0 && !items.includes(selectedHere)) items.push(selectedHere);
|
|
24
|
+
const hiddenCount = group.courts.length - items.length;
|
|
25
|
+
if (items.length === 0) return null;
|
|
26
|
+
return /* @__PURE__ */ jsxs("div", { children: [group.label !== null && /* @__PURE__ */ jsxs("button", {
|
|
27
|
+
type: "button",
|
|
28
|
+
"aria-controls": panelId,
|
|
29
|
+
"aria-expanded": expanded,
|
|
30
|
+
className: "text-foreground-strong-muted hover:text-foreground flex min-h-11 w-full items-center gap-1 py-1 text-start text-xs font-medium",
|
|
31
|
+
onClick: () => setOpen(!expanded),
|
|
32
|
+
children: [/* @__PURE__ */ jsx(DirectionalIcon, {
|
|
33
|
+
className: cn("size-3 shrink-0 transition-transform", expanded && "rotate-90"),
|
|
34
|
+
flip: !expanded,
|
|
35
|
+
icon: ChevronRightIcon
|
|
36
|
+
}), group.label]
|
|
37
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
38
|
+
hidden: group.label !== null && !expanded,
|
|
39
|
+
id: panelId,
|
|
40
|
+
children: [/* @__PURE__ */ jsx("ul", {
|
|
41
|
+
"aria-label": group.label ?? options.label,
|
|
42
|
+
className: "flex flex-col",
|
|
43
|
+
children: items.map(({ value, label, badgeText }) => {
|
|
44
|
+
const checked = options.selected === value;
|
|
45
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("label", {
|
|
46
|
+
className: "hover:bg-muted/60 has-[:focus-visible]:ring-ring flex min-h-11 cursor-pointer items-center gap-2 rounded-sm py-1.5 ps-1 pe-1 text-xs has-[:focus-visible]:ring-2",
|
|
47
|
+
children: [
|
|
48
|
+
/* @__PURE__ */ jsx("input", {
|
|
49
|
+
type: "radio",
|
|
50
|
+
name: `court-${panelId}`,
|
|
51
|
+
value,
|
|
52
|
+
checked,
|
|
53
|
+
className: "accent-primary size-3.5 shrink-0",
|
|
54
|
+
onChange: () => options.onChange(value),
|
|
55
|
+
onClick: () => {
|
|
56
|
+
if (checked) options.onChange(void 0);
|
|
57
|
+
}
|
|
58
|
+
}),
|
|
59
|
+
/* @__PURE__ */ jsx(BidiText, {
|
|
60
|
+
as: "span",
|
|
61
|
+
className: cn("text-foreground min-w-0 flex-1 truncate", checked && "font-medium"),
|
|
62
|
+
title: label ?? value,
|
|
63
|
+
children: label ?? value
|
|
64
|
+
}),
|
|
65
|
+
badgeText !== void 0 && /* @__PURE__ */ jsx("bdi", {
|
|
66
|
+
className: "text-muted-foreground text-2xs shrink-0 tabular-nums",
|
|
67
|
+
children: badgeText
|
|
68
|
+
})
|
|
69
|
+
]
|
|
70
|
+
}) }, value);
|
|
71
|
+
})
|
|
72
|
+
}), hiddenCount > 0 && /* @__PURE__ */ jsx("button", {
|
|
73
|
+
type: "button",
|
|
74
|
+
className: "text-muted-foreground hover:text-foreground -mx-1 min-h-11 px-1 py-1.5 text-xs",
|
|
75
|
+
onClick: () => setShowAll(true),
|
|
76
|
+
children: options.showAllLabel
|
|
77
|
+
})]
|
|
78
|
+
})] });
|
|
79
|
+
};
|
|
80
|
+
const MultipleCourtGroup = ({ group, courts, options, hintId }) => {
|
|
81
|
+
const labelId = useId();
|
|
82
|
+
const { selected } = options;
|
|
83
|
+
const values = courts.map(({ value }) => value);
|
|
84
|
+
const tierSelected = values.every((value) => selected.includes(value));
|
|
85
|
+
const nextTier = toggleCourtSelection(selected, values);
|
|
86
|
+
const tierDisabled = !tierSelected && nextTier.length > options.maxSelections;
|
|
87
|
+
return /* @__PURE__ */ jsxs("fieldset", {
|
|
88
|
+
className: "min-w-0",
|
|
89
|
+
children: [
|
|
90
|
+
/* @__PURE__ */ jsx("legend", {
|
|
91
|
+
className: "sr-only",
|
|
92
|
+
children: group.label ?? options.label
|
|
93
|
+
}),
|
|
94
|
+
group.label !== null && /* @__PURE__ */ jsxs("label", {
|
|
95
|
+
id: `${labelId}-tier`,
|
|
96
|
+
className: "flex min-h-11 cursor-pointer items-center gap-2 text-xs font-medium",
|
|
97
|
+
children: [/* @__PURE__ */ jsx(Checkbox, {
|
|
98
|
+
"aria-labelledby": `${labelId}-tier`,
|
|
99
|
+
checked: tierSelected,
|
|
100
|
+
indeterminate: !tierSelected && values.some((value) => selected.includes(value)),
|
|
101
|
+
disabled: tierDisabled,
|
|
102
|
+
"aria-describedby": hintId,
|
|
103
|
+
onCheckedChange: () => {
|
|
104
|
+
if (tierSelected || nextTier.length <= options.maxSelections) options.onChange(nextTier);
|
|
105
|
+
}
|
|
106
|
+
}), group.label]
|
|
107
|
+
}),
|
|
108
|
+
/* @__PURE__ */ jsx("ul", {
|
|
109
|
+
className: "flex flex-col",
|
|
110
|
+
children: courts.map(({ value, label, badgeText }, index) => {
|
|
111
|
+
const checked = selected.includes(value);
|
|
112
|
+
const next = toggleCourtSelection(selected, [value]);
|
|
113
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("label", {
|
|
114
|
+
id: `${labelId}-${index}`,
|
|
115
|
+
className: "hover:bg-muted/60 has-[:focus-visible]:ring-ring flex min-h-11 cursor-pointer items-center gap-2 rounded-sm text-sm has-[:focus-visible]:ring-2",
|
|
116
|
+
children: [
|
|
117
|
+
/* @__PURE__ */ jsx(Checkbox, {
|
|
118
|
+
"aria-labelledby": `${labelId}-${index}`,
|
|
119
|
+
checked,
|
|
120
|
+
disabled: !checked && next.length > options.maxSelections,
|
|
121
|
+
"aria-describedby": hintId,
|
|
122
|
+
onCheckedChange: () => {
|
|
123
|
+
if (checked || next.length <= options.maxSelections) options.onChange(next);
|
|
124
|
+
}
|
|
125
|
+
}),
|
|
126
|
+
/* @__PURE__ */ jsx("bdi", {
|
|
127
|
+
className: "min-w-0 flex-1 wrap-break-word",
|
|
128
|
+
children: label ?? value
|
|
129
|
+
}),
|
|
130
|
+
badgeText !== void 0 && /* @__PURE__ */ jsx("bdi", {
|
|
131
|
+
className: "text-muted-foreground text-xs tabular-nums",
|
|
132
|
+
children: badgeText
|
|
133
|
+
})
|
|
134
|
+
]
|
|
135
|
+
}) }, value);
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
]
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Court filtering shares an owner while each surface keeps its interaction
|
|
143
|
+
* model. Each model is its own export, so a bundle carries only the one it
|
|
144
|
+
* renders.
|
|
145
|
+
*/
|
|
146
|
+
const MultipleCourtFilter = (options) => {
|
|
147
|
+
const [query, setQuery] = useState("");
|
|
148
|
+
const hintId = useId();
|
|
149
|
+
const needle = options.matchText(query);
|
|
150
|
+
const missing = options.selected.filter((name) => !options.groups.some((group) => group.courts.some(({ value }) => value === name)));
|
|
151
|
+
const groups = [...options.groups];
|
|
152
|
+
if (missing.length > 0) groups.push({
|
|
153
|
+
value: "selected",
|
|
154
|
+
label: null,
|
|
155
|
+
courts: missing.map((value) => ({ value }))
|
|
156
|
+
});
|
|
157
|
+
const visible = groups.flatMap((group) => {
|
|
158
|
+
const courts = options.matchText(group.label ?? "").includes(needle) ? group.courts : group.courts.filter(({ value, label }) => options.matchText(label ?? value).includes(needle));
|
|
159
|
+
return courts.length === 0 ? [] : [{
|
|
160
|
+
group,
|
|
161
|
+
courts
|
|
162
|
+
}];
|
|
163
|
+
});
|
|
164
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
165
|
+
className: "min-w-0 space-y-2",
|
|
166
|
+
children: [
|
|
167
|
+
/* @__PURE__ */ jsxs(Field, { children: [/* @__PURE__ */ jsx(FieldLabel, {
|
|
168
|
+
className: "sr-only",
|
|
169
|
+
children: options.searchLabel
|
|
170
|
+
}), /* @__PURE__ */ jsx(SearchField, {
|
|
171
|
+
value: query,
|
|
172
|
+
onValueChange: setQuery,
|
|
173
|
+
clearLabel: options.clearLabel,
|
|
174
|
+
placeholder: options.searchLabel
|
|
175
|
+
})] }),
|
|
176
|
+
/* @__PURE__ */ jsx("p", {
|
|
177
|
+
id: hintId,
|
|
178
|
+
role: "status",
|
|
179
|
+
className: "text-muted-foreground text-xs",
|
|
180
|
+
children: options.selectionHint
|
|
181
|
+
}),
|
|
182
|
+
/* @__PURE__ */ jsx(ScrollArea, {
|
|
183
|
+
className: "h-72",
|
|
184
|
+
axis: "vertical",
|
|
185
|
+
"aria-label": options.label,
|
|
186
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
187
|
+
className: "space-y-2",
|
|
188
|
+
children: [visible.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
189
|
+
className: "text-muted-foreground py-2 text-sm",
|
|
190
|
+
children: options.emptyLabel
|
|
191
|
+
}), visible.map(({ group, courts }) => /* @__PURE__ */ jsx(MultipleCourtGroup, {
|
|
192
|
+
group,
|
|
193
|
+
courts,
|
|
194
|
+
options,
|
|
195
|
+
hintId
|
|
196
|
+
}, group.value))]
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
]
|
|
200
|
+
});
|
|
201
|
+
};
|
|
202
|
+
const SingleCourtFilter = (props) => /* @__PURE__ */ jsx("div", {
|
|
203
|
+
className: "flex flex-col gap-3",
|
|
204
|
+
children: props.groups.map((group) => /* @__PURE__ */ jsx(SingleCourtGroup, {
|
|
205
|
+
group,
|
|
206
|
+
options: props
|
|
207
|
+
}, group.value))
|
|
208
|
+
});
|
|
209
|
+
//#endregion
|
|
210
|
+
export { MultipleCourtFilter, SingleCourtFilter, toggleCourtSelection };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/utils.js";
|
|
3
3
|
import { Tooltip, TooltipContent as TooltipPopup, TooltipTrigger } from "./tooltip.js";
|
|
4
|
+
import { withoutDirectionalControls } from "./bidi-text.js";
|
|
4
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
6
|
import { useCallback, useId, useState } from "react";
|
|
6
7
|
//#region src/components/identity-text.tsx
|
|
@@ -10,6 +11,7 @@ const IdentityText = ({ children, className }) => {
|
|
|
10
11
|
const [overflow, setOverflow] = useState(false);
|
|
11
12
|
const [hasFocusOwner, setHasFocusOwner] = useState(false);
|
|
12
13
|
const [open, setOpen] = useState(false);
|
|
14
|
+
const name = withoutDirectionalControls(children);
|
|
13
15
|
const textRef = useCallback((node) => {
|
|
14
16
|
if (node === null) return;
|
|
15
17
|
const measure = () => setOverflow(node.scrollWidth > node.clientWidth);
|
|
@@ -56,14 +58,14 @@ const IdentityText = ({ children, className }) => {
|
|
|
56
58
|
role: overflow && !hasFocusOwner ? "button" : void 0,
|
|
57
59
|
tabIndex: overflow && !hasFocusOwner ? 0 : void 0
|
|
58
60
|
}),
|
|
59
|
-
children
|
|
61
|
+
children: name
|
|
60
62
|
}), /* @__PURE__ */ jsx(TooltipPopup, {
|
|
61
63
|
className: "max-w-sm",
|
|
62
64
|
role: "tooltip",
|
|
63
65
|
children: /* @__PURE__ */ jsx("span", {
|
|
64
66
|
className: "block max-w-sm wrap-break-word",
|
|
65
67
|
dir: "auto",
|
|
66
|
-
children
|
|
68
|
+
children: name
|
|
67
69
|
})
|
|
68
70
|
})]
|
|
69
71
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/utils.js";
|
|
3
|
-
import { ScrollArea } from "./scroll-area.js";
|
|
4
3
|
import { BidiText } from "./bidi-text.js";
|
|
4
|
+
import { ScrollArea } from "./scroll-area.js";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
//#region src/components/landing.tsx
|
|
7
7
|
const LandingLayout = ({ actions, hero, children, footer }) => /* @__PURE__ */ jsxs("div", {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/components/middle-truncate.d.ts
|
|
2
|
+
type MiddleTruncateProps = {
|
|
3
|
+
value: string;
|
|
4
|
+
className?: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* One line of text that, when it does not fit, gives up its middle and keeps
|
|
8
|
+
* its end: `Smlouva o dílo – revize…klienta.docx`. For names whose end tells
|
|
9
|
+
* them apart (file names, versions, case numbers), where a trailing ellipsis
|
|
10
|
+
* would cut exactly that part.
|
|
11
|
+
*
|
|
12
|
+
* The head clips after its last whole character and the tail slides over the
|
|
13
|
+
* slack, so the ellipsis sits flush against the text instead of after a gap
|
|
14
|
+
* or half a glyph. The ellipsis is generated content with empty alternative
|
|
15
|
+
* text: the element's text and accessible name stay the full value.
|
|
16
|
+
*/
|
|
17
|
+
export declare const MiddleTruncate: ({ value, className }: MiddleTruncateProps) => import("react").JSX.Element;
|
|
18
|
+
//#endregion
|
|
19
|
+
export {};
|