@stll/ui 0.67.0 → 0.69.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.d.ts +22 -6
- package/dist/calendar/day-calendar.js +97 -26
- package/dist/calendar/resource-calendar.js +7 -9
- package/dist/calendar/topic-colors.d.ts +3 -0
- package/dist/calendar/topic-colors.js +19 -0
- package/dist/components/button.js +4 -4
- package/dist/components/composer-tokens.d.ts +6 -2
- package/dist/components/composer-tokens.js +6 -2
- package/dist/components/composer.d.ts +13 -0
- package/dist/components/composer.js +19 -3
- package/dist/components/court-filter.js +4 -4
- package/dist/components/date-picker-popover.js +3 -2
- package/dist/components/document-identity-badge.js +9 -4
- package/dist/components/file-input.js +3 -2
- package/dist/components/identity-text.js +4 -2
- package/dist/components/input-group.js +3 -2
- package/dist/components/landing.js +6 -5
- package/dist/components/list.d.ts +11 -5
- package/dist/components/list.js +28 -9
- package/dist/components/loader.js +4 -2
- package/dist/components/outline-rail.js +6 -4
- package/dist/components/run-state-mark.d.ts +8 -0
- package/dist/components/run-state-mark.js +30 -0
- package/dist/components/run-state-mark.native.d.ts +9 -0
- package/dist/components/run-state-mark.native.js +98 -0
- package/dist/components/run-state-mark.shared.d.ts +53 -0
- package/dist/components/run-state-mark.shared.js +45 -0
- package/dist/components/status-mark.js +6 -6
- package/dist/components/status-mark.shared.d.ts +12 -0
- package/dist/components/status-mark.shared.js +14 -0
- package/dist/components/table.d.ts +1 -1
- package/dist/components/table.js +14 -8
- package/dist/components/text.native.d.ts +3 -1
- package/dist/components/text.native.js +4 -2
- package/dist/components/truncated-text.d.ts +14 -3
- package/dist/components/truncated-text.js +21 -5
- package/dist/components/view-toolbar.d.ts +5 -1
- package/dist/components/view-toolbar.js +6 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/inspector/chrome.d.ts +3 -3
- package/dist/inspector/chrome.js +28 -17
- package/dist/kanban/column-band-header.js +3 -2
- package/dist/kanban/column-header.js +3 -2
- package/dist/kanban/sticky-lane.js +3 -2
- package/dist/lib/matter-colors.js +2 -5
- package/dist/lib/stable-hash.d.ts +2 -0
- package/dist/lib/stable-hash.js +8 -0
- package/dist/review/review-comment-card.js +7 -6
- package/dist/review/review-decision-actions.d.ts +8 -1
- package/dist/review/review-decision-actions.js +13 -4
- package/dist/styles/theme.css +24 -0
- package/package.json +11 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React$1 from "react";
|
|
2
|
+
import { TruncatedTextLines } from "./truncated-text.js";
|
|
2
3
|
import { VariantProps } from "class-variance-authority";
|
|
3
4
|
import { useRender } from "@base-ui/react/use-render";
|
|
4
5
|
/**
|
|
@@ -39,15 +40,20 @@ declare const listItemMediaVariants: (props?: ({
|
|
|
39
40
|
/** Fixed 32px tile for an icon, logo or initial. */
|
|
40
41
|
declare const ListItemMedia: ({ className, variant, ...props }: React$1.ComponentProps<"div"> & VariantProps<typeof listItemMediaVariants>) => React$1.JSX.Element;
|
|
41
42
|
declare const ListItemContent: ({ className, ...props }: React$1.ComponentProps<"div">) => React$1.JSX.Element;
|
|
42
|
-
declare const
|
|
43
|
+
declare const listItemTitleVariants: (props?: ({
|
|
44
|
+
variant?: "name" | "statement" | null | undefined;
|
|
45
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
46
|
+
declare const ListItemTitle: ({ className, variant, ...props }: React$1.ComponentProps<"span"> & VariantProps<typeof listItemTitleVariants>) => React$1.JSX.Element;
|
|
43
47
|
declare const listItemDescriptionVariants: (props?: ({
|
|
44
|
-
variant?: "code" | "text" | null | undefined;
|
|
48
|
+
variant?: "code" | "paragraph" | "text" | null | undefined;
|
|
45
49
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
46
50
|
/**
|
|
47
|
-
* Secondary line
|
|
48
|
-
* never `whitespace-nowrap`.
|
|
51
|
+
* Secondary line, one line by default. Use the `paragraph` variant (two
|
|
52
|
+
* lines) or `lines` when more is genuinely needed; never `whitespace-nowrap`.
|
|
49
53
|
*/
|
|
50
|
-
declare const ListItemDescription: ({ className, variant, ...props }: React$1.ComponentProps<"span"> & VariantProps<typeof listItemDescriptionVariants>
|
|
54
|
+
declare const ListItemDescription: ({ className, variant, lines, ...props }: React$1.ComponentProps<"span"> & VariantProps<typeof listItemDescriptionVariants> & {
|
|
55
|
+
lines?: TruncatedTextLines;
|
|
56
|
+
}) => React$1.JSX.Element;
|
|
51
57
|
declare const ListItemActions: ({ className, ...props }: React$1.ComponentProps<"div">) => React$1.JSX.Element;
|
|
52
58
|
/** Single-line empty row inside a `List`. Phrase it as the next action. */
|
|
53
59
|
declare const ListEmpty: ({ className, ...props }: React$1.ComponentProps<"li">) => React$1.JSX.Element;
|
package/dist/components/list.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/utils.js";
|
|
3
3
|
import { AnimatedCount } from "./animated-count.js";
|
|
4
|
+
import { TruncatedText } from "./truncated-text.js";
|
|
4
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
6
|
import { mergeProps } from "@base-ui/react/merge-props";
|
|
6
7
|
import { useRender } from "@base-ui/react/use-render";
|
|
@@ -106,29 +107,47 @@ const ListItemContent = ({ className, ...props }) => /* @__PURE__ */ jsx("div",
|
|
|
106
107
|
"data-slot": "list-item-content",
|
|
107
108
|
...props
|
|
108
109
|
});
|
|
109
|
-
const
|
|
110
|
-
|
|
110
|
+
const listItemTitleVariants = cva("text-foreground text-sm font-medium", {
|
|
111
|
+
defaultVariants: { variant: "name" },
|
|
112
|
+
variants: { variant: {
|
|
113
|
+
/** A name: one line, cut at the end. */
|
|
114
|
+
name: "",
|
|
115
|
+
/** A statement a reader must see whole to judge (a proposed fact): up
|
|
116
|
+
* to two lines, then cut. */
|
|
117
|
+
statement: "text-pretty wrap-break-word"
|
|
118
|
+
} }
|
|
119
|
+
});
|
|
120
|
+
const LIST_ITEM_TITLE_LINES = {
|
|
121
|
+
name: 1,
|
|
122
|
+
statement: 2
|
|
123
|
+
};
|
|
124
|
+
const ListItemTitle = ({ className, variant, ...props }) => /* @__PURE__ */ jsx(TruncatedText, {
|
|
125
|
+
className: cn(listItemTitleVariants({ variant }), className),
|
|
111
126
|
"data-slot": "list-item-title",
|
|
112
127
|
dir: "auto",
|
|
128
|
+
lines: LIST_ITEM_TITLE_LINES[variant ?? "name"],
|
|
113
129
|
...props
|
|
114
130
|
});
|
|
115
131
|
const listItemDescriptionVariants = cva("text-muted-foreground text-xs", {
|
|
116
132
|
defaultVariants: { variant: "text" },
|
|
117
133
|
variants: { variant: {
|
|
118
|
-
/** Prose,
|
|
119
|
-
text: "
|
|
120
|
-
/**
|
|
121
|
-
|
|
134
|
+
/** Prose, cut to one line so long provider copy cannot widen the row. */
|
|
135
|
+
text: "wrap-break-word",
|
|
136
|
+
/** Prose that genuinely needs a second line (a reason, an error). */
|
|
137
|
+
paragraph: "wrap-break-word",
|
|
138
|
+
/** A URL or command: monospace, left-to-right, cut at the end. */
|
|
139
|
+
code: "font-mono"
|
|
122
140
|
} }
|
|
123
141
|
});
|
|
124
142
|
/**
|
|
125
|
-
* Secondary line
|
|
126
|
-
* never `whitespace-nowrap`.
|
|
143
|
+
* Secondary line, one line by default. Use the `paragraph` variant (two
|
|
144
|
+
* lines) or `lines` when more is genuinely needed; never `whitespace-nowrap`.
|
|
127
145
|
*/
|
|
128
|
-
const ListItemDescription = ({ className, variant, ...props }) => /* @__PURE__ */ jsx(
|
|
146
|
+
const ListItemDescription = ({ className, variant, lines, ...props }) => /* @__PURE__ */ jsx(TruncatedText, {
|
|
129
147
|
className: cn(listItemDescriptionVariants({ variant }), className),
|
|
130
148
|
"data-slot": "list-item-description",
|
|
131
149
|
dir: variant === "code" ? "ltr" : "auto",
|
|
150
|
+
lines: lines ?? (variant === "paragraph" ? 2 : 1),
|
|
132
151
|
...props
|
|
133
152
|
});
|
|
134
153
|
const ListItemActions = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { LoaderCircleIcon } from "../icons.js";
|
|
2
2
|
import { cn } from "../lib/utils.js";
|
|
3
3
|
import { StellaMark } from "./stella-mark.js";
|
|
4
|
+
import { TruncatedText } from "./truncated-text.js";
|
|
4
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
6
|
// src/components/loader.tsx
|
|
6
7
|
/**
|
|
@@ -41,8 +42,9 @@ const LoaderState = ({ label, detail, hint, className }) => /* @__PURE__ */ jsxs
|
|
|
41
42
|
className: "text-foreground text-sm font-medium",
|
|
42
43
|
children: label
|
|
43
44
|
}),
|
|
44
|
-
detail !== void 0 && /* @__PURE__ */ jsx(
|
|
45
|
-
className: "text-muted-foreground
|
|
45
|
+
detail !== void 0 && /* @__PURE__ */ jsx(TruncatedText, {
|
|
46
|
+
className: "text-muted-foreground text-xs tabular-nums",
|
|
47
|
+
render: /* @__PURE__ */ jsx("p", {}),
|
|
46
48
|
children: detail
|
|
47
49
|
}),
|
|
48
50
|
hint !== void 0 && /* @__PURE__ */ jsx("p", {
|
|
@@ -7,6 +7,7 @@ import { DirectionalIcon } from "./directional-icon.js";
|
|
|
7
7
|
import { reconcileOutlineItems, retainOutlineIdSet } from "./outline-rail.logic.js";
|
|
8
8
|
import { ScrollArea } from "./scroll-area.js";
|
|
9
9
|
import { Tooltip, TooltipContent as TooltipPopup, TooltipTrigger } from "./tooltip.js";
|
|
10
|
+
import { TruncatedText } from "./truncated-text.js";
|
|
10
11
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
11
12
|
import { memo, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
12
13
|
import { Temporal } from "temporal-polyfill/full";
|
|
@@ -168,14 +169,15 @@ const OutlineRowView = ({ item, hasChildren, isCollapsed, isActive, isHovered, i
|
|
|
168
169
|
onClick: () => onJump(item.id),
|
|
169
170
|
type: "button"
|
|
170
171
|
}),
|
|
171
|
-
children: item.title === void 0 ? /* @__PURE__ */ jsx(
|
|
172
|
-
|
|
172
|
+
children: item.title === void 0 ? /* @__PURE__ */ jsx(TruncatedText, {
|
|
173
|
+
hint: false,
|
|
173
174
|
children: item.label
|
|
174
175
|
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
175
176
|
className: "shrink-0 font-medium",
|
|
176
177
|
children: item.label
|
|
177
|
-
}), /* @__PURE__ */ jsx(
|
|
178
|
-
className: "
|
|
178
|
+
}), /* @__PURE__ */ jsx(TruncatedText, {
|
|
179
|
+
className: "font-normal",
|
|
180
|
+
hint: false,
|
|
179
181
|
children: item.title
|
|
180
182
|
})] })
|
|
181
183
|
}), /* @__PURE__ */ jsx(TooltipPopup, { children: outlineEntryText(item) })] }),
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { RunMarkSize, RunMarkState, RunStateMarkContract } from "./run-state-mark.shared.js";
|
|
2
|
+
/**
|
|
3
|
+
* Where one run of work stands, as a glyph: a bold "!" when it waits on the
|
|
4
|
+
* reader, a turning ring while it works (./run-state-mark.shared.ts). Its
|
|
5
|
+
* meaning is named by the element around it.
|
|
6
|
+
*/
|
|
7
|
+
export declare const RunStateMark: ({ size, state }: RunStateMarkContract) => import("react").JSX.Element;
|
|
8
|
+
export type { RunMarkSize, RunMarkState };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { RUN_MARK_SIZES, RUN_MARK_TONES, RUN_STATE_MARKS } from "./run-state-mark.shared.js";
|
|
3
|
+
import { StatusMark } from "./status-mark.js";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
import { panic } from "better-result";
|
|
6
|
+
// src/components/run-state-mark.tsx
|
|
7
|
+
/**
|
|
8
|
+
* Where one run of work stands, as a glyph: a bold "!" when it waits on the
|
|
9
|
+
* reader, a turning ring while it works (./run-state-mark.shared.ts). Its
|
|
10
|
+
* meaning is named by the element around it.
|
|
11
|
+
*/
|
|
12
|
+
const RunStateMark = ({ size = "row", state }) => {
|
|
13
|
+
const { glyph, tone } = RUN_STATE_MARKS[state];
|
|
14
|
+
const { glyphClass, markClass } = RUN_MARK_SIZES[size];
|
|
15
|
+
const { textClass } = RUN_MARK_TONES[tone];
|
|
16
|
+
switch (glyph) {
|
|
17
|
+
case "attention": return /* @__PURE__ */ jsx("span", {
|
|
18
|
+
"aria-hidden": true,
|
|
19
|
+
className: cn(textClass, glyphClass, "leading-none font-bold"),
|
|
20
|
+
"data-slot": "run-state-mark",
|
|
21
|
+
children: "!"
|
|
22
|
+
});
|
|
23
|
+
case "running": return /* @__PURE__ */ jsx(StatusMark, {
|
|
24
|
+
className: cn(textClass, markClass),
|
|
25
|
+
status: "running"
|
|
26
|
+
});
|
|
27
|
+
default: return panic("Unhandled run mark glyph");
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
export { RunStateMark };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { RunMarkSize, RunMarkState, RunStateMarkContract } from "./run-state-mark.shared.js";
|
|
2
|
+
/**
|
|
3
|
+
* The web's `RunStateMark` (./run-state-mark.tsx) on a phone: the bold "!"
|
|
4
|
+
* in the embedded bold face, or the web `StatusMark`'s running ring drawn
|
|
5
|
+
* with react-native-svg. Hidden from assistive technology, as on the web:
|
|
6
|
+
* the row or heading around it names the state.
|
|
7
|
+
*/
|
|
8
|
+
export declare const RunStateMark: ({ size, state }: RunStateMarkContract) => import("react").JSX.Element;
|
|
9
|
+
export type { RunMarkSize, RunMarkState };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { RUN_MARK_SIZES, RUN_MARK_TONES, RUN_STATE_MARKS } from "./run-state-mark.shared.js";
|
|
2
|
+
import { STATUS_MARK_RING_RADIUS, STATUS_MARK_RUNNING_ARC, STATUS_MARK_SPIN_MS, STATUS_MARK_STROKE_WIDTH, STATUS_MARK_TRACK_OPACITY, STATUS_MARK_VIEWBOX } from "./status-mark.shared.js";
|
|
3
|
+
import { sansFontFamily } from "./text.native.js";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { useEffect, useState } from "react";
|
|
6
|
+
import { panic } from "better-result";
|
|
7
|
+
import { Animated, Easing, Text } from "react-native";
|
|
8
|
+
import Svg, { Circle } from "react-native-svg";
|
|
9
|
+
import { useCSSVariable } from "uniwind";
|
|
10
|
+
// src/components/run-state-mark.native.tsx
|
|
11
|
+
/**
|
|
12
|
+
* The web's `RunStateMark` (./run-state-mark.tsx) on a phone: the bold "!"
|
|
13
|
+
* in the embedded bold face, or the web `StatusMark`'s running ring drawn
|
|
14
|
+
* with react-native-svg. Hidden from assistive technology, as on the web:
|
|
15
|
+
* the row or heading around it names the state.
|
|
16
|
+
*/
|
|
17
|
+
const RunStateMark = ({ size = "row", state }) => {
|
|
18
|
+
const { glyph, tone } = RUN_STATE_MARKS[state];
|
|
19
|
+
const { glyphClass, px } = RUN_MARK_SIZES[size];
|
|
20
|
+
const { textClass, variable } = RUN_MARK_TONES[tone];
|
|
21
|
+
switch (glyph) {
|
|
22
|
+
case "attention": return /* @__PURE__ */ jsx(Text, {
|
|
23
|
+
accessibilityElementsHidden: true,
|
|
24
|
+
className: `${textClass} ${glyphClass} leading-none`,
|
|
25
|
+
importantForAccessibility: "no-hide-descendants",
|
|
26
|
+
style: { fontFamily: sansFontFamily("bold") },
|
|
27
|
+
children: "!"
|
|
28
|
+
});
|
|
29
|
+
case "running": return /* @__PURE__ */ jsx(RunningRing, {
|
|
30
|
+
colorVariable: variable,
|
|
31
|
+
px
|
|
32
|
+
});
|
|
33
|
+
default: return panic("Unhandled run mark glyph");
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const CENTRE = 8;
|
|
37
|
+
const CIRCUMFERENCE = 2 * Math.PI * STATUS_MARK_RING_RADIUS;
|
|
38
|
+
const ARC_DASHES = [CIRCUMFERENCE * STATUS_MARK_RUNNING_ARC, CIRCUMFERENCE * (1 - STATUS_MARK_RUNNING_ARC)];
|
|
39
|
+
/**
|
|
40
|
+
* The arc turns on its faint track. It keeps turning under Reduce Motion, as
|
|
41
|
+
* the platform's own activity indicator does; the ring is too small to move
|
|
42
|
+
* anything else on screen.
|
|
43
|
+
*/
|
|
44
|
+
const RunningRing = ({ colorVariable, px }) => {
|
|
45
|
+
const resolved = useCSSVariable(colorVariable);
|
|
46
|
+
const color = typeof resolved === "string" ? resolved : "currentColor";
|
|
47
|
+
const [turn] = useState(() => new Animated.Value(0));
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const spin = Animated.loop(Animated.timing(turn, {
|
|
50
|
+
duration: STATUS_MARK_SPIN_MS,
|
|
51
|
+
easing: (progress) => Easing.linear(progress),
|
|
52
|
+
isInteraction: false,
|
|
53
|
+
toValue: 1,
|
|
54
|
+
useNativeDriver: true
|
|
55
|
+
}));
|
|
56
|
+
spin.start();
|
|
57
|
+
return () => {
|
|
58
|
+
spin.stop();
|
|
59
|
+
};
|
|
60
|
+
}, [turn]);
|
|
61
|
+
const rotate = turn.interpolate({
|
|
62
|
+
inputRange: [0, 1],
|
|
63
|
+
outputRange: ["0deg", "360deg"]
|
|
64
|
+
});
|
|
65
|
+
return /* @__PURE__ */ jsx(Animated.View, {
|
|
66
|
+
accessibilityElementsHidden: true,
|
|
67
|
+
importantForAccessibility: "no-hide-descendants",
|
|
68
|
+
style: {
|
|
69
|
+
height: px,
|
|
70
|
+
transform: [{ rotate }],
|
|
71
|
+
width: px
|
|
72
|
+
},
|
|
73
|
+
children: /* @__PURE__ */ jsxs(Svg, {
|
|
74
|
+
fill: "none",
|
|
75
|
+
height: px,
|
|
76
|
+
viewBox: STATUS_MARK_VIEWBOX,
|
|
77
|
+
width: px,
|
|
78
|
+
children: [/* @__PURE__ */ jsx(Circle, {
|
|
79
|
+
cx: CENTRE,
|
|
80
|
+
cy: CENTRE,
|
|
81
|
+
opacity: STATUS_MARK_TRACK_OPACITY,
|
|
82
|
+
r: STATUS_MARK_RING_RADIUS,
|
|
83
|
+
stroke: color,
|
|
84
|
+
strokeWidth: STATUS_MARK_STROKE_WIDTH
|
|
85
|
+
}), /* @__PURE__ */ jsx(Circle, {
|
|
86
|
+
cx: CENTRE,
|
|
87
|
+
cy: CENTRE,
|
|
88
|
+
r: STATUS_MARK_RING_RADIUS,
|
|
89
|
+
stroke: color,
|
|
90
|
+
strokeDasharray: ARC_DASHES,
|
|
91
|
+
strokeLinecap: "round",
|
|
92
|
+
strokeWidth: STATUS_MARK_STROKE_WIDTH,
|
|
93
|
+
transform: `rotate(-90 ${CENTRE} ${CENTRE})`
|
|
94
|
+
})]
|
|
95
|
+
})
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
export { RunStateMark };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** The theme colours a run mark takes, as the classes and variables naming them. */
|
|
2
|
+
export declare const RUN_MARK_TONES: {
|
|
3
|
+
readonly muted: {
|
|
4
|
+
readonly textClass: "text-muted-foreground";
|
|
5
|
+
readonly variable: "--color-muted-foreground";
|
|
6
|
+
};
|
|
7
|
+
readonly warning: {
|
|
8
|
+
readonly textClass: "text-warning-foreground";
|
|
9
|
+
readonly variable: "--color-warning-foreground";
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Each run state's glyph and tone. Its keys are the states the API reports
|
|
14
|
+
* (`CHAT_ACTIVE_RUN_STATE` in `@stll/api-contract/chat`, which this published
|
|
15
|
+
* package cannot import). A surface passes the state it read, so a state the
|
|
16
|
+
* API adds stops compiling at every surface until it has a mark here.
|
|
17
|
+
*/
|
|
18
|
+
export declare const RUN_STATE_MARKS: {
|
|
19
|
+
readonly "needs-you": {
|
|
20
|
+
readonly glyph: "attention";
|
|
21
|
+
readonly tone: "warning";
|
|
22
|
+
};
|
|
23
|
+
readonly working: {
|
|
24
|
+
readonly glyph: "running";
|
|
25
|
+
readonly tone: "muted";
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export type RunMarkState = keyof typeof RUN_STATE_MARKS;
|
|
29
|
+
/**
|
|
30
|
+
* `row` sits in a line of text, `badge` on a small icon. The pixel size is a
|
|
31
|
+
* phone's; the web sets the same size with the classes.
|
|
32
|
+
*/
|
|
33
|
+
export declare const RUN_MARK_SIZES: {
|
|
34
|
+
readonly row: {
|
|
35
|
+
readonly glyphClass: "text-xs";
|
|
36
|
+
readonly markClass: "size-3";
|
|
37
|
+
readonly px: 12;
|
|
38
|
+
};
|
|
39
|
+
readonly badge: {
|
|
40
|
+
readonly glyphClass: "text-3xs";
|
|
41
|
+
readonly markClass: "size-2.5";
|
|
42
|
+
readonly px: 10;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
export type RunMarkSize = keyof typeof RUN_MARK_SIZES;
|
|
46
|
+
/**
|
|
47
|
+
* The props every run mark renderer accepts. The mark is decorative: the
|
|
48
|
+
* element around it names the state for assistive technology.
|
|
49
|
+
*/
|
|
50
|
+
export type RunStateMarkContract = {
|
|
51
|
+
state: RunMarkState;
|
|
52
|
+
size?: RunMarkSize;
|
|
53
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// src/components/run-state-mark.shared.ts
|
|
2
|
+
/** The theme colours a run mark takes, as the classes and variables naming them. */
|
|
3
|
+
const RUN_MARK_TONES = {
|
|
4
|
+
muted: {
|
|
5
|
+
textClass: "text-muted-foreground",
|
|
6
|
+
variable: "--color-muted-foreground"
|
|
7
|
+
},
|
|
8
|
+
warning: {
|
|
9
|
+
textClass: "text-warning-foreground",
|
|
10
|
+
variable: "--color-warning-foreground"
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Each run state's glyph and tone. Its keys are the states the API reports
|
|
15
|
+
* (`CHAT_ACTIVE_RUN_STATE` in `@stll/api-contract/chat`, which this published
|
|
16
|
+
* package cannot import). A surface passes the state it read, so a state the
|
|
17
|
+
* API adds stops compiling at every surface until it has a mark here.
|
|
18
|
+
*/
|
|
19
|
+
const RUN_STATE_MARKS = {
|
|
20
|
+
"needs-you": {
|
|
21
|
+
glyph: "attention",
|
|
22
|
+
tone: "warning"
|
|
23
|
+
},
|
|
24
|
+
working: {
|
|
25
|
+
glyph: "running",
|
|
26
|
+
tone: "muted"
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* `row` sits in a line of text, `badge` on a small icon. The pixel size is a
|
|
31
|
+
* phone's; the web sets the same size with the classes.
|
|
32
|
+
*/
|
|
33
|
+
const RUN_MARK_SIZES = {
|
|
34
|
+
row: {
|
|
35
|
+
glyphClass: "text-xs",
|
|
36
|
+
markClass: "size-3",
|
|
37
|
+
px: 12
|
|
38
|
+
},
|
|
39
|
+
badge: {
|
|
40
|
+
glyphClass: "text-3xs",
|
|
41
|
+
markClass: "size-2.5",
|
|
42
|
+
px: 10
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
export { RUN_MARK_SIZES, RUN_MARK_TONES, RUN_STATE_MARKS };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/utils.js";
|
|
3
|
+
import { STATUS_MARK_RING_RADIUS, STATUS_MARK_STROKE_WIDTH, STATUS_MARK_VIEWBOX } from "./status-mark.shared.js";
|
|
3
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
5
|
// src/components/status-mark.tsx
|
|
5
|
-
const RING_RADIUS = 6.25;
|
|
6
6
|
const CHECK_PATH = "M5.4 8.3 7.2 10.1 10.6 6.3";
|
|
7
7
|
const CROSS_PATH = "M6.1 6.1 9.9 9.9M9.9 6.1 6.1 9.9";
|
|
8
8
|
const on = {
|
|
@@ -26,8 +26,8 @@ const StatusMark = ({ className, label, status }) => /* @__PURE__ */ jsxs("svg",
|
|
|
26
26
|
stroke: "currentColor",
|
|
27
27
|
strokeLinecap: "round",
|
|
28
28
|
strokeLinejoin: "round",
|
|
29
|
-
strokeWidth:
|
|
30
|
-
viewBox:
|
|
29
|
+
strokeWidth: STATUS_MARK_STROKE_WIDTH,
|
|
30
|
+
viewBox: STATUS_MARK_VIEWBOX,
|
|
31
31
|
...label === void 0 ? { "aria-hidden": true } : {
|
|
32
32
|
"aria-label": label,
|
|
33
33
|
role: "img"
|
|
@@ -38,14 +38,14 @@ const StatusMark = ({ className, label, status }) => /* @__PURE__ */ jsxs("svg",
|
|
|
38
38
|
cx: "8",
|
|
39
39
|
cy: "8",
|
|
40
40
|
fill: "currentColor",
|
|
41
|
-
r:
|
|
41
|
+
r: STATUS_MARK_RING_RADIUS,
|
|
42
42
|
stroke: "none"
|
|
43
43
|
}),
|
|
44
44
|
/* @__PURE__ */ jsx("circle", {
|
|
45
45
|
className: "opacity-0 transition-opacity duration-200 group-data-[status=running]/status:opacity-20",
|
|
46
46
|
cx: "8",
|
|
47
47
|
cy: "8",
|
|
48
|
-
r:
|
|
48
|
+
r: STATUS_MARK_RING_RADIUS
|
|
49
49
|
}),
|
|
50
50
|
/* @__PURE__ */ jsx("g", {
|
|
51
51
|
className: SPIN_CLASS,
|
|
@@ -54,7 +54,7 @@ const StatusMark = ({ className, label, status }) => /* @__PURE__ */ jsxs("svg",
|
|
|
54
54
|
cx: "8",
|
|
55
55
|
cy: "8",
|
|
56
56
|
pathLength: 100,
|
|
57
|
-
r:
|
|
57
|
+
r: STATUS_MARK_RING_RADIUS,
|
|
58
58
|
transform: "rotate(-90 8 8)"
|
|
59
59
|
})
|
|
60
60
|
}),
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const STATUS_MARK_VIEWBOX = "0 0 16 16";
|
|
2
|
+
export declare const STATUS_MARK_STROKE_WIDTH = 1.5;
|
|
3
|
+
export declare const STATUS_MARK_RING_RADIUS = 6.25;
|
|
4
|
+
/**
|
|
5
|
+
* The running arc's share of the ring and the faint track it runs on. The
|
|
6
|
+
* web spells them as utilities (`[stroke-dasharray:30_70]`, `opacity-20`),
|
|
7
|
+
* which must stay literal for Tailwind to find them.
|
|
8
|
+
*/
|
|
9
|
+
export declare const STATUS_MARK_RUNNING_ARC = 0.3;
|
|
10
|
+
export declare const STATUS_MARK_TRACK_OPACITY = 0.2;
|
|
11
|
+
/** One turn of the running arc: `--animate-status-spin` in the theme. */
|
|
12
|
+
export declare const STATUS_MARK_SPIN_MS = 1100;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/components/status-mark.shared.ts
|
|
2
|
+
const STATUS_MARK_VIEWBOX = "0 0 16 16";
|
|
3
|
+
const STATUS_MARK_STROKE_WIDTH = 1.5;
|
|
4
|
+
const STATUS_MARK_RING_RADIUS = 6.25;
|
|
5
|
+
/**
|
|
6
|
+
* The running arc's share of the ring and the faint track it runs on. The
|
|
7
|
+
* web spells them as utilities (`[stroke-dasharray:30_70]`, `opacity-20`),
|
|
8
|
+
* which must stay literal for Tailwind to find them.
|
|
9
|
+
*/
|
|
10
|
+
const STATUS_MARK_RUNNING_ARC = .3;
|
|
11
|
+
const STATUS_MARK_TRACK_OPACITY = .2;
|
|
12
|
+
/** One turn of the running arc: `--animate-status-spin` in the theme. */
|
|
13
|
+
const STATUS_MARK_SPIN_MS = 1100;
|
|
14
|
+
export { STATUS_MARK_RING_RADIUS, STATUS_MARK_RUNNING_ARC, STATUS_MARK_SPIN_MS, STATUS_MARK_STROKE_WIDTH, STATUS_MARK_TRACK_OPACITY, STATUS_MARK_VIEWBOX };
|
|
@@ -90,7 +90,7 @@ type TableCellProps = Omit<React$1.ComponentProps<"td">, "align"> & {
|
|
|
90
90
|
*/
|
|
91
91
|
flush?: boolean;
|
|
92
92
|
};
|
|
93
|
-
declare const TableCell: ({ className, align, tone, numeric, truncate, flush, ...props }: TableCellProps) => React$1.JSX.Element;
|
|
93
|
+
declare const TableCell: ({ className, align, tone, numeric, truncate, flush, children, ...props }: TableCellProps) => React$1.JSX.Element;
|
|
94
94
|
type SortDirection = "asc" | "desc" | null;
|
|
95
95
|
type SortableHeadProps = Omit<TableHeadProps, "onClick"> & {
|
|
96
96
|
sortDirection?: SortDirection;
|
package/dist/components/table.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ArrowDownIcon, ArrowUpIcon } from "../icons.js";
|
|
2
2
|
import { cn } from "../lib/utils.js";
|
|
3
3
|
import { Button } from "./button.js";
|
|
4
|
+
import { TruncatedText } from "./truncated-text.js";
|
|
4
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
6
|
// src/components/table.tsx
|
|
6
7
|
const Table = ({ className, density = "default", inset = "default", scroll = "self", ...props }) => {
|
|
@@ -49,12 +50,20 @@ const TableHead = ({ className, align, numeric = false, ...props }) => /* @__PUR
|
|
|
49
50
|
"data-slot": "table-head",
|
|
50
51
|
...props
|
|
51
52
|
});
|
|
52
|
-
const TableCell = ({ className, align, tone = "default", numeric = false, truncate = false, flush = false, ...props }) => {
|
|
53
|
+
const TableCell = ({ className, align, tone = "default", numeric = false, truncate = false, flush = false, children, ...props }) => {
|
|
53
54
|
const resolvedAlign = resolveTableAlign(align, numeric);
|
|
55
|
+
const cellClassName = cn("align-middle leading-none whitespace-nowrap has-[[role=checkbox]]:pe-0", flush ? "p-0" : "p-2.5 in-data-[density=compact]:px-0 in-data-[density=compact]:py-1.5 in-data-[inset=card]:px-4 in-data-[slot=frame]:first:p-[calc(--spacing(2.5)-1px)] in-data-[slot=frame]:last:p-[calc(--spacing(2.5)-1px)]", "in-data-[density=compact]:leading-6 in-data-[density=compact]:whitespace-normal", resolvedAlign !== void 0 && TABLE_ALIGN_CLASS[resolvedAlign], tone === "muted" && "text-muted-foreground", numeric && "tabular-nums", className);
|
|
56
|
+
if (truncate) return /* @__PURE__ */ jsx(TruncatedText, {
|
|
57
|
+
className: cn("table-cell", cellClassName),
|
|
58
|
+
"data-slot": "table-cell",
|
|
59
|
+
render: /* @__PURE__ */ jsx("td", { ...props }),
|
|
60
|
+
children
|
|
61
|
+
});
|
|
54
62
|
return /* @__PURE__ */ jsx("td", {
|
|
55
|
-
className:
|
|
63
|
+
className: cellClassName,
|
|
56
64
|
"data-slot": "table-cell",
|
|
57
|
-
...props
|
|
65
|
+
...props,
|
|
66
|
+
children
|
|
58
67
|
});
|
|
59
68
|
};
|
|
60
69
|
/**
|
|
@@ -82,14 +91,11 @@ const SortableHead = ({ children, sortDirection = null, onSort, trailing, classN
|
|
|
82
91
|
children: /* @__PURE__ */ jsxs("span", {
|
|
83
92
|
className: "inline-flex items-center gap-1",
|
|
84
93
|
children: [/* @__PURE__ */ jsxs(Button, {
|
|
85
|
-
className: cn("min-w-0 gap-1", endAligned ? "-me-
|
|
94
|
+
className: cn("min-w-0 gap-1 px-2.5", endAligned ? "-me-2.5 flex-row-reverse" : "-ms-2.5"),
|
|
86
95
|
onClick: onSort,
|
|
87
96
|
type: "button",
|
|
88
97
|
variant: "muted",
|
|
89
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
90
|
-
className: "truncate",
|
|
91
|
-
children
|
|
92
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
98
|
+
children: [/* @__PURE__ */ jsx(TruncatedText, { children }), /* @__PURE__ */ jsx("span", {
|
|
93
99
|
"aria-hidden": true,
|
|
94
100
|
className: "inline-flex w-3 shrink-0 justify-center",
|
|
95
101
|
children: sortIcon
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { TextContract, TextVariant } from "./text.shared.js";
|
|
1
|
+
import { TextContract, TextFace, TextVariant } from "./text.shared.js";
|
|
2
2
|
import { TextProps } from "react-native";
|
|
3
|
+
/** The embedded face of one weight, for a glyph set outside every role. */
|
|
4
|
+
export declare const sansFontFamily: (weight: TextFace["weight"]) => string;
|
|
3
5
|
/** The font a role is set in, for a native control that draws its own text. */
|
|
4
6
|
export declare const textFontFamily: (variant: TextContract["variant"]) => string;
|
|
5
7
|
/**
|
|
@@ -22,8 +22,10 @@ const NOT_A_COLOUR = /^text-(?:xs|sm|base|lg|\d*xl|\[|left|center|right|justify|
|
|
|
22
22
|
* whatever order they were written in.
|
|
23
23
|
*/
|
|
24
24
|
const withForeground = (classes) => classes.split(/\s+/u).some((name) => name.startsWith("text-") && !NOT_A_COLOUR.test(name)) ? classes : `text-foreground ${classes}`;
|
|
25
|
+
/** The embedded face of one weight, for a glyph set outside every role. */
|
|
26
|
+
const sansFontFamily = (weight) => SANS_FACES[weight];
|
|
25
27
|
/** The font a role is set in, for a native control that draws its own text. */
|
|
26
|
-
const textFontFamily = (variant) =>
|
|
28
|
+
const textFontFamily = (variant) => sansFontFamily(TEXT_VARIANT_FACES[variant ?? "body"].weight);
|
|
27
29
|
/**
|
|
28
30
|
* Text in one of the design system's roles (./text.shared.ts), as the web
|
|
29
31
|
* renderer (./text.tsx) sets it: the same size and colour, in the role's
|
|
@@ -34,4 +36,4 @@ const Text = ({ className, style, variant = "body", ...props }) => /* @__PURE__
|
|
|
34
36
|
style: [{ fontFamily: textFontFamily(variant) }, style],
|
|
35
37
|
...props
|
|
36
38
|
});
|
|
37
|
-
export { Text, textFontFamily };
|
|
39
|
+
export { Text, sansFontFamily, textFontFamily };
|
|
@@ -14,10 +14,21 @@ import { useRender } from "@base-ui/react/use-render";
|
|
|
14
14
|
* The tap popover stays out of the way when the text sits inside a link,
|
|
15
15
|
* button or other control: the tap belongs to that control.
|
|
16
16
|
*/
|
|
17
|
-
declare const TruncatedText: ({ children, className, lines, render, side, tooltip, ...props }: TruncatedTextProps) => React$1.JSX.Element;
|
|
18
|
-
/**
|
|
19
|
-
|
|
17
|
+
declare const TruncatedText: ({ children, className, hint, lines, render, side, tooltip, ...props }: TruncatedTextProps) => React$1.JSX.Element;
|
|
18
|
+
/**
|
|
19
|
+
* How many lines the text may take before it is cut; `"none"` lets it take
|
|
20
|
+
* as many as it needs, for text that is cut only in some states (collapsed,
|
|
21
|
+
* compact) and shown whole in the others.
|
|
22
|
+
*/
|
|
23
|
+
type TruncatedTextLines = 1 | 2 | 3 | 4 | 5 | 6 | 8 | "none";
|
|
20
24
|
type TruncatedTextProps = useRender.ComponentProps<"span"> & {
|
|
25
|
+
/**
|
|
26
|
+
* Whether cut text shows its whole self on hover, focus or tap. Pass false
|
|
27
|
+
* only where something around the text already shows it (a tooltip on the
|
|
28
|
+
* row, a title the text sits in); the text stays inside its container
|
|
29
|
+
* either way.
|
|
30
|
+
*/
|
|
31
|
+
hint?: boolean;
|
|
21
32
|
lines?: TruncatedTextLines;
|
|
22
33
|
side?: React$1.ComponentProps<typeof TooltipPopup>["side"];
|
|
23
34
|
/**
|
|
@@ -21,7 +21,7 @@ import { useRender } from "@base-ui/react/use-render";
|
|
|
21
21
|
* The tap popover stays out of the way when the text sits inside a link,
|
|
22
22
|
* button or other control: the tap belongs to that control.
|
|
23
23
|
*/
|
|
24
|
-
const TruncatedText = ({ children, className, lines = 1, render, side = "top", tooltip, ...props }) => {
|
|
24
|
+
const TruncatedText = ({ children, className, hint = true, lines = 1, render, side = "top", tooltip, ...props }) => {
|
|
25
25
|
const ref = React$1.useRef(null);
|
|
26
26
|
const [surface, setSurface] = React$1.useState(null);
|
|
27
27
|
const content = tooltip ?? children;
|
|
@@ -31,7 +31,7 @@ const TruncatedText = ({ children, className, lines = 1, render, side = "top", t
|
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
33
|
const element = ref.current;
|
|
34
|
-
if (element === null || LAST_POINTER_TYPE.get(element) === "touch") return;
|
|
34
|
+
if (element === null || LAST_POINTER_TYPE.get(element) === "touch" || isInsideTooltipTrigger(element)) return;
|
|
35
35
|
if (isTextOverflowing(element)) setSurface("tooltip");
|
|
36
36
|
};
|
|
37
37
|
const handleClick = (event) => {
|
|
@@ -49,6 +49,7 @@ const TruncatedText = ({ children, className, lines = 1, render, side = "top", t
|
|
|
49
49
|
ref,
|
|
50
50
|
render
|
|
51
51
|
});
|
|
52
|
+
if (!hint) return text;
|
|
52
53
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Tooltip, {
|
|
53
54
|
onOpenChange: handleTooltipOpenChange,
|
|
54
55
|
open: surface === "tooltip",
|
|
@@ -92,10 +93,20 @@ function recordPointerType(event) {
|
|
|
92
93
|
function forgetPointerType(event) {
|
|
93
94
|
LAST_POINTER_TYPE.delete(event.currentTarget);
|
|
94
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* `max-w-full` caps the text at its container even where the container does
|
|
98
|
+
* not stretch it (a flex column with `items-start`, an inline-block): without
|
|
99
|
+
* it a nowrap line sizes to its whole text and runs past the container.
|
|
100
|
+
*/
|
|
95
101
|
const TRUNCATED_TEXT_LINES_CLASS = {
|
|
96
|
-
1: "block min-w-0 truncate",
|
|
97
|
-
2: "line-clamp-2 min-w-0 wrap-anywhere",
|
|
98
|
-
3: "line-clamp-3 min-w-0 wrap-anywhere"
|
|
102
|
+
1: "block min-w-0 max-w-full truncate",
|
|
103
|
+
2: "line-clamp-2 min-w-0 max-w-full wrap-anywhere",
|
|
104
|
+
3: "line-clamp-3 min-w-0 max-w-full wrap-anywhere",
|
|
105
|
+
4: "line-clamp-4 min-w-0 max-w-full wrap-anywhere",
|
|
106
|
+
5: "line-clamp-5 min-w-0 max-w-full wrap-anywhere",
|
|
107
|
+
6: "line-clamp-6 min-w-0 max-w-full wrap-anywhere",
|
|
108
|
+
8: "line-clamp-8 min-w-0 max-w-full wrap-anywhere",
|
|
109
|
+
none: "min-w-0 max-w-full"
|
|
99
110
|
};
|
|
100
111
|
/** Whether the element's text is cut by its box, in either direction. */
|
|
101
112
|
function isTextOverflowing(element) {
|
|
@@ -103,6 +114,11 @@ function isTextOverflowing(element) {
|
|
|
103
114
|
return element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight;
|
|
104
115
|
}
|
|
105
116
|
const CONTROL_SELECTOR = "a[href], button, label, summary, [role=button], [role=link], [role=menuitem], [role=option], [role=tab]";
|
|
117
|
+
/** Whether a tooltip around the element already opens on its pointer. */
|
|
118
|
+
function isInsideTooltipTrigger(element) {
|
|
119
|
+
const parent = element.parentElement;
|
|
120
|
+
return parent !== null && parent.closest("[data-slot=\"tooltip-trigger\"]") !== null;
|
|
121
|
+
}
|
|
106
122
|
/** Whether a tap on the element already belongs to a control around it. */
|
|
107
123
|
function isInsideControl(element) {
|
|
108
124
|
return element.closest(CONTROL_SELECTOR) !== null;
|