@tangle-network/ui 11.39.0 → 11.40.1
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/CHANGELOG.md +27 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -1
- package/dist/lib/control-presentation.d.ts +1 -1
- package/dist/lib/control-presentation.js +9 -7
- package/dist/presentation.d.ts +1 -1
- package/dist/presentation.js +9 -8
- package/dist/primitives/avatar.d.ts +9 -1
- package/dist/primitives/avatar.js +6 -2
- package/dist/primitives/button.d.ts +2 -1
- package/dist/primitives/field.d.ts +27 -0
- package/dist/primitives/field.js +54 -0
- package/dist/primitives/info-tip.js +50 -0
- package/dist/primitives/page-shell.d.ts +19 -3
- package/dist/primitives/page-shell.js +80 -22
- package/dist/primitives/segmented-control.js +1 -1
- package/dist/primitives/settings.d.ts +125 -0
- package/dist/primitives/settings.js +316 -0
- package/dist/primitives/switch.js +1 -1
- package/dist/primitives/upload-progress.js +11 -6
- package/dist/primitives.d.ts +4 -2
- package/dist/primitives.js +3 -1
- package/package.json +2 -2
- package/src/lib/control-presentation.ts +9 -7
- package/src/presentation.ts +10 -8
- package/src/primitives/avatar.tsx +10 -5
- package/src/primitives/button.tsx +2 -1
- package/src/primitives/control-presentation.stories.tsx +8 -1
- package/src/primitives/field.tsx +63 -0
- package/src/primitives/index.ts +16 -0
- package/src/primitives/info-tip.tsx +40 -0
- package/src/primitives/page-shell.tsx +70 -8
- package/src/primitives/settings.stories.tsx +267 -0
- package/src/primitives/settings.tsx +404 -0
- package/src/primitives/switch.tsx +3 -0
- package/src/primitives/upload-progress.stories.tsx +36 -1
- package/src/primitives/upload-progress.tsx +13 -8
- package/src/styles/compat.css +5 -5
- package/src/styles/presentation.css +43 -2
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
//#region src/primitives/settings.d.ts
|
|
3
|
+
export type SettingsRowLayout = "inline" | "compact" | "stacked";
|
|
4
|
+
export interface SettingsRowProps {
|
|
5
|
+
/** The row's id, for a deep link such as `#timezone`. */
|
|
6
|
+
id?: string;
|
|
7
|
+
label: string;
|
|
8
|
+
/** One line that states a consequence the person needs to act. Most rows need none. */
|
|
9
|
+
description?: React.ReactNode;
|
|
10
|
+
/** Help that explains the setting, shown from an info button beside the label. */
|
|
11
|
+
info?: React.ReactNode;
|
|
12
|
+
/**
|
|
13
|
+
* The control's id. The label targets it, and the row's description, error
|
|
14
|
+
* or lock reason renders at {@link fieldMessageId}. When the row's only child
|
|
15
|
+
* carries this id, the row adds that message to its `aria-describedby` and
|
|
16
|
+
* marks it invalid on error; otherwise point the control there yourself.
|
|
17
|
+
*/
|
|
18
|
+
htmlFor?: string;
|
|
19
|
+
/** A save state after the label, such as "Saving…" or "Saved". It is announced politely. */
|
|
20
|
+
status?: React.ReactNode;
|
|
21
|
+
/** Replaces the description in danger text and is announced. */
|
|
22
|
+
error?: React.ReactNode;
|
|
23
|
+
/** Shows a Retry action after the error. */
|
|
24
|
+
onRetry?: () => void;
|
|
25
|
+
/**
|
|
26
|
+
* `inline` (default): text left, control right, stacked below 34rem.
|
|
27
|
+
* `compact`: the control keeps its own width and stays on the line (switches, single buttons).
|
|
28
|
+
* `stacked`: the control spans the row under the text (textareas, lists).
|
|
29
|
+
*/
|
|
30
|
+
layout?: SettingsRowLayout;
|
|
31
|
+
/** Why the control is unavailable, naming who can change it. Replaces the description after a lock glyph. */
|
|
32
|
+
disabledReason?: React.ReactNode;
|
|
33
|
+
children?: React.ReactNode;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* One setting: its label, an optional consequence line and its control. The
|
|
37
|
+
* label carries the save status after it; an error or a lock reason replaces
|
|
38
|
+
* the description. Rows inside a {@link SettingsSection} are divided by a
|
|
39
|
+
* hairline and stack when the card is narrower than 34rem.
|
|
40
|
+
*/
|
|
41
|
+
export declare function SettingsRow({ id, label, description, info, htmlFor, status, error, onRetry, layout, disabledReason, children }: SettingsRowProps): React.JSX.Element;
|
|
42
|
+
export interface SettingsSectionProps {
|
|
43
|
+
/** The section's anchor: `#id` scrolls to it. The title's id is `${id}-title`. */
|
|
44
|
+
id: string;
|
|
45
|
+
title: string;
|
|
46
|
+
/** One short line, at most about twelve words. Leave it out when the title says enough. */
|
|
47
|
+
description?: React.ReactNode;
|
|
48
|
+
/** Help that explains the section, shown from an info button beside the title. */
|
|
49
|
+
info?: React.ReactNode;
|
|
50
|
+
/** Section actions, top right, such as "Edit brief" or "Add source". */
|
|
51
|
+
actions?: React.ReactNode;
|
|
52
|
+
/** `danger` draws the card's edge and title in the danger tone. */
|
|
53
|
+
tone?: "default" | "danger";
|
|
54
|
+
/** The card's footer on the muted plane, such as the one save bar a section may have. */
|
|
55
|
+
footer?: React.ReactNode;
|
|
56
|
+
/** Rows, lists and any other blocks of the section. */
|
|
57
|
+
children?: React.ReactNode;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* One settings card: a 16px title with optional description, info tip and
|
|
61
|
+
* actions, then its rows and lists, then an optional footer. The card is the
|
|
62
|
+
* width container its rows and lists respond to.
|
|
63
|
+
*/
|
|
64
|
+
export declare function SettingsSection({ id, title, description, info, actions, tone, footer, children }: SettingsSectionProps): React.JSX.Element;
|
|
65
|
+
/** Track widths for a {@link SettingsList}. Every slot its items use needs a column, except status and menu, which have fallbacks. */
|
|
66
|
+
export interface SettingsListColumns {
|
|
67
|
+
/** A logo or avatar column, such as `"36px"`. */
|
|
68
|
+
leading?: string;
|
|
69
|
+
/** The title column; `minmax(0,1fr)` by default. */
|
|
70
|
+
main?: string;
|
|
71
|
+
/** A fixed state column, such as `"8rem"`. Below 34rem the state moves under the title, full width. */
|
|
72
|
+
status?: string;
|
|
73
|
+
/** A fixed column for the at-rest action, such as `"9rem"`. Below 34rem it shrinks to its content. */
|
|
74
|
+
action?: string;
|
|
75
|
+
/** A column for the ⋯ menu, such as `"36px"`. Without it the menu ends the action cell. */
|
|
76
|
+
menu?: string;
|
|
77
|
+
}
|
|
78
|
+
export interface SettingsListProps {
|
|
79
|
+
columns: SettingsListColumns;
|
|
80
|
+
"aria-label"?: string;
|
|
81
|
+
children?: React.ReactNode;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Comparable rows whose status and action columns line up across items (CSS
|
|
85
|
+
* subgrid). Below 34rem each item becomes two lines: the title and meta with
|
|
86
|
+
* its action on the right, then the state under them at full width.
|
|
87
|
+
*/
|
|
88
|
+
export declare function SettingsList({ columns, children, "aria-label": ariaLabel }: SettingsListProps): React.JSX.Element;
|
|
89
|
+
export interface SettingsListMenuItem {
|
|
90
|
+
label: string;
|
|
91
|
+
onSelect: () => void;
|
|
92
|
+
/** Destructive actions (delete, remove, revoke) belong here, in the danger tone. */
|
|
93
|
+
tone?: "danger";
|
|
94
|
+
/** Shown under the label; the item stays focusable so the reason is read, and selecting it does nothing. */
|
|
95
|
+
disabledReason?: React.ReactNode;
|
|
96
|
+
}
|
|
97
|
+
export interface SettingsListAction {
|
|
98
|
+
label: string;
|
|
99
|
+
onSelect: () => void;
|
|
100
|
+
/** Why the action is unavailable, naming who can take it. The button stays focusable, does nothing, and the reason shows under the meta. */
|
|
101
|
+
disabledReason?: React.ReactNode;
|
|
102
|
+
}
|
|
103
|
+
export interface SettingsListItemProps {
|
|
104
|
+
/** A logo or avatar, in the list's `leading` column. */
|
|
105
|
+
leading?: React.ReactNode;
|
|
106
|
+
/** The item's name. Make it the link to the item's detail. */
|
|
107
|
+
title: React.ReactNode;
|
|
108
|
+
/** One line under the title, such as an address or a schedule. */
|
|
109
|
+
meta?: React.ReactNode;
|
|
110
|
+
/** The item's state as plain text, or a control that sets it (a role select). */
|
|
111
|
+
status?: React.ReactNode;
|
|
112
|
+
/** Shown under the meta in danger text and announced. */
|
|
113
|
+
error?: React.ReactNode;
|
|
114
|
+
/** The one verb the item needs at rest. It is drawn as an outline button, so it never reads as the state beside it. */
|
|
115
|
+
action?: SettingsListAction;
|
|
116
|
+
/** Everything else, including destructive actions, behind a ⋯ button. */
|
|
117
|
+
menu?: SettingsListMenuItem[];
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* One item in a {@link SettingsList}; each slot goes to the list's column of
|
|
121
|
+
* that name. Without a status column the state follows the meta, and without
|
|
122
|
+
* a menu column the ⋯ menu ends the action cell.
|
|
123
|
+
*/
|
|
124
|
+
export declare function SettingsListItem({ leading, title, meta, status, error, action, menu }: SettingsListItemProps): React.JSX.Element;
|
|
125
|
+
//#endregion
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/utils.js";
|
|
3
|
+
import { Button } from "./button.js";
|
|
4
|
+
import { Card, CardFooter, CardTitle } from "./card.js";
|
|
5
|
+
import { Label } from "./label.js";
|
|
6
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "./dropdown-menu.js";
|
|
7
|
+
import { InfoTip } from "./info-tip.js";
|
|
8
|
+
import { fieldMessageId } from "./field.js";
|
|
9
|
+
import * as React from "react";
|
|
10
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { CircleAlert, Ellipsis, Lock } from "lucide-react";
|
|
12
|
+
//#region src/primitives/settings.tsx
|
|
13
|
+
const SCROLL_OFFSET = "scroll-mt-[calc(var(--shell-header-height)_+_1rem)]";
|
|
14
|
+
const DIVIDED = "not-first:border-t not-first:border-[color:var(--border-subtle)]";
|
|
15
|
+
const ERROR_TEXT = "text-[color:var(--surface-danger-text)]";
|
|
16
|
+
function ErrorLine({ id, className, children }) {
|
|
17
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
18
|
+
id,
|
|
19
|
+
role: "alert",
|
|
20
|
+
className: cn("flex items-start gap-1.5 text-sm leading-5 [overflow-wrap:anywhere]", ERROR_TEXT, className),
|
|
21
|
+
children: [/* @__PURE__ */ jsx(CircleAlert, {
|
|
22
|
+
"aria-hidden": "true",
|
|
23
|
+
className: "mt-0.5 size-4 shrink-0"
|
|
24
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
25
|
+
className: "min-w-0",
|
|
26
|
+
children
|
|
27
|
+
})]
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function LockLine({ id, className, children }) {
|
|
31
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
32
|
+
id,
|
|
33
|
+
className: cn("flex items-start gap-1.5 text-sm leading-5 text-muted-foreground [overflow-wrap:anywhere]", className),
|
|
34
|
+
children: [/* @__PURE__ */ jsx(Lock, {
|
|
35
|
+
"aria-hidden": "true",
|
|
36
|
+
className: "mt-0.5 size-4 shrink-0"
|
|
37
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
38
|
+
className: "min-w-0",
|
|
39
|
+
children
|
|
40
|
+
})]
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const ROW_COLUMNS = {
|
|
44
|
+
inline: "grid-cols-[minmax(0,1fr)_minmax(13rem,20rem)] @max-[34rem]/settings-section:grid-cols-1",
|
|
45
|
+
compact: "grid-cols-[minmax(0,1fr)_auto]",
|
|
46
|
+
stacked: "grid-cols-1"
|
|
47
|
+
};
|
|
48
|
+
const ROW_CONTROL = {
|
|
49
|
+
inline: "justify-end @max-[34rem]/settings-section:flex-col @max-[34rem]/settings-section:items-stretch",
|
|
50
|
+
compact: "justify-end",
|
|
51
|
+
stacked: "flex-col items-stretch"
|
|
52
|
+
};
|
|
53
|
+
/** Point the row's own control at the row's message without changing anything else about it. */
|
|
54
|
+
function describeControl(children, htmlFor, messageId, invalid) {
|
|
55
|
+
if (!htmlFor || !messageId || !React.isValidElement(children) || children.props.id !== htmlFor) return children;
|
|
56
|
+
const ids = new Set([...(children.props["aria-describedby"] ?? "").split(/\s+/), messageId].filter(Boolean));
|
|
57
|
+
return React.cloneElement(children, {
|
|
58
|
+
"aria-describedby": [...ids].join(" "),
|
|
59
|
+
...invalid && children.props["aria-invalid"] === void 0 ? { "aria-invalid": true } : {}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* One setting: its label, an optional consequence line and its control. The
|
|
64
|
+
* label carries the save status after it; an error or a lock reason replaces
|
|
65
|
+
* the description. Rows inside a {@link SettingsSection} are divided by a
|
|
66
|
+
* hairline and stack when the card is narrower than 34rem.
|
|
67
|
+
*/
|
|
68
|
+
function SettingsRow({ id, label, description, info, htmlFor, status, error, onRetry, layout = "inline", disabledReason, children }) {
|
|
69
|
+
const messageId = htmlFor ? fieldMessageId(htmlFor) : void 0;
|
|
70
|
+
const control = describeControl(children, htmlFor, error != null || disabledReason != null || description != null ? messageId : void 0, error != null);
|
|
71
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
72
|
+
id,
|
|
73
|
+
"data-layout": layout,
|
|
74
|
+
className: cn("grid min-w-0 items-center gap-x-8 gap-y-3 px-5 py-4 @max-[34rem]/settings-section:px-4", SCROLL_OFFSET, DIVIDED, ROW_COLUMNS[layout]),
|
|
75
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
76
|
+
className: "min-w-0",
|
|
77
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
78
|
+
className: "flex min-h-5 flex-wrap items-center gap-x-2 gap-y-1",
|
|
79
|
+
children: [
|
|
80
|
+
htmlFor ? /* @__PURE__ */ jsx(Label, {
|
|
81
|
+
htmlFor,
|
|
82
|
+
className: "leading-5 text-foreground",
|
|
83
|
+
children: label
|
|
84
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
85
|
+
className: "font-medium text-sm leading-5 text-foreground",
|
|
86
|
+
children: label
|
|
87
|
+
}),
|
|
88
|
+
info != null && /* @__PURE__ */ jsx(InfoTip, {
|
|
89
|
+
label,
|
|
90
|
+
children: info
|
|
91
|
+
}),
|
|
92
|
+
/* @__PURE__ */ jsx("span", {
|
|
93
|
+
"aria-live": "polite",
|
|
94
|
+
className: "inline-flex items-center gap-1 text-sm leading-5 text-muted-foreground",
|
|
95
|
+
children: status
|
|
96
|
+
})
|
|
97
|
+
]
|
|
98
|
+
}), error != null ? /* @__PURE__ */ jsxs(ErrorLine, {
|
|
99
|
+
id: messageId,
|
|
100
|
+
className: "mt-0.5",
|
|
101
|
+
children: [error, onRetry && /* @__PURE__ */ jsxs(Fragment$1, { children: [" ", /* @__PURE__ */ jsx(Button, {
|
|
102
|
+
type: "button",
|
|
103
|
+
variant: "bare",
|
|
104
|
+
onClick: onRetry,
|
|
105
|
+
className: "rounded-sm font-medium text-[var(--accent-text)] underline-offset-4 hover:underline",
|
|
106
|
+
children: "Retry"
|
|
107
|
+
})] })]
|
|
108
|
+
}) : disabledReason != null ? /* @__PURE__ */ jsx(LockLine, {
|
|
109
|
+
id: messageId,
|
|
110
|
+
className: "mt-0.5",
|
|
111
|
+
children: disabledReason
|
|
112
|
+
}) : description != null && /* @__PURE__ */ jsx("div", {
|
|
113
|
+
id: messageId,
|
|
114
|
+
className: "mt-0.5 text-sm leading-5 text-muted-foreground [overflow-wrap:anywhere]",
|
|
115
|
+
children: description
|
|
116
|
+
})]
|
|
117
|
+
}), control != null && control !== false && /* @__PURE__ */ jsx("div", {
|
|
118
|
+
className: cn("flex min-w-0 items-center gap-2", ROW_CONTROL[layout]),
|
|
119
|
+
children: control
|
|
120
|
+
})]
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* One settings card: a 16px title with optional description, info tip and
|
|
125
|
+
* actions, then its rows and lists, then an optional footer. The card is the
|
|
126
|
+
* width container its rows and lists respond to.
|
|
127
|
+
*/
|
|
128
|
+
function SettingsSection({ id, title, description, info, actions, tone = "default", footer, children }) {
|
|
129
|
+
const titleId = `${id}-title`;
|
|
130
|
+
const danger = tone === "danger";
|
|
131
|
+
return /* @__PURE__ */ jsxs(Card, {
|
|
132
|
+
id,
|
|
133
|
+
role: "region",
|
|
134
|
+
"aria-labelledby": titleId,
|
|
135
|
+
"data-tone": tone,
|
|
136
|
+
className: cn("@container/settings-section min-w-0", SCROLL_OFFSET, danger && "border-[color:var(--surface-danger-border)]"),
|
|
137
|
+
children: [
|
|
138
|
+
/* @__PURE__ */ jsxs("div", {
|
|
139
|
+
className: "flex items-start justify-between gap-4 px-5 py-4 @max-[34rem]/settings-section:flex-wrap @max-[34rem]/settings-section:px-4",
|
|
140
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
141
|
+
className: "min-w-0",
|
|
142
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
143
|
+
className: "flex items-center gap-1.5",
|
|
144
|
+
children: [/* @__PURE__ */ jsx(CardTitle, {
|
|
145
|
+
as: "h2",
|
|
146
|
+
id: titleId,
|
|
147
|
+
className: cn("leading-6 [overflow-wrap:anywhere]", danger && ERROR_TEXT),
|
|
148
|
+
children: title
|
|
149
|
+
}), info != null && /* @__PURE__ */ jsx(InfoTip, {
|
|
150
|
+
label: title,
|
|
151
|
+
children: info
|
|
152
|
+
})]
|
|
153
|
+
}), description != null && /* @__PURE__ */ jsx("div", {
|
|
154
|
+
className: "mt-0.5 text-sm leading-5 text-muted-foreground [overflow-wrap:anywhere]",
|
|
155
|
+
children: description
|
|
156
|
+
})]
|
|
157
|
+
}), actions != null && /* @__PURE__ */ jsx("div", {
|
|
158
|
+
className: "flex shrink-0 flex-wrap items-center gap-2",
|
|
159
|
+
children: actions
|
|
160
|
+
})]
|
|
161
|
+
}),
|
|
162
|
+
children,
|
|
163
|
+
footer != null && /* @__PURE__ */ jsx(CardFooter, {
|
|
164
|
+
className: "justify-between gap-3 rounded-b-[inherit] border-t border-[color:var(--border-subtle)] bg-muted px-5 py-3 text-sm leading-5 [&:not(:first-child)]:pt-3 @max-[34rem]/settings-section:px-4",
|
|
165
|
+
children: footer
|
|
166
|
+
})
|
|
167
|
+
]
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
const LIST_SLOTS = [
|
|
171
|
+
"leading",
|
|
172
|
+
"main",
|
|
173
|
+
"status",
|
|
174
|
+
"action",
|
|
175
|
+
"menu"
|
|
176
|
+
];
|
|
177
|
+
const LENGTH = /^[\d.]+(?:px|rem|em|ch)$/;
|
|
178
|
+
function listTracks(columns, narrow) {
|
|
179
|
+
const tracks = LIST_SLOTS.flatMap((slot) => {
|
|
180
|
+
if (slot === "main") return [[slot, columns.main ?? "minmax(0,1fr)"]];
|
|
181
|
+
const width = columns[slot];
|
|
182
|
+
if (!width || narrow && slot === "status") return [];
|
|
183
|
+
return [[slot, narrow && slot === "action" ? "auto" : width]];
|
|
184
|
+
});
|
|
185
|
+
const pad = narrow ? "1rem" : "1.25rem";
|
|
186
|
+
return tracks.map(([slot, width], index) => {
|
|
187
|
+
return `[${slot}] ${(index === 0 || index === tracks.length - 1) && LENGTH.test(width) ? `calc(${width} + ${pad})` : width}`;
|
|
188
|
+
}).join(" ");
|
|
189
|
+
}
|
|
190
|
+
const SettingsListContext = React.createContext({});
|
|
191
|
+
/**
|
|
192
|
+
* Comparable rows whose status and action columns line up across items (CSS
|
|
193
|
+
* subgrid). Below 34rem each item becomes two lines: the title and meta with
|
|
194
|
+
* its action on the right, then the state under them at full width.
|
|
195
|
+
*/
|
|
196
|
+
function SettingsList({ columns, children, "aria-label": ariaLabel }) {
|
|
197
|
+
const style = {
|
|
198
|
+
"--settings-list-columns": listTracks(columns, false),
|
|
199
|
+
"--settings-list-columns-narrow": listTracks(columns, true)
|
|
200
|
+
};
|
|
201
|
+
return /* @__PURE__ */ jsx(SettingsListContext.Provider, {
|
|
202
|
+
value: columns,
|
|
203
|
+
children: /* @__PURE__ */ jsx("ul", {
|
|
204
|
+
role: "list",
|
|
205
|
+
"aria-label": ariaLabel,
|
|
206
|
+
style,
|
|
207
|
+
className: cn("grid min-w-0 gap-x-4 [grid-template-columns:var(--settings-list-columns)] @max-[34rem]/settings-section:[grid-template-columns:var(--settings-list-columns-narrow)]", DIVIDED),
|
|
208
|
+
children
|
|
209
|
+
})
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
function ItemMenu({ items, titleId }) {
|
|
213
|
+
const triggerId = React.useId();
|
|
214
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
|
|
215
|
+
asChild: true,
|
|
216
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
217
|
+
type: "button",
|
|
218
|
+
variant: "ghost",
|
|
219
|
+
size: "icon",
|
|
220
|
+
id: triggerId,
|
|
221
|
+
"aria-label": "More actions",
|
|
222
|
+
"aria-labelledby": `${triggerId} ${titleId}`,
|
|
223
|
+
children: /* @__PURE__ */ jsx(Ellipsis, { "aria-hidden": "true" })
|
|
224
|
+
})
|
|
225
|
+
}), /* @__PURE__ */ jsx(DropdownMenuContent, {
|
|
226
|
+
align: "end",
|
|
227
|
+
className: "min-w-44 max-w-72",
|
|
228
|
+
children: items.map((item) => {
|
|
229
|
+
const unavailable = item.disabledReason != null;
|
|
230
|
+
return /* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
231
|
+
"aria-disabled": unavailable || void 0,
|
|
232
|
+
onSelect: (event) => {
|
|
233
|
+
if (unavailable) event.preventDefault();
|
|
234
|
+
else item.onSelect();
|
|
235
|
+
},
|
|
236
|
+
className: cn("flex-col items-start gap-0.5 leading-5", item.tone === "danger" && !unavailable && cn(ERROR_TEXT, "focus:text-[color:var(--surface-danger-text)]"), unavailable && "cursor-not-allowed text-muted-foreground focus:text-muted-foreground"),
|
|
237
|
+
children: [/* @__PURE__ */ jsx("span", { children: item.label }), unavailable && /* @__PURE__ */ jsx(LockLine, { children: item.disabledReason })]
|
|
238
|
+
}, item.label);
|
|
239
|
+
})
|
|
240
|
+
})] });
|
|
241
|
+
}
|
|
242
|
+
const NARROW_STATUS = "@max-[34rem]/settings-section:col-start-[main] @max-[34rem]/settings-section:col-end-[-1] @max-[34rem]/settings-section:row-start-2 @max-[34rem]/settings-section:mt-2";
|
|
243
|
+
/**
|
|
244
|
+
* One item in a {@link SettingsList}; each slot goes to the list's column of
|
|
245
|
+
* that name. Without a status column the state follows the meta, and without
|
|
246
|
+
* a menu column the ⋯ menu ends the action cell.
|
|
247
|
+
*/
|
|
248
|
+
function SettingsListItem({ leading, title, meta, status, error, action, menu }) {
|
|
249
|
+
const columns = React.useContext(SettingsListContext);
|
|
250
|
+
const titleId = React.useId();
|
|
251
|
+
const reasonId = `${titleId}-reason`;
|
|
252
|
+
const locked = action?.disabledReason != null;
|
|
253
|
+
const menuNode = menu && menu.length > 0 ? /* @__PURE__ */ jsx(ItemMenu, {
|
|
254
|
+
items: menu,
|
|
255
|
+
titleId
|
|
256
|
+
}) : null;
|
|
257
|
+
const actionButton = action && /* @__PURE__ */ jsx(Button, {
|
|
258
|
+
type: "button",
|
|
259
|
+
variant: "outline",
|
|
260
|
+
"aria-disabled": locked || void 0,
|
|
261
|
+
"aria-describedby": locked ? reasonId : void 0,
|
|
262
|
+
onClick: locked ? void 0 : action.onSelect,
|
|
263
|
+
children: action.label
|
|
264
|
+
});
|
|
265
|
+
const actionCell = (actionButton || menuNode && !columns.menu) && /* @__PURE__ */ jsxs(Fragment$1, { children: [actionButton, !columns.menu && menuNode] });
|
|
266
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
267
|
+
className: cn("col-span-full grid grid-cols-subgrid items-center px-5 py-3 @max-[34rem]/settings-section:px-4", DIVIDED),
|
|
268
|
+
children: [
|
|
269
|
+
leading != null && /* @__PURE__ */ jsx("div", {
|
|
270
|
+
className: "col-start-[leading] row-start-1 flex",
|
|
271
|
+
children: leading
|
|
272
|
+
}),
|
|
273
|
+
/* @__PURE__ */ jsxs("div", {
|
|
274
|
+
className: "col-start-[main] row-start-1 min-w-0",
|
|
275
|
+
children: [
|
|
276
|
+
/* @__PURE__ */ jsx("div", {
|
|
277
|
+
id: titleId,
|
|
278
|
+
className: "font-medium text-sm leading-5 text-foreground [overflow-wrap:anywhere] [&_a]:rounded-sm [&_a]:text-foreground [&_a]:underline-offset-4 [&_a:hover]:underline [&_a:focus-visible]:outline-2 [&_a:focus-visible]:outline-offset-2 [&_a:focus-visible]:outline-[var(--focus-border)]",
|
|
279
|
+
children: title
|
|
280
|
+
}),
|
|
281
|
+
meta != null && /* @__PURE__ */ jsx("div", {
|
|
282
|
+
className: "text-sm leading-5 text-muted-foreground [overflow-wrap:anywhere]",
|
|
283
|
+
children: meta
|
|
284
|
+
}),
|
|
285
|
+
status != null && !columns.status && /* @__PURE__ */ jsx("div", {
|
|
286
|
+
className: "mt-1 text-sm leading-5 text-muted-foreground",
|
|
287
|
+
children: status
|
|
288
|
+
}),
|
|
289
|
+
locked && /* @__PURE__ */ jsx(LockLine, {
|
|
290
|
+
id: reasonId,
|
|
291
|
+
className: "mt-1",
|
|
292
|
+
children: action?.disabledReason
|
|
293
|
+
})
|
|
294
|
+
]
|
|
295
|
+
}),
|
|
296
|
+
error != null && /* @__PURE__ */ jsx(ErrorLine, {
|
|
297
|
+
className: "col-start-[main] row-start-2 mt-1 @max-[34rem]/settings-section:col-end-[-1] @max-[34rem]/settings-section:row-start-3",
|
|
298
|
+
children: error
|
|
299
|
+
}),
|
|
300
|
+
status != null && columns.status && /* @__PURE__ */ jsx("div", {
|
|
301
|
+
className: cn("col-start-[status] row-start-1 min-w-0 text-sm leading-5 text-muted-foreground", NARROW_STATUS),
|
|
302
|
+
children: status
|
|
303
|
+
}),
|
|
304
|
+
actionCell && /* @__PURE__ */ jsx("div", {
|
|
305
|
+
className: "col-start-[action] row-start-1 flex min-w-0 items-center justify-end gap-1",
|
|
306
|
+
children: actionCell
|
|
307
|
+
}),
|
|
308
|
+
menuNode && columns.menu && /* @__PURE__ */ jsx("div", {
|
|
309
|
+
className: "col-start-[menu] row-start-1 flex justify-end",
|
|
310
|
+
children: menuNode
|
|
311
|
+
})
|
|
312
|
+
]
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
//#endregion
|
|
316
|
+
export { SettingsList, SettingsListItem, SettingsRow, SettingsSection };
|
|
@@ -6,7 +6,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
6
6
|
import * as SwitchPrimitives from "@radix-ui/react-switch";
|
|
7
7
|
//#region src/primitives/switch.tsx
|
|
8
8
|
const Switch = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(SwitchPrimitives.Root, {
|
|
9
|
-
className: cn("peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 shadow-sm transition-[background-color,border-color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-transparent data-[state=checked]:bg-[var(--accent-text)] data-[state=unchecked]:border-muted-foreground data-[state=unchecked]:bg-transparent", focusRing, className),
|
|
9
|
+
className: cn("relative pointer-coarse:before:absolute pointer-coarse:before:-inset-x-1.5 pointer-coarse:before:-inset-y-3.5 pointer-coarse:before:content-['']", "peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 shadow-sm transition-[background-color,border-color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-transparent data-[state=checked]:bg-[var(--accent-text)] data-[state=unchecked]:border-muted-foreground data-[state=unchecked]:bg-transparent", focusRing, className),
|
|
10
10
|
...props,
|
|
11
11
|
ref,
|
|
12
12
|
children: /* @__PURE__ */ jsx(SwitchPrimitives.Thumb, { className: cn("pointer-events-none block h-4 w-4 rounded-full shadow-lg ring-0 transition-[translate,background-color] data-[state=checked]:translate-x-4 data-[state=checked]:bg-card data-[state=unchecked]:translate-x-0 data-[state=unchecked]:bg-muted-foreground") })
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { Button } from "./button.js";
|
|
2
3
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
4
|
import { AlertCircle, CheckCircle2, FileText, Loader2, RefreshCw, X } from "lucide-react";
|
|
4
5
|
//#region src/primitives/upload-progress.tsx
|
|
@@ -52,16 +53,20 @@ function UploadProgress({ files, onRemove, onRetry, className }) {
|
|
|
52
53
|
}),
|
|
53
54
|
/* @__PURE__ */ jsxs("div", {
|
|
54
55
|
className: "flex shrink-0 items-center gap-1",
|
|
55
|
-
children: [file.status === "error" && onRetry && /* @__PURE__ */ jsx(
|
|
56
|
+
children: [file.status === "error" && onRetry && /* @__PURE__ */ jsx(Button, {
|
|
56
57
|
type: "button",
|
|
58
|
+
variant: "ghost",
|
|
59
|
+
size: "icon-sm",
|
|
57
60
|
onClick: () => onRetry(file.id),
|
|
58
|
-
|
|
59
|
-
children: /* @__PURE__ */ jsx(RefreshCw, {
|
|
60
|
-
}), onRemove && /* @__PURE__ */ jsx(
|
|
61
|
+
"aria-label": `Retry ${file.name}`,
|
|
62
|
+
children: /* @__PURE__ */ jsx(RefreshCw, { "aria-hidden": "true" })
|
|
63
|
+
}), onRemove && /* @__PURE__ */ jsx(Button, {
|
|
61
64
|
type: "button",
|
|
65
|
+
variant: "ghost",
|
|
66
|
+
size: "icon-sm",
|
|
62
67
|
onClick: () => onRemove(file.id),
|
|
63
|
-
|
|
64
|
-
children: /* @__PURE__ */ jsx(X, {
|
|
68
|
+
"aria-label": `Remove ${file.name}`,
|
|
69
|
+
children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
|
|
65
70
|
})]
|
|
66
71
|
})
|
|
67
72
|
]
|
package/dist/primitives.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { Calendar, CalendarDate, CalendarProps, formatCalendarDate, localToday,
|
|
|
16
16
|
import { DatePicker, DatePickerProps } from "./primitives/date-picker.js";
|
|
17
17
|
import { TimeSelect, TimeSelectProps } from "./primitives/time-select.js";
|
|
18
18
|
import { Badge, BadgeProps, badgeVariants } from "./primitives/badge.js";
|
|
19
|
-
import { Avatar, AvatarFallback, AvatarImage } from "./primitives/avatar.js";
|
|
19
|
+
import { Avatar, AvatarFallback, AvatarImage, AvatarProps } from "./primitives/avatar.js";
|
|
20
20
|
import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from "./primitives/dropdown-menu.js";
|
|
21
21
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue } from "./primitives/select.js";
|
|
22
22
|
import { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableProps, TableRow } from "./primitives/table.js";
|
|
@@ -44,6 +44,8 @@ import { InlineCode, InlineCodeProps } from "./primitives/code-block.js";
|
|
|
44
44
|
import { ThemeToggle, useTheme } from "./primitives/theme-toggle.js";
|
|
45
45
|
import { Heading, HeadingProps, HeadingVariant } from "./primitives/heading.js";
|
|
46
46
|
import { PageShell, PageShellProps } from "./primitives/page-shell.js";
|
|
47
|
+
import { Field, FieldProps, fieldMessageId } from "./primitives/field.js";
|
|
48
|
+
import { SettingsList, SettingsListAction, SettingsListColumns, SettingsListItem, SettingsListItemProps, SettingsListMenuItem, SettingsListProps, SettingsRow, SettingsRowLayout, SettingsRowProps, SettingsSection, SettingsSectionProps } from "./primitives/settings.js";
|
|
47
49
|
import { PageHeader, PageHeaderProps } from "./primitives/page-header.js";
|
|
48
50
|
import { Alert, AlertDescription, AlertProps, AlertTitle, AlertTone } from "./primitives/alert.js";
|
|
49
51
|
import { Pagination, PaginationProps } from "./primitives/pagination.js";
|
|
@@ -54,4 +56,4 @@ import { Wizard, WizardProps, WizardStep, WizardStepProps } from "./primitives/w
|
|
|
54
56
|
import { Metric, MetricProps, MetricStrip, MetricStripProps } from "./primitives/metric-strip.js";
|
|
55
57
|
import { FilterField, FilterFieldProps, Toolbar, ToolbarProps } from "./primitives/toolbar.js";
|
|
56
58
|
import { Logo, LogoProps, TangleKnot } from "@tangle-network/brand";
|
|
57
|
-
export { Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, type AlertProps, AlertTitle, type AlertTone, ArtifactPane, type ArtifactPaneProps, Avatar, AvatarFallback, AvatarImage, Badge, type BadgeProps, Button, type ButtonProps, CATEGORY_TONES, Calendar, type CalendarDate, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, type CardTitleProps, type CategoryTone, Checkbox, Chip, type ChipProps, CodeBlock, type CodeBlockProps, Collapsible, CollapsibleContent, CollapsibleTrigger, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, DatePicker, type DatePickerProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropZone, type DropZoneProps, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, FilterField, type FilterFieldProps, Heading, type HeadingProps, type HeadingVariant, HelpText, type HelpTextProps, IconTile, type IconTileProps, InlineCode, type InlineCodeProps, Input, type InputProps, Label, Logo, type LogoProps, Metric, type MetricProps, MetricStrip, type MetricStripProps, PageHeader, type PageHeaderProps, PageShell, type PageShellProps, Pagination, type PaginationProps, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, SegmentedControl, type SegmentedControlOption, type SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, type SidebarDropZoneProps, Skeleton, SkeletonCard, SkeletonTable, Slider, StatCard, type StatCardProps, StatusPill, type StatusPillProps, type StatusTone, Switch, TONE_CLASSES, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, type TableProps, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, type TagProps, TangleKnot, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, type TextShimmerProps, Textarea, type TextareaProps, ThemeToggle, TimeSelect, type TimeSelectProps, type Toast, ToastContainer, ToastProvider, type Tone, type ToneClasses, Toolbar, type ToolbarProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UploadFile, UploadProgress, type UploadProgressProps, Wizard, type WizardProps, WizardStep, type WizardStepProps, badgeVariants, buttonVariants, formatCalendarDate, initialsOf, localToday, parseCalendarDate, toneFor, useTheme, useToast };
|
|
59
|
+
export { Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, type AlertProps, AlertTitle, type AlertTone, ArtifactPane, type ArtifactPaneProps, Avatar, AvatarFallback, AvatarImage, type AvatarProps, Badge, type BadgeProps, Button, type ButtonProps, CATEGORY_TONES, Calendar, type CalendarDate, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, type CardTitleProps, type CategoryTone, Checkbox, Chip, type ChipProps, CodeBlock, type CodeBlockProps, Collapsible, CollapsibleContent, CollapsibleTrigger, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, DatePicker, type DatePickerProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropZone, type DropZoneProps, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, Field, type FieldProps, FilterField, type FilterFieldProps, Heading, type HeadingProps, type HeadingVariant, HelpText, type HelpTextProps, IconTile, type IconTileProps, InlineCode, type InlineCodeProps, Input, type InputProps, Label, Logo, type LogoProps, Metric, type MetricProps, MetricStrip, type MetricStripProps, PageHeader, type PageHeaderProps, PageShell, type PageShellProps, Pagination, type PaginationProps, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, SegmentedControl, type SegmentedControlOption, type SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, SettingsList, type SettingsListAction, type SettingsListColumns, SettingsListItem, type SettingsListItemProps, type SettingsListMenuItem, type SettingsListProps, SettingsRow, type SettingsRowLayout, type SettingsRowProps, SettingsSection, type SettingsSectionProps, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, type SidebarDropZoneProps, Skeleton, SkeletonCard, SkeletonTable, Slider, StatCard, type StatCardProps, StatusPill, type StatusPillProps, type StatusTone, Switch, TONE_CLASSES, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, type TableProps, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, type TagProps, TangleKnot, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, type TextShimmerProps, Textarea, type TextareaProps, ThemeToggle, TimeSelect, type TimeSelectProps, type Toast, ToastContainer, ToastProvider, type Tone, type ToneClasses, Toolbar, type ToolbarProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UploadFile, UploadProgress, type UploadProgressProps, Wizard, type WizardProps, WizardStep, type WizardStepProps, badgeVariants, buttonVariants, fieldMessageId, formatCalendarDate, initialsOf, localToday, parseCalendarDate, toneFor, useTheme, useToast };
|
package/dist/primitives.js
CHANGED
|
@@ -42,6 +42,8 @@ import { CodeBlock, CopyButton } from "./markdown/code-block.js";
|
|
|
42
42
|
import { InlineCode } from "./primitives/code-block.js";
|
|
43
43
|
import { ThemeToggle, useTheme } from "./primitives/theme-toggle.js";
|
|
44
44
|
import { PageShell } from "./primitives/page-shell.js";
|
|
45
|
+
import { Field, fieldMessageId } from "./primitives/field.js";
|
|
46
|
+
import { SettingsList, SettingsListItem, SettingsRow, SettingsSection } from "./primitives/settings.js";
|
|
45
47
|
import { PageHeader } from "./primitives/page-header.js";
|
|
46
48
|
import { Alert, AlertDescription, AlertTitle } from "./primitives/alert.js";
|
|
47
49
|
import { Pagination } from "./primitives/pagination.js";
|
|
@@ -54,4 +56,4 @@ import { IconTile, initialsOf } from "./primitives/icon-tile.js";
|
|
|
54
56
|
import { Metric, MetricStrip } from "./primitives/metric-strip.js";
|
|
55
57
|
import { FilterField, Toolbar } from "./primitives/toolbar.js";
|
|
56
58
|
import { Logo, TangleKnot } from "@tangle-network/brand";
|
|
57
|
-
export { Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, ArtifactPane, Avatar, AvatarFallback, AvatarImage, Badge, Button, CATEGORY_TONES, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Chip, CodeBlock, Collapsible, CollapsibleContent, CollapsibleTrigger, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, DatePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropZone, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, FilterField, Heading, HelpText, IconTile, InlineCode, Input, Label, Logo, Metric, MetricStrip, PageHeader, PageShell, Pagination, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, SegmentedControl, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, Skeleton, SkeletonCard, SkeletonTable, Slider, StatCard, StatusPill, Switch, TONE_CLASSES, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, TangleKnot, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, Textarea, ThemeToggle, TimeSelect, ToastContainer, ToastProvider, Toolbar, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UploadProgress, Wizard, WizardStep, badgeVariants, buttonVariants, formatCalendarDate, initialsOf, localToday, parseCalendarDate, toneFor, useTheme, useToast };
|
|
59
|
+
export { Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, ArtifactPane, Avatar, AvatarFallback, AvatarImage, Badge, Button, CATEGORY_TONES, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Chip, CodeBlock, Collapsible, CollapsibleContent, CollapsibleTrigger, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, DatePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropZone, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, Field, FilterField, Heading, HelpText, IconTile, InlineCode, Input, Label, Logo, Metric, MetricStrip, PageHeader, PageShell, Pagination, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, SegmentedControl, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, SettingsList, SettingsListItem, SettingsRow, SettingsSection, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, Skeleton, SkeletonCard, SkeletonTable, Slider, StatCard, StatusPill, Switch, TONE_CLASSES, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, TangleKnot, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, Textarea, ThemeToggle, TimeSelect, ToastContainer, ToastProvider, Toolbar, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UploadProgress, Wizard, WizardStep, badgeVariants, buttonVariants, fieldMessageId, formatCalendarDate, initialsOf, localToday, parseCalendarDate, toneFor, useTheme, useToast };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/ui",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.40.1",
|
|
4
4
|
"description": "Generic React UI components for Tangle products — primitives, chat, run, files, editor, markdown.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
"peerDependencies": {
|
|
166
166
|
"@hocuspocus/provider": ">=2.15.0 <5.0.0",
|
|
167
167
|
"@nanostores/react": ">=0.8.4 <3.0.0",
|
|
168
|
-
"@tangle-network/brand": "^1.
|
|
168
|
+
"@tangle-network/brand": "^1.26.0",
|
|
169
169
|
"@tiptap/core": ">=3.20.4 <4.0.0",
|
|
170
170
|
"@tiptap/extension-collaboration": ">=3.20.4 <4.0.0",
|
|
171
171
|
"@tiptap/extension-collaboration-caret": ">=3.7.0 <4.0.0",
|
|
@@ -46,22 +46,24 @@ export const controlSquare = {
|
|
|
46
46
|
export const fieldTouchText = "pointer-coarse:text-base";
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
|
-
* A control
|
|
50
|
-
*
|
|
49
|
+
* A control smaller than 44px on a fine pointer still gets a 44px target
|
|
50
|
+
* in both dimensions on a coarse one (WCAG 2.5.5, Apple HIG). Same floor as `touch`.
|
|
51
|
+
* Follow the primary pointer, matching SegmentedControl; a secondary touch
|
|
52
|
+
* input alone does not inflate a fine-pointer desktop layout.
|
|
51
53
|
*/
|
|
52
|
-
export const touchTarget = "pointer-coarse:min-h-11";
|
|
54
|
+
export const touchTarget = "pointer-coarse:min-h-11 pointer-coarse:min-w-11";
|
|
53
55
|
|
|
54
56
|
/** Field sizes: the control scale with field padding. */
|
|
55
57
|
export const fieldSizes = {
|
|
56
|
-
sm: cn(controlHeight.sm, controlText.sm, controlRadius.sm, "px-2.5"),
|
|
57
|
-
md: cn(controlHeight.md, controlText.md, controlRadius.md, "px-3"),
|
|
58
|
-
lg: cn(controlHeight.lg, controlText.lg, controlRadius.lg, "px-4"),
|
|
58
|
+
sm: cn(controlHeight.sm, controlText.sm, controlRadius.sm, touchTarget, "px-2.5"),
|
|
59
|
+
md: cn(controlHeight.md, controlText.md, controlRadius.md, touchTarget, "px-3"),
|
|
60
|
+
lg: cn(controlHeight.lg, controlText.lg, controlRadius.lg, touchTarget, "px-4"),
|
|
59
61
|
} as const satisfies Record<ControlSize, string>;
|
|
60
62
|
|
|
61
63
|
// Explicit opt-in sizes kept for existing callers. Compact follows the
|
|
62
64
|
// consumer's density token (md); touch is lg with a guaranteed 44px minimum.
|
|
63
65
|
export const controlSizes = {
|
|
64
|
-
compact:
|
|
66
|
+
compact: `h-[var(--control-height)] px-3 py-1 text-sm ${touchTarget}`,
|
|
65
67
|
touch: "h-11 min-h-11 min-w-11 px-4 py-2 text-base",
|
|
66
68
|
} as const;
|
|
67
69
|
|
package/src/presentation.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** React-free presentation. Consumers scan this published source with Tailwind. */
|
|
2
2
|
import { surfaceMotion } from "./lib/motion";
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
|
-
import { controlHeight, controlMotion, controlRadius, controlSizes, controlSquare, controlText, fieldPresentation, fieldSizes, fieldTouchText } from "./lib/control-presentation";
|
|
4
|
+
import { controlHeight, controlMotion, controlRadius, controlSizes, controlSquare, controlText, fieldPresentation, fieldSizes, fieldTouchText, touchTarget } from "./lib/control-presentation";
|
|
5
5
|
import { focusRing, focusFieldInvalid } from "./lib/focus";
|
|
6
6
|
import { ixControl, ixControlSoft, ixSelected, ixTabUnderline } from "./lib/interaction";
|
|
7
7
|
import { cn } from "./lib/utils";
|
|
@@ -18,6 +18,8 @@ export const buttonVariants = cva(
|
|
|
18
18
|
`bg-destructive text-destructive-foreground hover:bg-destructive/90 active:scale-[0.97] ${ixControl}`,
|
|
19
19
|
outline:
|
|
20
20
|
`border border-border bg-card hover:bg-muted active:scale-[0.97] text-foreground ${ixControl}`,
|
|
21
|
+
"destructive-outline":
|
|
22
|
+
`border border-[var(--surface-danger-border)] bg-card hover:bg-[var(--surface-danger-bg)] active:scale-[0.97] text-[var(--surface-danger-text)] ${ixControl}`,
|
|
21
23
|
secondary:
|
|
22
24
|
`bg-muted border border-border text-foreground hover:bg-muted/80 active:scale-[0.97] ${ixControl}`,
|
|
23
25
|
ghost:
|
|
@@ -27,15 +29,15 @@ export const buttonVariants = cva(
|
|
|
27
29
|
`bg-[var(--btn-primary-bg)] text-[var(--btn-primary-text)] border border-[var(--border-accent)] hover:bg-[var(--btn-primary-hover)] active:scale-[0.97] ${ixControl}`,
|
|
28
30
|
},
|
|
29
31
|
size: {
|
|
30
|
-
default: `${controlHeight.md} ${controlText.md} ${controlRadius.md} px-4 py-2`,
|
|
31
|
-
md: `${controlHeight.md} ${controlText.md} ${controlRadius.md} px-4 py-2`,
|
|
32
|
-
sm: `${controlHeight.sm} ${controlText.sm} ${controlRadius.sm} px-3`,
|
|
33
|
-
lg: `${controlHeight.lg} ${controlText.lg} ${controlRadius.lg} px-7`,
|
|
32
|
+
default: `${controlHeight.md} ${controlText.md} ${controlRadius.md} ${touchTarget} px-4 py-2`,
|
|
33
|
+
md: `${controlHeight.md} ${controlText.md} ${controlRadius.md} ${touchTarget} px-4 py-2`,
|
|
34
|
+
sm: `${controlHeight.sm} ${controlText.sm} ${controlRadius.sm} ${touchTarget} px-3`,
|
|
35
|
+
lg: `${controlHeight.lg} ${controlText.lg} ${controlRadius.lg} ${touchTarget} px-7`,
|
|
34
36
|
display: "tangle-control-display",
|
|
35
37
|
xl: "h-13 rounded-xl px-9 text-base",
|
|
36
|
-
icon: `${controlSquare.md} ${controlText.md} ${controlRadius.md}`,
|
|
37
|
-
"icon-sm": `${controlSquare.sm} ${controlText.sm} ${controlRadius.sm}`,
|
|
38
|
-
"icon-lg": `${controlSquare.lg} ${controlText.lg} ${controlRadius.lg}`,
|
|
38
|
+
icon: `${controlSquare.md} ${controlText.md} ${controlRadius.md} ${touchTarget}`,
|
|
39
|
+
"icon-sm": `${controlSquare.sm} ${controlText.sm} ${controlRadius.sm} ${touchTarget}`,
|
|
40
|
+
"icon-lg": `${controlSquare.lg} ${controlText.lg} ${controlRadius.lg} ${touchTarget}`,
|
|
39
41
|
...controlSizes,
|
|
40
42
|
},
|
|
41
43
|
},
|