@tangle-network/ui 11.39.0 → 11.40.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/CHANGELOG.md +19 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -1
- package/dist/presentation.d.ts +1 -1
- package/dist/presentation.js +1 -0
- 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/settings.d.ts +125 -0
- package/dist/primitives/settings.js +316 -0
- package/dist/primitives/switch.js +1 -1
- package/dist/primitives.d.ts +4 -2
- package/dist/primitives.js +3 -1
- package/package.json +2 -2
- package/src/presentation.ts +2 -0
- package/src/primitives/avatar.tsx +10 -5
- package/src/primitives/button.tsx +2 -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/styles/presentation.css +43 -2
|
@@ -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") })
|
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.0",
|
|
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",
|
package/src/presentation.ts
CHANGED
|
@@ -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:
|
|
@@ -2,14 +2,19 @@ import * as AvatarPrimitive from "@radix-ui/react-avatar";
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { cn } from "../lib/utils";
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
const AVATAR_SIZES = { sm: "h-8 w-8", md: "h-10 w-10" } as const;
|
|
6
|
+
|
|
7
|
+
export interface AvatarProps extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> {
|
|
8
|
+
/** sm 32px (a list row beside a two-line name), md 40px (default). */
|
|
9
|
+
size?: keyof typeof AVATAR_SIZES;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const Avatar = React.forwardRef<React.ElementRef<typeof AvatarPrimitive.Root>, AvatarProps>(({ className, size = "md", ...props }, ref) => (
|
|
9
13
|
<AvatarPrimitive.Root
|
|
10
14
|
ref={ref}
|
|
11
15
|
className={cn(
|
|
12
|
-
"relative flex
|
|
16
|
+
"relative flex shrink-0 overflow-hidden rounded-full",
|
|
17
|
+
AVATAR_SIZES[size],
|
|
13
18
|
className,
|
|
14
19
|
)}
|
|
15
20
|
{...props}
|
|
@@ -36,7 +36,8 @@ function buttonClassName(
|
|
|
36
36
|
|
|
37
37
|
export interface ButtonProps
|
|
38
38
|
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
39
|
-
|
|
39
|
+
/** `destructive-outline` is a destructive action at rest, such as "Delete project…", whose confirmation uses `destructive`. */
|
|
40
|
+
variant?: "default" | "destructive" | "destructive-outline" | "outline" | "secondary" | "ghost" | "link" | "sandbox" | "bare" | null;
|
|
40
41
|
/** sm 32px, default/md 36px (`--control-height`), lg 44px; icon sizes are squares on the same heights. */
|
|
41
42
|
size?: "default" | "md" | "sm" | "lg" | "xl" | "display" | "icon" | "icon-sm" | "icon-lg" | "compact" | "touch" | null;
|
|
42
43
|
asChild?: boolean;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { CircleAlert } from "lucide-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { cn } from "../lib/utils";
|
|
6
|
+
import { InfoTip } from "./info-tip";
|
|
7
|
+
import { Label } from "./label";
|
|
8
|
+
|
|
9
|
+
export interface FieldProps {
|
|
10
|
+
label: string;
|
|
11
|
+
/** The control's id; the label targets it. */
|
|
12
|
+
htmlFor: string;
|
|
13
|
+
/** Help that explains what the setting does, shown from an info button. */
|
|
14
|
+
info?: React.ReactNode;
|
|
15
|
+
/** One short sentence the person needs to fill the field in. */
|
|
16
|
+
hint?: React.ReactNode;
|
|
17
|
+
/** A validation error. The control should set `aria-invalid` and reference {@link fieldMessageId}. */
|
|
18
|
+
error?: React.ReactNode;
|
|
19
|
+
/** A non-error status, such as a check that passed. */
|
|
20
|
+
status?: React.ReactNode;
|
|
21
|
+
/** Trailing content in the label row, such as a "Use default" action. */
|
|
22
|
+
aside?: React.ReactNode;
|
|
23
|
+
children: React.ReactNode;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The id of a field's hint, error, or status line, for the control's `aria-describedby`. */
|
|
27
|
+
export function fieldMessageId(htmlFor: string): string {
|
|
28
|
+
return `${htmlFor}-message`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A labelled form field: the label (with an optional info tip and a trailing
|
|
33
|
+
* aside) above the control, and one message line under it. Every line is
|
|
34
|
+
* 14px; an error replaces the status and the hint and is announced.
|
|
35
|
+
*/
|
|
36
|
+
export function Field({ label, htmlFor, info, hint, error, status, aside, children }: FieldProps) {
|
|
37
|
+
const message = error ?? status ?? hint;
|
|
38
|
+
return (
|
|
39
|
+
<div className="min-w-0 space-y-2">
|
|
40
|
+
<div className="flex min-h-6 items-center justify-between gap-2">
|
|
41
|
+
<span className="flex min-w-0 items-center gap-1.5">
|
|
42
|
+
<Label htmlFor={htmlFor} className="leading-6 text-foreground">{label}</Label>
|
|
43
|
+
{info && <InfoTip label={label}>{info}</InfoTip>}
|
|
44
|
+
</span>
|
|
45
|
+
{aside}
|
|
46
|
+
</div>
|
|
47
|
+
{children}
|
|
48
|
+
{message && (
|
|
49
|
+
<div
|
|
50
|
+
id={fieldMessageId(htmlFor)}
|
|
51
|
+
role={error ? "alert" : undefined}
|
|
52
|
+
className={cn(
|
|
53
|
+
"text-sm leading-6 [overflow-wrap:anywhere]",
|
|
54
|
+
error ? "flex items-start gap-1.5 text-[var(--surface-danger-text)]" : "text-muted-foreground",
|
|
55
|
+
)}
|
|
56
|
+
>
|
|
57
|
+
{error && <CircleAlert aria-hidden="true" className="mt-1 size-4 shrink-0" />}
|
|
58
|
+
{error ? <span className="min-w-0">{message}</span> : message}
|
|
59
|
+
</div>
|
|
60
|
+
)}
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
package/src/primitives/index.ts
CHANGED
|
@@ -79,6 +79,7 @@ export { Badge, badgeVariants } from "./badge";
|
|
|
79
79
|
export type { BadgeProps } from "./badge";
|
|
80
80
|
|
|
81
81
|
export { Avatar, AvatarImage, AvatarFallback } from "./avatar";
|
|
82
|
+
export type { AvatarProps } from "./avatar";
|
|
82
83
|
|
|
83
84
|
export {
|
|
84
85
|
DropdownMenu,
|
|
@@ -199,6 +200,21 @@ export type { HeadingProps, HeadingVariant } from "./heading";
|
|
|
199
200
|
export { PageShell } from "./page-shell";
|
|
200
201
|
export type { PageShellProps } from "./page-shell";
|
|
201
202
|
|
|
203
|
+
export { Field, fieldMessageId } from "./field";
|
|
204
|
+
export type { FieldProps } from "./field";
|
|
205
|
+
|
|
206
|
+
export { SettingsList, SettingsListItem, SettingsRow, SettingsSection } from "./settings";
|
|
207
|
+
export type {
|
|
208
|
+
SettingsListAction,
|
|
209
|
+
SettingsListColumns,
|
|
210
|
+
SettingsListItemProps,
|
|
211
|
+
SettingsListMenuItem,
|
|
212
|
+
SettingsListProps,
|
|
213
|
+
SettingsRowLayout,
|
|
214
|
+
SettingsRowProps,
|
|
215
|
+
SettingsSectionProps,
|
|
216
|
+
} from "./settings";
|
|
217
|
+
|
|
202
218
|
export { PageHeader } from "./page-header";
|
|
203
219
|
export type { PageHeaderProps } from "./page-header";
|
|
204
220
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Info } from "lucide-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { Button } from "./button";
|
|
6
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* An info button beside a label, for help that explains a setting. It opens
|
|
10
|
+
* on hover and keyboard focus and toggles on tap. The glyph stays 20px; on a
|
|
11
|
+
* coarse pointer a transparent 44px hit area surrounds it. It brings its own
|
|
12
|
+
* provider, so a row or field works in an app that never mounted one.
|
|
13
|
+
*/
|
|
14
|
+
export function InfoTip({ label, children }: { label: string; children: React.ReactNode }) {
|
|
15
|
+
const [open, setOpen] = React.useState(false);
|
|
16
|
+
// Radix closes a tooltip on every trigger press and click; a tap or click toggles it instead.
|
|
17
|
+
const openAtPress = React.useRef(false);
|
|
18
|
+
return (
|
|
19
|
+
<TooltipProvider delayDuration={150}>
|
|
20
|
+
<Tooltip open={open} onOpenChange={setOpen}>
|
|
21
|
+
<TooltipTrigger asChild>
|
|
22
|
+
<Button
|
|
23
|
+
type="button"
|
|
24
|
+
variant="bare"
|
|
25
|
+
aria-label={`About ${label}`}
|
|
26
|
+
onPointerDown={() => { openAtPress.current = open; }}
|
|
27
|
+
onClick={(event) => {
|
|
28
|
+
event.preventDefault();
|
|
29
|
+
setOpen(event.detail === 0 ? !open : !openAtPress.current);
|
|
30
|
+
}}
|
|
31
|
+
className="relative inline-flex size-5 shrink-0 items-center justify-center rounded-full text-muted-foreground hover:text-foreground pointer-coarse:before:absolute pointer-coarse:before:-inset-3 pointer-coarse:before:content-['']"
|
|
32
|
+
>
|
|
33
|
+
<Info aria-hidden="true" className="size-4" />
|
|
34
|
+
</Button>
|
|
35
|
+
</TooltipTrigger>
|
|
36
|
+
<TooltipContent side="top" className="max-w-72 text-sm leading-5">{children}</TooltipContent>
|
|
37
|
+
</Tooltip>
|
|
38
|
+
</TooltipProvider>
|
|
39
|
+
);
|
|
40
|
+
}
|