torch-glare 2.4.5 → 2.5.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/apps/lib/components/DataViews/{badgeAdapter.ts → badge.ts} +2 -2
- package/apps/lib/components/DataViews/cell.tsx +324 -0
- package/apps/lib/components/DataViews/context.ts +144 -0
- package/apps/lib/components/DataViews/data-views.tsx +383 -0
- package/apps/lib/components/DataViews/filters/children.tsx +98 -0
- package/apps/lib/components/DataViews/filters/custom.tsx +34 -0
- package/apps/lib/components/DataViews/filters/filters.tsx +163 -0
- package/apps/lib/components/DataViews/filters/index.ts +4 -0
- package/apps/lib/components/DataViews/filters/labelled.tsx +20 -0
- package/apps/lib/components/DataViews/filters/presets.tsx +65 -0
- package/apps/lib/components/DataViews/filters/summary.tsx +65 -0
- package/apps/lib/components/DataViews/filters/sync.tsx +35 -0
- package/apps/lib/components/DataViews/filters/values.ts +173 -0
- package/apps/lib/components/DataViews/header.tsx +217 -0
- package/apps/lib/components/DataViews/hooks/index.ts +5 -0
- package/apps/lib/components/DataViews/hooks/useActiveRow.ts +22 -0
- package/apps/lib/components/DataViews/hooks/useControllable.ts +52 -0
- package/apps/lib/components/DataViews/index.ts +74 -26
- package/apps/lib/components/DataViews/panel/columns.tsx +153 -0
- package/apps/lib/components/DataViews/panel/controls.tsx +106 -0
- package/apps/lib/components/DataViews/panel/index.ts +3 -0
- package/apps/lib/components/DataViews/panel/panel.tsx +164 -0
- package/apps/lib/components/DataViews/panel/saved-views.tsx +67 -0
- package/apps/lib/components/DataViews/panel/section.tsx +79 -0
- package/apps/lib/components/DataViews/panel/sort.tsx +42 -0
- package/apps/lib/components/DataViews/panel/tab.tsx +31 -0
- package/apps/lib/components/DataViews/slots.ts +63 -0
- package/apps/lib/components/DataViews/states.tsx +38 -0
- package/apps/lib/components/DataViews/types.ts +485 -178
- package/apps/lib/components/DataViews/views/board-view.tsx +379 -0
- package/apps/lib/components/DataViews/views/card-rows.tsx +36 -0
- package/apps/lib/components/DataViews/views/inbox-view.tsx +257 -0
- package/apps/lib/components/DataViews/views/pane-views.tsx +192 -0
- package/apps/lib/components/DataViews/views/table-view.tsx +426 -0
- package/apps/lib/components/DataViews/views/tree-view.tsx +365 -0
- package/apps/lib/components/FormBuilder/context.ts +20 -6
- package/apps/lib/components/FormBuilder/field-kind.ts +28 -0
- package/apps/lib/components/FormBuilder/fields/DateField.tsx +3 -3
- package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +7 -6
- package/apps/lib/components/FormBuilder/fields/PhoneField.tsx +7 -6
- package/apps/lib/components/FormBuilder/fields/SelectField.tsx +7 -7
- package/apps/lib/components/FormBuilder/fields/TableField.tsx +1 -1
- package/apps/lib/components/FormBuilder/fields/TextField.tsx +9 -9
- package/apps/lib/components/FormBuilder/form-builder.tsx +55 -3
- package/apps/lib/components/FormBuilder/index.ts +3 -1
- package/apps/lib/components/FormBuilder/types.ts +40 -0
- package/apps/lib/components/TabSwitch.tsx +16 -4
- package/apps/lib/components/TreeFolder/TreeFolder.tsx +6 -3
- package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +16 -14
- package/apps/lib/components/TreeFolder/index.ts +1 -1
- package/apps/lib/components/TreeFolder/useTreeFolderDnD.ts +70 -207
- package/apps/lib/hooks/useDragDrop.tsx +365 -0
- package/apps/lib/hooks/useInfiniteScroll.ts +108 -0
- package/apps/lib/registry.json +159 -4
- package/apps/lib/tsconfig.tsbuildinfo +1 -1
- package/apps/lib/utils/dataViews/path.ts +67 -0
- package/apps/lib/utils/dataViews/query.ts +73 -0
- package/apps/lib/utils/dataViews/types.ts +187 -0
- package/docs/components/breadcrumb.md +1 -1
- package/docs/components/button-group.md +1 -1
- package/docs/components/button.md +1 -1
- package/docs/components/card.md +1 -1
- package/docs/components/checkbox.md +1 -1
- package/docs/components/data-views/backend-response.md +324 -0
- package/docs/components/data-views/examples/a11y-rtl.md +250 -0
- package/docs/components/data-views/examples/api-orders-route.md +130 -0
- package/docs/components/data-views/examples/fields.md +362 -0
- package/docs/components/data-views/examples/filters.md +308 -0
- package/docs/components/data-views/examples/inbox-routing.md +218 -0
- package/docs/components/data-views/examples/index.md +29 -0
- package/docs/components/data-views/examples/overview.md +244 -0
- package/docs/components/data-views/examples/panel.md +212 -0
- package/docs/components/data-views/examples/scale.md +231 -0
- package/docs/components/data-views/examples/server-side.md +210 -0
- package/docs/components/data-views/examples/state.md +250 -0
- package/docs/components/data-views/examples/tree-custom.md +388 -0
- package/docs/components/data-views/examples/view-registry.md +313 -0
- package/docs/components/data-views/examples/views.md +534 -0
- package/docs/components/data-views/guide.md +405 -0
- package/docs/components/data-views/index.md +1504 -0
- package/docs/components/data-views/migration.md +79 -0
- package/docs/components/date-picker.md +0 -1
- package/docs/components/form-builder.md +3 -2
- package/docs/components/form-renderer.md +2 -1
- package/docs/components/form.md +1 -1
- package/docs/components/input-field.md +1 -1
- package/docs/components/input-otp.md +1 -1
- package/docs/components/input.md +1 -1
- package/docs/components/labeled-check-box.md +1 -1
- package/docs/components/labeled-radio.md +1 -1
- package/docs/components/radio-card.md +1 -1
- package/docs/components/radio.md +1 -1
- package/docs/components/search-field.md +1 -1
- package/docs/components/section-block.md +1 -1
- package/docs/components/select.md +1 -1
- package/docs/components/simple-select.md +1 -1
- package/docs/components/switch.md +1 -1
- package/docs/components/tab-switch.md +1 -1
- package/docs/components/table.md +1 -1
- package/docs/components/text-editor.md +1 -1
- package/docs/components/textarea.md +1 -1
- package/docs/components/toggle-button.md +1 -1
- package/docs/components/toggle.md +1 -1
- package/docs/components/tree-folder.md +110 -0
- package/docs/how-to/forms-with-form-builder.md +2 -2
- package/docs/reference/components.md +16 -6
- package/docs/tutorials/component-composition.md +11 -13
- package/package.json +3 -2
- package/apps/lib/components/DataViews/DataViewRadio.tsx +0 -49
- package/apps/lib/components/DataViews/DataViewsConfigPanel.tsx +0 -393
- package/apps/lib/components/DataViews/DataViewsHeader.tsx +0 -207
- package/apps/lib/components/DataViews/DataViewsLayout.tsx +0 -332
- package/apps/lib/components/DataViews/FilterPanel.tsx +0 -493
- package/apps/lib/components/DataViews/HeaderSearch.tsx +0 -93
- package/apps/lib/components/DataViews/InboxView.tsx +0 -463
- package/apps/lib/components/DataViews/InboxViewCard.tsx +0 -127
- package/apps/lib/components/DataViews/KanbanView.tsx +0 -336
- package/apps/lib/components/DataViews/PanelControls.tsx +0 -39
- package/apps/lib/components/DataViews/SettingsPanel.tsx +0 -279
- package/apps/lib/components/DataViews/TableView.tsx +0 -212
- package/apps/lib/components/DataViews/TreeView.tsx +0 -364
- package/apps/lib/components/DataViews/fieldRenderers.tsx +0 -299
- package/apps/lib/components/DataViews/filters/DatePickerRangeFilter.tsx +0 -87
- package/apps/lib/components/DataViews/filters/DateRangePopover.tsx +0 -120
- package/apps/lib/components/DataViews/filters/PresetChips.tsx +0 -45
- package/apps/lib/components/DataViews/filters/RangeSliderWithInputs.tsx +0 -165
- package/apps/lib/components/DataViews/tree/TreeDrawer.tsx +0 -50
- package/apps/lib/components/DataViews/tree/TreeSidebar.tsx +0 -74
- package/apps/lib/hooks/useDataViewsState.ts +0 -175
- package/apps/lib/utils/dataViews/columnUtils.ts +0 -132
- package/apps/lib/utils/dataViews/fieldUtils.ts +0 -197
- package/apps/lib/utils/dataViews/nestedDataUtils.tsx +0 -371
- package/apps/lib/utils/dataViews/pathUtils.ts +0 -139
- package/apps/lib/utils/dataViews/rangeUtils.ts +0 -234
- package/apps/lib/utils/dataViews/treeUtils.ts +0 -396
- package/docs/components/data-views-config-panel.md +0 -208
- package/docs/components/data-views-layout.md +0 -291
- package/docs/components/inbox-view.md +0 -170
- package/docs/components/kanban-view.md +0 -135
- package/docs/components/table-view.md +0 -141
- package/docs/components/tree-view.md +0 -147
- package/docs/how-to/data-views-from-backend-response.md +0 -194
|
@@ -6,7 +6,7 @@ import type { ControllerRenderProps, FieldValues } from "react-hook-form";
|
|
|
6
6
|
import { InputField } from "../../InputField";
|
|
7
7
|
import { Textarea } from "../../Textarea";
|
|
8
8
|
import { PasswordLevel } from "../../PasswordLevel";
|
|
9
|
-
import { useLoading,
|
|
9
|
+
import { useLoading, useOnTable } from "../context";
|
|
10
10
|
import { formatNumber } from "../numberFormat";
|
|
11
11
|
import type { BaseFieldProps, CurrencyFieldProps, PasswordFieldProps } from "../types";
|
|
12
12
|
import { FieldShell } from "./FieldShell";
|
|
@@ -107,7 +107,7 @@ function NumberInput({
|
|
|
107
107
|
|
|
108
108
|
function TextLike({ props, type }: { props: BaseFieldProps; type: "text" | "email" | "password" }) {
|
|
109
109
|
const loading = useLoading();
|
|
110
|
-
const
|
|
110
|
+
const onTable = useOnTable();
|
|
111
111
|
return (
|
|
112
112
|
<FieldShell {...props}>
|
|
113
113
|
{(field) => (
|
|
@@ -117,7 +117,7 @@ function TextLike({ props, type }: { props: BaseFieldProps; type: "text" | "emai
|
|
|
117
117
|
value={field.value ?? ""}
|
|
118
118
|
placeholder={props.placeholder}
|
|
119
119
|
disabled={props.disabled || loading}
|
|
120
|
-
onTable={
|
|
120
|
+
onTable={onTable}
|
|
121
121
|
className="w-full"
|
|
122
122
|
/>
|
|
123
123
|
)}
|
|
@@ -133,7 +133,7 @@ export function EmailField(props: BaseFieldProps) {
|
|
|
133
133
|
}
|
|
134
134
|
export function PasswordField(props: PasswordFieldProps) {
|
|
135
135
|
const loading = useLoading();
|
|
136
|
-
const
|
|
136
|
+
const onTable = useOnTable();
|
|
137
137
|
return (
|
|
138
138
|
<FieldShell {...props}>
|
|
139
139
|
{(field) => (
|
|
@@ -144,7 +144,7 @@ export function PasswordField(props: PasswordFieldProps) {
|
|
|
144
144
|
value={field.value ?? ""}
|
|
145
145
|
placeholder={props.placeholder}
|
|
146
146
|
disabled={props.disabled || loading}
|
|
147
|
-
onTable={
|
|
147
|
+
onTable={onTable}
|
|
148
148
|
className="w-full"
|
|
149
149
|
/>
|
|
150
150
|
{props.strengthMeter && <PasswordLevel value={field.value ?? ""} />}
|
|
@@ -156,7 +156,7 @@ export function PasswordField(props: PasswordFieldProps) {
|
|
|
156
156
|
|
|
157
157
|
export function NumberField(props: BaseFieldProps) {
|
|
158
158
|
const loading = useLoading();
|
|
159
|
-
const
|
|
159
|
+
const onTable = useOnTable();
|
|
160
160
|
return (
|
|
161
161
|
<FieldShell {...props}>
|
|
162
162
|
{(field) => (
|
|
@@ -164,7 +164,7 @@ export function NumberField(props: BaseFieldProps) {
|
|
|
164
164
|
field={field}
|
|
165
165
|
placeholder={props.placeholder}
|
|
166
166
|
disabled={props.disabled || loading}
|
|
167
|
-
onTable={
|
|
167
|
+
onTable={onTable}
|
|
168
168
|
/>
|
|
169
169
|
)}
|
|
170
170
|
</FieldShell>
|
|
@@ -173,7 +173,7 @@ export function NumberField(props: BaseFieldProps) {
|
|
|
173
173
|
|
|
174
174
|
export function CurrencyField(props: CurrencyFieldProps) {
|
|
175
175
|
const loading = useLoading();
|
|
176
|
-
const
|
|
176
|
+
const onTable = useOnTable();
|
|
177
177
|
return (
|
|
178
178
|
<FieldShell {...props}>
|
|
179
179
|
{(field) => (
|
|
@@ -186,7 +186,7 @@ export function CurrencyField(props: CurrencyFieldProps) {
|
|
|
186
186
|
}
|
|
187
187
|
placeholder={props.placeholder}
|
|
188
188
|
disabled={props.disabled || loading}
|
|
189
|
-
onTable={
|
|
189
|
+
onTable={onTable}
|
|
190
190
|
/>
|
|
191
191
|
)}
|
|
192
192
|
</FieldShell>
|
|
@@ -8,6 +8,7 @@ import { cn } from "../../utils/cn";
|
|
|
8
8
|
import { Form } from "../Form";
|
|
9
9
|
import { SectionBlock, type SectionColor, type SectionVariant } from "../SectionBlock";
|
|
10
10
|
import { LoadingContext, DirectionContext, StepperContext, FormIdContext } from "./context";
|
|
11
|
+
import { markFieldKind } from "./field-kind";
|
|
11
12
|
import { Header } from "./header";
|
|
12
13
|
import type { FormBuilderRootProps } from "./types";
|
|
13
14
|
import {
|
|
@@ -97,6 +98,7 @@ function FormBuilderRoot<T extends FieldValues = FieldValues>({
|
|
|
97
98
|
values,
|
|
98
99
|
loading = false,
|
|
99
100
|
fieldDirection,
|
|
101
|
+
layout = "page",
|
|
100
102
|
resetOnSuccess,
|
|
101
103
|
conclusion,
|
|
102
104
|
className,
|
|
@@ -148,10 +150,18 @@ function FormBuilderRoot<T extends FieldValues = FieldValues>({
|
|
|
148
150
|
rest
|
|
149
151
|
);
|
|
150
152
|
|
|
151
|
-
// The fields column caps at
|
|
152
|
-
// standalone.
|
|
153
|
+
// The fields column caps at 1100px and centers — as the middle column of the grid, and
|
|
154
|
+
// standalone. `layout="bare"` drops the cap and the gutters so an embedded form fills its
|
|
155
|
+
// container instead: 48px of padding a side is most of a settings rail.
|
|
153
156
|
const fieldsInner = (
|
|
154
|
-
<div
|
|
157
|
+
<div
|
|
158
|
+
className={cn(
|
|
159
|
+
"flex w-full flex-col gap-4",
|
|
160
|
+
layout === "page" && "mx-auto max-w-[1100px] px-[48px]",
|
|
161
|
+
)}
|
|
162
|
+
>
|
|
163
|
+
{fields}
|
|
164
|
+
</div>
|
|
155
165
|
);
|
|
156
166
|
|
|
157
167
|
const formEl = (
|
|
@@ -284,3 +294,45 @@ export const FormBuilder = Object.assign(FormBuilderRoot, {
|
|
|
284
294
|
Back,
|
|
285
295
|
Next,
|
|
286
296
|
});
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* What each field produces, for consumers that read a form they did not author.
|
|
300
|
+
*
|
|
301
|
+
* Stamped here rather than on the field definitions because the aliases — `DateRange`, `Tags`,
|
|
302
|
+
* `Image` — only exist in the table above, and a reader checking "is this list complete?" should
|
|
303
|
+
* have one place to look. `Tags` and `MultiSelect` are the same component, so the repeat is
|
|
304
|
+
* harmless.
|
|
305
|
+
*/
|
|
306
|
+
(
|
|
307
|
+
[
|
|
308
|
+
[FormBuilder.Text, "text"],
|
|
309
|
+
[FormBuilder.Email, "text"],
|
|
310
|
+
[FormBuilder.Password, "text"],
|
|
311
|
+
[FormBuilder.Textarea, "text"],
|
|
312
|
+
[FormBuilder.Otp, "text"],
|
|
313
|
+
[FormBuilder.Color, "text"],
|
|
314
|
+
[FormBuilder.Phone, "text"],
|
|
315
|
+
[FormBuilder.Number, "number"],
|
|
316
|
+
[FormBuilder.Currency, "number"],
|
|
317
|
+
[FormBuilder.Select, "choice"],
|
|
318
|
+
[FormBuilder.SearchableSelect, "choice"],
|
|
319
|
+
[FormBuilder.RadioList, "choice"],
|
|
320
|
+
[FormBuilder.RadioCards, "choice"],
|
|
321
|
+
[FormBuilder.MultiSelect, "multiChoice"],
|
|
322
|
+
[FormBuilder.Tags, "multiChoice"],
|
|
323
|
+
[FormBuilder.CheckboxGroup, "multiChoice"],
|
|
324
|
+
[FormBuilder.TreeSelect, "multiChoice"],
|
|
325
|
+
[FormBuilder.Checkbox, "boolean"],
|
|
326
|
+
[FormBuilder.SwitchBox, "boolean"],
|
|
327
|
+
[FormBuilder.Date, "date"],
|
|
328
|
+
[FormBuilder.DateRange, "date"],
|
|
329
|
+
[FormBuilder.DateTime, "date"],
|
|
330
|
+
[FormBuilder.DateMultiple, "date"],
|
|
331
|
+
[FormBuilder.Slider, "slider"],
|
|
332
|
+
[FormBuilder.Custom, "custom"],
|
|
333
|
+
[FormBuilder.File, "custom"],
|
|
334
|
+
[FormBuilder.Image, "custom"],
|
|
335
|
+
[FormBuilder.Signature, "custom"],
|
|
336
|
+
[FormBuilder.RichText, "custom"],
|
|
337
|
+
] as const
|
|
338
|
+
).forEach(([component, kind]) => markFieldKind(component, kind));
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
export { FormBuilder } from "./form-builder";
|
|
2
2
|
export type { SectionProps } from "./form-builder";
|
|
3
|
+
export { fieldKindOf, markFieldKind } from "./field-kind";
|
|
3
4
|
export { FormHeaderBar } from "./header";
|
|
4
5
|
export type { HeaderProps, HeaderVariant, FormHeaderBarProps } from "./header";
|
|
5
6
|
export { RichTextField } from "../TextEditor/RichTextField";
|
|
6
7
|
export type { StepProps } from "./stepper";
|
|
7
|
-
export type { FieldDirection } from "./context";
|
|
8
|
+
export type { FieldDirection, CellMode } from "./context";
|
|
8
9
|
export type {
|
|
9
10
|
BaseFieldProps,
|
|
11
|
+
FieldKind,
|
|
10
12
|
OptionItem,
|
|
11
13
|
SelectFieldProps,
|
|
12
14
|
SearchableSelectFieldProps,
|
|
@@ -11,6 +11,35 @@ import type {
|
|
|
11
11
|
import type { FieldDirection } from "./context";
|
|
12
12
|
import type { SectionColor } from "../SectionBlock";
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* What shape of value a field produces, stamped on each `FormBuilder.*` field component.
|
|
16
|
+
*
|
|
17
|
+
* This is for consumers that have to interpret a form they did not author — `DataViews.Filters`
|
|
18
|
+
* reads it to decide whether a child's value becomes a list, a numeric range or a date range. It
|
|
19
|
+
* describes the field, not any one consumer's use of it.
|
|
20
|
+
*
|
|
21
|
+
* Read it with `fieldKindOf(element)` rather than comparing `element.type` against an imported
|
|
22
|
+
* component: identity comparison breaks across duplicated module instances and stops matching
|
|
23
|
+
* after a hot reload.
|
|
24
|
+
*/
|
|
25
|
+
export type FieldKind =
|
|
26
|
+
/** A string value — `Text`, `Textarea`, `Email`, `Password`, `Color`, `Phone`, `Otp`. */
|
|
27
|
+
| "text"
|
|
28
|
+
/** A number — `Number`, `Currency`. */
|
|
29
|
+
| "number"
|
|
30
|
+
/** One of a supplied list — `Select`, `SearchableSelect`, `RadioList`, `RadioCards`. */
|
|
31
|
+
| "choice"
|
|
32
|
+
/** Several of a supplied list — `MultiSelect`, `Tags`, `CheckboxGroup`, `TreeSelect`. */
|
|
33
|
+
| "multiChoice"
|
|
34
|
+
/** A boolean — `Checkbox`, `SwitchBox`. */
|
|
35
|
+
| "boolean"
|
|
36
|
+
/** A date or date range — `Date`, `DateRange`, `DateTime`, `DateMultiple`. */
|
|
37
|
+
| "date"
|
|
38
|
+
/** Two numbers when `range`, otherwise one — `Slider`. */
|
|
39
|
+
| "slider"
|
|
40
|
+
/** Whatever the caller renders — `Custom`, `File`, `Image`, `Signature`, `RichText`. */
|
|
41
|
+
| "custom";
|
|
42
|
+
|
|
14
43
|
/** Props shared by every `FormBuilder.*` field. `name` is the RHF path. */
|
|
15
44
|
export interface BaseFieldProps {
|
|
16
45
|
name: string;
|
|
@@ -249,6 +278,17 @@ export interface FormBuilderRootProps<T extends FieldValues = FieldValues> {
|
|
|
249
278
|
loading?: boolean;
|
|
250
279
|
/** Field row direction. Defaults to horizontal (vertical inside a drawer). */
|
|
251
280
|
fieldDirection?: FieldDirection;
|
|
281
|
+
/**
|
|
282
|
+
* How the fields column is framed.
|
|
283
|
+
*
|
|
284
|
+
* `"page"` (the default) is the full-page form: centred, capped at 1100px, and held off the
|
|
285
|
+
* edges by 48px gutters.
|
|
286
|
+
*
|
|
287
|
+
* `"bare"` drops all three so the fields fill whatever they are placed inside. Use it when the
|
|
288
|
+
* form is embedded in something narrow — a 260px settings rail has no room to give up 96px to
|
|
289
|
+
* gutters, and its controls would overflow the panel rather than fill it.
|
|
290
|
+
*/
|
|
291
|
+
layout?: "page" | "bare";
|
|
252
292
|
/** Reset to defaults after a successful submit. */
|
|
253
293
|
resetOnSuccess?: boolean;
|
|
254
294
|
/**
|
|
@@ -48,7 +48,7 @@ const trackStyles = cva(
|
|
|
48
48
|
// transparent and lighten on hover.
|
|
49
49
|
const optionStyles = cva(
|
|
50
50
|
[
|
|
51
|
-
"flex items-center justify-center gap-[
|
|
51
|
+
"flex items-center justify-center gap-[4px]",
|
|
52
52
|
"rounded-[8px]",
|
|
53
53
|
"font-[510] leading-none",
|
|
54
54
|
"transition-all duration-200 ease-in-out",
|
|
@@ -74,8 +74,11 @@ const optionStyles = cva(
|
|
|
74
74
|
true: [
|
|
75
75
|
"bg-white",
|
|
76
76
|
"text-[#1C1D1F]",
|
|
77
|
+
// The border is not in the design. It stays because its transparent twin below is what
|
|
78
|
+
// keeps both states the same width — without it, selecting a tab resizes the pill and
|
|
79
|
+
// shoves its neighbours. The design's own 4px gap absorbs the 1px.
|
|
77
80
|
"border border-black/5",
|
|
78
|
-
"shadow-[
|
|
81
|
+
"drop-shadow-[0px_0px_5px_rgba(0,0,0,0.25)]",
|
|
79
82
|
],
|
|
80
83
|
false: [
|
|
81
84
|
"border border-transparent",
|
|
@@ -121,8 +124,17 @@ function TabSwitchInner<T extends string = string>(
|
|
|
121
124
|
|
|
122
125
|
return (
|
|
123
126
|
<div key={option.value} className="flex items-center">
|
|
124
|
-
{
|
|
125
|
-
<div
|
|
127
|
+
{idx > 0 && (
|
|
128
|
+
<div
|
|
129
|
+
aria-hidden
|
|
130
|
+
className={cn(
|
|
131
|
+
// The slot is always here, and only its ink changes. Unmounting it instead cost
|
|
132
|
+
// the track 7px every time the rule below hid one, so picking a tab resized the
|
|
133
|
+
// track and shoved its neighbours sideways.
|
|
134
|
+
"mx-[3px] h-3 w-px",
|
|
135
|
+
showDivider ? "bg-border-presentation-action-disabled" : "bg-transparent",
|
|
136
|
+
)}
|
|
137
|
+
/>
|
|
126
138
|
)}
|
|
127
139
|
<button
|
|
128
140
|
type="button"
|
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
TreeFolderVisibleRow,
|
|
23
23
|
} from "./types";
|
|
24
24
|
import { useTreeFolderDnD } from "./useTreeFolderDnD";
|
|
25
|
+
import { DndContext, DragList } from "../../hooks/useDragDrop";
|
|
25
26
|
|
|
26
27
|
export type TreeFolderProps = {
|
|
27
28
|
data: TreeFolderNode[];
|
|
@@ -230,10 +231,9 @@ export const TreeFolder = forwardRef<TreeFolderHandle, TreeFolderProps>(
|
|
|
230
231
|
}, []);
|
|
231
232
|
|
|
232
233
|
// ---- DnD ----
|
|
233
|
-
const { dragIds, dropTarget,
|
|
234
|
+
const { dragIds, dropTarget, contextProps, ids } = useTreeFolderDnD({
|
|
234
235
|
data,
|
|
235
236
|
rowsById,
|
|
236
|
-
scrollContainerRef: scrollRef,
|
|
237
237
|
enabled: dndEnabled,
|
|
238
238
|
onMove: handleMoveInternal,
|
|
239
239
|
canDrop: ({ parentId }) => {
|
|
@@ -310,6 +310,7 @@ export const TreeFolder = forwardRef<TreeFolderHandle, TreeFolderProps>(
|
|
|
310
310
|
)}
|
|
311
311
|
|
|
312
312
|
<TreeFolderStyles />
|
|
313
|
+
<DndContext {...contextProps}>
|
|
313
314
|
<div ref={scrollRef} role="tree" className="tf-scroll flex-1 min-h-0 overflow-auto">
|
|
314
315
|
{isEmpty ? (
|
|
315
316
|
(emptyState ?? (
|
|
@@ -319,6 +320,7 @@ export const TreeFolder = forwardRef<TreeFolderHandle, TreeFolderProps>(
|
|
|
319
320
|
))
|
|
320
321
|
) : (
|
|
321
322
|
<div className="min-w-max" style={stripStyle}>
|
|
323
|
+
<DragList ids={ids} makeRoom={false}>
|
|
322
324
|
{visibleRows.map((row, idx) => {
|
|
323
325
|
const prevRow = visibleRows[idx - 1];
|
|
324
326
|
const nextRow = visibleRows[idx + 1];
|
|
@@ -364,13 +366,14 @@ export const TreeFolder = forwardRef<TreeFolderHandle, TreeFolderProps>(
|
|
|
364
366
|
dndEnabled={dndEnabled}
|
|
365
367
|
onSelect={handleSelect}
|
|
366
368
|
onToggle={handleToggle}
|
|
367
|
-
dragHandlers={getRowDragHandlers(row.node.id)}
|
|
368
369
|
/>
|
|
369
370
|
);
|
|
370
371
|
})}
|
|
372
|
+
</DragList>
|
|
371
373
|
</div>
|
|
372
374
|
)}
|
|
373
375
|
</div>
|
|
376
|
+
</DndContext>
|
|
374
377
|
</div>
|
|
375
378
|
);
|
|
376
379
|
},
|
|
@@ -1,20 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { ChevronRight, ChevronDown, GripVertical } from "lucide-react";
|
|
4
|
-
import
|
|
4
|
+
import { useDragItem } from "../../hooks/useDragDrop";
|
|
5
5
|
import { cn } from "../../utils/cn";
|
|
6
6
|
import { resolveIcon } from "./icons";
|
|
7
7
|
import type { TreeFolderIconResolver, TreeFolderNode, TreeFolderVisibleRow } from "./types";
|
|
8
8
|
|
|
9
|
-
export type TreeFolderRowDragHandlers = {
|
|
10
|
-
draggable: boolean;
|
|
11
|
-
onDragStart: (e: DragEvent<HTMLElement>) => void;
|
|
12
|
-
onDragEnd: (e: DragEvent<HTMLElement>) => void;
|
|
13
|
-
onDragOver: (e: DragEvent<HTMLElement>) => void;
|
|
14
|
-
onDragLeave: (e: DragEvent<HTMLElement>) => void;
|
|
15
|
-
onDrop: (e: DragEvent<HTMLElement>) => void;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
9
|
export type TreeFolderRowProps = {
|
|
19
10
|
row: TreeFolderVisibleRow;
|
|
20
11
|
rowHeight: number;
|
|
@@ -37,7 +28,6 @@ export type TreeFolderRowProps = {
|
|
|
37
28
|
dndEnabled: boolean;
|
|
38
29
|
onSelect: (id: string | null) => void;
|
|
39
30
|
onToggle: (id: string) => void;
|
|
40
|
-
dragHandlers: TreeFolderRowDragHandlers;
|
|
41
31
|
};
|
|
42
32
|
|
|
43
33
|
export function TreeFolderRow({
|
|
@@ -57,9 +47,14 @@ export function TreeFolderRow({
|
|
|
57
47
|
dndEnabled,
|
|
58
48
|
onSelect,
|
|
59
49
|
onToggle,
|
|
60
|
-
dragHandlers,
|
|
61
50
|
}: TreeFolderRowProps) {
|
|
62
51
|
const { node, level, isOpen, isInternal, ancestorHasMoreSiblings } = row;
|
|
52
|
+
|
|
53
|
+
const {
|
|
54
|
+
ref: dragRef,
|
|
55
|
+
handleProps: dragProps,
|
|
56
|
+
style: dragStyle,
|
|
57
|
+
} = useDragItem(node.id, !dndEnabled || Boolean(node.disabled), { follow: false });
|
|
63
58
|
const data = node;
|
|
64
59
|
const hasChildren = isInternal;
|
|
65
60
|
|
|
@@ -134,10 +129,13 @@ export function TreeFolderRow({
|
|
|
134
129
|
|
|
135
130
|
return (
|
|
136
131
|
<div
|
|
132
|
+
ref={dragRef}
|
|
137
133
|
data-row-id={node.id}
|
|
138
134
|
className={cn("select-none group/row", outerClassName)}
|
|
139
|
-
|
|
140
|
-
|
|
135
|
+
// The row is the drag surface, not just the grip: that is how this tree has always behaved,
|
|
136
|
+
// and the 8px threshold is what keeps a click on it still a click.
|
|
137
|
+
style={{ ...dragStyle, height: rowHeight }}
|
|
138
|
+
{...dragProps}
|
|
141
139
|
>
|
|
142
140
|
<span aria-hidden className={bandClassName} />
|
|
143
141
|
|
|
@@ -234,6 +232,10 @@ export function TreeFolderRow({
|
|
|
234
232
|
{data.name}
|
|
235
233
|
</span>
|
|
236
234
|
|
|
235
|
+
{/* `meta` has always been part of `TreeFolderNode` but was never painted. It sits after
|
|
236
|
+
the name, before the collapsed-child count, and only appears when a node supplies it. */}
|
|
237
|
+
{data.meta != null && <span className="shrink-0 pr-2">{data.meta}</span>}
|
|
238
|
+
|
|
237
239
|
{hasChildren && !isOpen && (
|
|
238
240
|
<span
|
|
239
241
|
className={cn(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { TreeFolder } from "./TreeFolder";
|
|
2
2
|
export type { TreeFolderHandle, TreeFolderProps } from "./TreeFolder";
|
|
3
3
|
export { TreeFolderRow } from "./TreeFolderRow";
|
|
4
|
-
export type { TreeFolderRowProps
|
|
4
|
+
export type { TreeFolderRowProps } from "./TreeFolderRow";
|
|
5
5
|
export { TreeFolderBreadcrumb } from "./TreeFolderBreadcrumb";
|
|
6
6
|
export { defaultIconRegistry, defaultIconFor } from "./icons";
|
|
7
7
|
export {
|