torch-glare 2.5.3 → 2.5.5

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.
Files changed (56) hide show
  1. package/apps/lib/components/ActionButton.tsx +39 -6
  2. package/apps/lib/components/Badge.tsx +5 -2
  3. package/apps/lib/components/BadgeField.tsx +71 -67
  4. package/apps/lib/components/Button.tsx +4 -4
  5. package/apps/lib/components/Calendar.tsx +7 -17
  6. package/apps/lib/components/Card.tsx +2 -1
  7. package/apps/lib/components/ColorPicker.tsx +1 -1
  8. package/apps/lib/components/ContextMenu.tsx +65 -22
  9. package/apps/lib/components/DataViews/context.ts +2 -2
  10. package/apps/lib/components/DataViews/data-views.tsx +3 -3
  11. package/apps/lib/components/DataViews/filters/filters.tsx +0 -2
  12. package/apps/lib/components/DataViews/views/table-view.tsx +190 -153
  13. package/apps/lib/components/Drawer.tsx +18 -1
  14. package/apps/lib/components/DropdownMenu.tsx +65 -22
  15. package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +19 -5
  16. package/apps/lib/components/FormBuilder/types.ts +16 -0
  17. package/apps/lib/components/FormRenderer/form-renderer.tsx +16 -5
  18. package/apps/lib/components/FormRenderer/stepper.tsx +22 -16
  19. package/apps/lib/components/HeaderBar.tsx +51 -53
  20. package/apps/lib/components/Input.tsx +19 -4
  21. package/apps/lib/components/InputField.tsx +46 -47
  22. package/apps/lib/components/Popover.tsx +108 -56
  23. package/apps/lib/components/SearchableSelect.tsx +22 -17
  24. package/apps/lib/components/SearchableTree.tsx +33 -17
  25. package/apps/lib/components/SearchableTreeDialog.tsx +21 -12
  26. package/apps/lib/components/SectionBlock.tsx +3 -1
  27. package/apps/lib/components/Select.tsx +86 -54
  28. package/apps/lib/components/SimpleSelect.tsx +2 -2
  29. package/apps/lib/components/SlideDatePicker.tsx +15 -4
  30. package/apps/lib/components/Stepper.tsx +329 -180
  31. package/apps/lib/components/Switch.tsx +2 -2
  32. package/apps/lib/components/TabSwitch.tsx +18 -12
  33. package/apps/lib/components/Table.tsx +1 -1
  34. package/apps/lib/components/Textarea.tsx +29 -4
  35. package/apps/lib/components/TreeDropDown.tsx +18 -6
  36. package/apps/lib/layouts/FieldSection.tsx +28 -2
  37. package/apps/lib/registry.json +7 -17
  38. package/apps/lib/tsconfig.tsbuildinfo +1 -1
  39. package/docs/components/action-button.md +3 -1
  40. package/docs/components/badge-field.md +4 -4
  41. package/docs/components/context-menu.md +3 -1
  42. package/docs/components/data-views/examples/filters.md +0 -1
  43. package/docs/components/data-views/index.md +1 -17
  44. package/docs/components/dropdown-menu.md +3 -0
  45. package/docs/components/form-builder.md +27 -1
  46. package/docs/components/header-bar.md +3 -2
  47. package/docs/components/input-field.md +3 -3
  48. package/docs/components/select.md +1 -1
  49. package/docs/components/stepper.md +119 -24
  50. package/docs/migration/changelog.md +13 -0
  51. package/docs/reference/tailwind-plugins.md +42 -0
  52. package/docs/tutorials/getting-started.md +32 -11
  53. package/package.json +1 -1
  54. package/apps/lib/components/DataViews/filters/summary.tsx +0 -65
  55. package/apps/lib/components/FormStepper.tsx +0 -272
  56. package/docs/components/form-stepper.md +0 -250
@@ -174,11 +174,22 @@ function FormRendererRoot<T extends FieldValues = FieldValues>({
174
174
  // and the rail still has room to be legible (a plain `minmax(0,…)` let it collapse to 0 in a
175
175
  // narrow preview frame). Past that floor the labels truncate instead of pushing.
176
176
  const bodyInner = isStepper ? (
177
- <div className="grid w-full grid-cols-[minmax(180px,1fr)_minmax(0,1100px)_minmax(180px,1fr)] gap-8">
178
- <StepperNav control={formInstance.control as Control<FieldValues>} />
179
- {fieldsColumn}
180
- {/* Empty third column — balances the rail's gutter so the middle column is centred. */}
181
- <div />
177
+ // `@container` on the wrapper, not on the grid: an element cannot query its own size, so the
178
+ // measured box has to be an ancestor. It sits here rather than on the scroll body because
179
+ // `bodyInner` is also used directly when there is no header, and this way both paths get a
180
+ // container. The form then reacts to the width it is actually given — a rail, a drawer, a
181
+ // narrow preview frame — instead of the viewport's.
182
+ <div className="@container w-full">
183
+ {/* Below `@lg` the balancing column is dropped: it is empty, and in a narrow container its
184
+ 180px floor plus a 32px gap is width the fields column needs more than the centring.
185
+ Both halves are required — a `display:none` child is not a grid item, but the third
186
+ track would still be reserved if the template kept describing it. */}
187
+ <div className="grid w-full grid-cols-[minmax(180px,1fr)_minmax(0,1100px)] gap-8 @lg:grid-cols-[minmax(180px,1fr)_minmax(0,1100px)_minmax(180px,1fr)]">
188
+ <StepperNav control={formInstance.control as Control<FieldValues>} />
189
+ {fieldsColumn}
190
+ {/* Empty third column — balances the rail's gutter so the middle column is centred. */}
191
+ <div className="hidden @lg:block" />
192
+ </div>
182
193
  </div>
183
194
  ) : (
184
195
  fieldsColumn
@@ -10,7 +10,16 @@ import { Button } from "../Button";
10
10
  // stepper — the rail, the state, Back/Next — is chrome and lives here. The dependency only ever
11
11
  // points this way: FormRenderer → FormBuilder, never back.
12
12
  import { StepContext, type StepRegistry } from "../FormBuilder/context";
13
- import { FormStepper, FormStep, FormStepIndicator, FormStepLabel } from "../FormStepper";
13
+ // Aliased: this module exports its own `Stepper` and `Step` (the wizard behaviour —
14
+ // `FormRenderer.Stepper` / `.Step`), which would otherwise collide with the presentational
15
+ // component of the same name.
16
+ import {
17
+ Stepper as StepperRail,
18
+ Step as StepPill,
19
+ StepIndicator,
20
+ StepLabel,
21
+ StepConnector,
22
+ } from "../Stepper";
14
23
 
15
24
  // ─── Stepper state context ───────────────────────────────────────────────────
16
25
 
@@ -116,7 +125,7 @@ function StepperNav({ control }: { control: Control<FieldValues> }) {
116
125
  // `min-w-0` so the rail can be squeezed: its grid track no longer grows to fit a long label
117
126
  // (see form-renderer.tsx), so the column has to be allowed to shrink and let the labels
118
127
  // truncate instead of spilling over the fields column.
119
- <FormStepper activeStep={currentStep} className="min-w-0 shrink-0 flex-col items-start gap-[4px]">
128
+ <StepperRail activeStep={currentStep} orientation="vertical" className="min-w-0 shrink-0">
120
129
  {titles.map((title, index) => {
121
130
  // The step buttons ARE the navigation: click to move. Backward is free;
122
131
  // clicking forward validates the steps in between (goToStep) and stops at
@@ -129,11 +138,11 @@ function StepperNav({ control }: { control: Control<FieldValues> }) {
129
138
  : "default";
130
139
  return (
131
140
  <React.Fragment key={title}>
132
- {/* The shrink/truncate pair is passed in rather than changed in `FormStepper`, so its
141
+ {/* The shrink/truncate pair is passed in rather than changed in `Stepper`, so its
133
142
  other consumers keep sizing to their labels. `shrink` beats the pill's own
134
143
  `shrink-0` and `truncate` beats the label's `whitespace-nowrap` — both via
135
- tailwind-merge, which `FormStepper` runs the caller's className through. */}
136
- <FormStep
144
+ tailwind-merge, which `Stepper` runs the caller's className through. */}
145
+ <StepPill
137
146
  index={index}
138
147
  type={type}
139
148
  onClick={() => void goToStep(index)}
@@ -142,20 +151,17 @@ function StepperNav({ control }: { control: Control<FieldValues> }) {
142
151
  // past the track instead of clipping inside it.
143
152
  className="min-w-0 max-w-full shrink"
144
153
  >
145
- <FormStepIndicator />
146
- <FormStepLabel className="truncate">{title}</FormStepLabel>
147
- </FormStep>
148
- {/* Connector between steps — a 3×16 rounded bar centred under the badge. */}
149
- {index < titles.length - 1 && (
150
- <div
151
- aria-hidden
152
- className="ms-[12.5px] mt-[2px] h-[16px] w-[3px] rounded-full bg-[#A0A0A0]"
153
- />
154
- )}
154
+ <StepIndicator />
155
+ {/* `max-w-none` opts out of the component's 106px cap: the rail sizes to its own
156
+ grid track, so the label should truncate there rather than 106px early. */}
157
+ <StepLabel className="max-w-none truncate">{title}</StepLabel>
158
+ </StepPill>
159
+ {/* Connector between steps — the component centres it under the indicator per size. */}
160
+ {index < titles.length - 1 && <StepConnector />}
155
161
  </React.Fragment>
156
162
  );
157
163
  })}
158
- </FormStepper>
164
+ </StepperRail>
159
165
  );
160
166
  }
161
167
 
@@ -22,13 +22,18 @@ import { Themes } from "../utils/types";
22
22
  * (renders plain "sales iNVOICE" on the LEFT, colored "de-344" on the RIGHT)
23
23
  */
24
24
 
25
- // Inner row: order is reversed for "detail" so the badge ends up on the right.
26
- const rowStyles = cva(["flex", "items-center"], {
25
+ // 28px / weight 510 — the design's `Font/Size/Display/Medium`, which is exactly what this class
26
+ // carries. Shared by the badge and the plain title so the two pieces cannot drift apart.
27
+ const headerTextStyles = "typography-display-medium-medium uppercase [font-feature-settings:'cv05'_on]";
28
+
29
+ // Colored pill holding `label`. 32px tall, 4px side padding, 8px radius — Figma `Header.Badge`.
30
+ const badgeStyles = cva(["flex", "h-8", "items-center", "justify-center", "rounded-lg", "px-1"], {
27
31
  variants: {
28
32
  variant: {
29
- new: "flex-row",
30
- edit: "flex-row",
31
- detail: "flex-row-reverse",
33
+ new: "bg-blue-sparkle-alpha-50",
34
+ edit: "bg-orange-alpha-50",
35
+ // `White Alpha/15`, not /30 — the detail chip is the faintest of the three.
36
+ detail: "bg-white-alpha-15",
32
37
  },
33
38
  },
34
39
  defaultVariants: {
@@ -36,49 +41,22 @@ const rowStyles = cva(["flex", "items-center"], {
36
41
  },
37
42
  });
38
43
 
39
- // Colored pill holding `label`.
40
- const badgeStyles = cva(
41
- ["flex", "h-8", "items-center", "justify-center", "gap-2.5", "rounded-lg", "px-1"],
42
- {
43
- variants: {
44
- variant: {
45
- new: "bg-blue-sparkle-alpha-50",
46
- edit: "bg-orange-alpha-50",
47
- detail: "bg-white-alpha-30",
48
- },
49
- },
50
- defaultVariants: {
51
- variant: "new",
52
- },
53
- },
54
- );
55
-
56
44
  // Text inside the colored pill.
57
- const badgeTextStyles = cva(
58
- [
59
- "font-sans",
60
- "text-[28px]",
61
- "font-[510]",
62
- "leading-normal",
63
- "uppercase",
64
- "[font-feature-settings:'cv05'_on]",
65
- ],
66
- {
67
- variants: {
68
- variant: {
69
- new: "text-blue-sparkle-200",
70
- edit: "text-orange-200",
71
- detail: "text-white-00",
72
- },
73
- },
74
- defaultVariants: {
75
- variant: "new",
45
+ const badgeTextStyles = cva([headerTextStyles], {
46
+ variants: {
47
+ variant: {
48
+ new: "text-blue-sparkle-200",
49
+ edit: "text-orange-200",
50
+ detail: "text-white-00",
76
51
  },
77
52
  },
78
- );
53
+ defaultVariants: {
54
+ variant: "new",
55
+ },
56
+ });
79
57
 
80
58
  interface HeaderBarProps
81
- extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof rowStyles> {
59
+ extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeStyles> {
82
60
  theme?: Themes;
83
61
  /** The colored emphasis pill text. */
84
62
  label: string;
@@ -88,25 +66,45 @@ interface HeaderBarProps
88
66
 
89
67
  const HeaderBar = forwardRef<HTMLDivElement, HeaderBarProps>(
90
68
  ({ variant = "new", label, title, theme, className, ...props }, ref) => {
69
+ const badge = (
70
+ <div className={cn(badgeStyles({ variant }))}>
71
+ <p className={cn(badgeTextStyles({ variant }))}>{label}</p>
72
+ </div>
73
+ );
74
+
75
+ // The plain title sits in its own 32px box with 6px side padding — that padding, not a row
76
+ // `gap`, is what separates it from the badge (matching the design's `padding` frame).
77
+ const plain = (
78
+ <div className="flex h-8 items-center justify-center px-1.5">
79
+ <p className={cn(headerTextStyles, "text-white-00")}>{title}</p>
80
+ </div>
81
+ );
82
+
91
83
  return (
92
84
  <div
93
85
  ref={ref}
94
86
  data-theme={theme}
95
87
  className={cn(
96
- "inline-flex flex-col items-start rounded-[14px] border border-black-600 bg-black-1000 p-1.5 shadow-[0_0_32px_2px_rgba(0,0,0,0.05),0_0_32px_2px_rgba(0,0,0,0.05)]",
88
+ "inline-flex flex-col items-start overflow-hidden rounded-[14px] border border-black-600 bg-black-1000 p-1.5 shadow-[0_0_32px_2px_rgba(0,0,0,0.05),0_0_32px_2px_rgba(0,0,0,0.05)]",
97
89
  className,
98
90
  )}
99
91
  {...props}
100
92
  >
101
- <div className={cn(rowStyles({ variant }))}>
102
- <div className={cn(badgeStyles({ variant }))}>
103
- <p className={cn(badgeTextStyles({ variant }))}>{label}</p>
104
- </div>
105
- <div className="flex h-8 items-center justify-center px-1.5">
106
- <p className="font-sans text-[28px] font-[510] leading-normal uppercase text-white-00 [font-feature-settings:'cv05'_on]">
107
- {title}
108
- </p>
109
- </div>
93
+ {/* `detail` genuinely swaps the two children rather than using `flex-row-reverse`. The
94
+ visual result is the same in LTR, but reversing the row would flip the pair the wrong
95
+ way under `dir="rtl"` and would read out of order to a screen reader. */}
96
+ <div className="flex items-center">
97
+ {variant === "detail" ? (
98
+ <>
99
+ {plain}
100
+ {badge}
101
+ </>
102
+ ) : (
103
+ <>
104
+ {badge}
105
+ {plain}
106
+ </>
107
+ )}
110
108
  </div>
111
109
  </div>
112
110
  );
@@ -62,7 +62,16 @@ interface TrillingProps extends HTMLAttributes<HTMLDivElement> {
62
62
  }
63
63
  export const Trilling = ({ children, className, ...props }: TrillingProps) => {
64
64
  return (
65
- <div {...props} className={cn("flex items-center justify-center h-full gap-1 py-1", className)}>
65
+ // `ps-1` mirrors the inner side of Figma's `EndField-Container` (`p-[4px]`), keeping the field's
66
+ // text clear of the end button instead of running straight into it.
67
+ //
68
+ // Logical, not physical: this slot flips to the left edge in RTL, so a `pl-1` would put the
69
+ // padding on the outside — measured as 0px of clearance and the button 8px from the edge
70
+ // instead of 4px. `padding-inline-start` follows the slot.
71
+ <div
72
+ {...props}
73
+ className={cn("flex items-center justify-center h-full gap-1 py-1 ps-1", className)}
74
+ >
66
75
  {children}
67
76
  </div>
68
77
  );
@@ -135,7 +144,10 @@ input:-webkit-autofill:active {
135
144
 
136
145
  export const GroupStyles = cva(
137
146
  [
138
- "flex w-full min-w-0 px-1 justify-center items-center",
147
+ // 3px, not 4: Figma's `EndField-Container` is `p-[4px]` and its stroke sits *inside* that
148
+ // padding, so the end button lands 4px from the field's outer edge. CSS `border-box` adds the
149
+ // 1px border on top of the padding, so 4px here would render 5px. 1 + 3 = 4.
150
+ "flex w-full min-w-0 px-[3px] justify-center items-center",
139
151
  "typography-body-small-regular",
140
152
  "border",
141
153
  "transition-all duration-200 ease-in-out",
@@ -180,14 +192,17 @@ export const GroupStyles = cva(
180
192
  "[&_input]:text-white",
181
193
  ],
182
194
  },
195
+ // Radius per Figma's `radius/*` collection: S is `radius/lg` (8px) and M is `radius/xl`
196
+ // (12px) — a deliberate jump, not consecutive steps. This `Group` is the shared field root,
197
+ // so it is also what gives `InputField` and `InnerLabelField` their corners.
183
198
  size: {
184
199
  S: [
185
200
  "h-[30px] min-h-[30px]",
186
- "rounded-[6px] [&_input]:h-[30px] [&_div[data-role='icon']]:text-[16px]",
201
+ "rounded-radius-lg [&_input]:h-[30px] [&_div[data-role='icon']]:text-[16px]",
187
202
  ],
188
203
  M: [
189
204
  "h-[40px] min-h-[40px]",
190
- "rounded-[8px] [&_input]:h-[40px] [&_div[data-role='icon']]:text-[18px] [&_div[data-role='icon']]:px-[2px]",
205
+ "rounded-radius-xl [&_input]:h-[40px] [&_div[data-role='icon']]:text-[18px] [&_div[data-role='icon']]:px-[2px]",
191
206
  ],
192
207
  },
193
208
  error: {
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { forwardRef, InputHTMLAttributes, ReactNode, useEffect, useRef, useState } from "react";
3
3
  import { cn } from "../utils/cn";
4
- import { Tooltip, ToolTipSide } from "./Tooltip";
4
+ import { ToolTipSide } from "./Tooltip";
5
5
  import { Popover, PopoverContent, PopoverTrigger } from "./Popover";
6
6
  import { ActionButton } from "./ActionButton";
7
7
  import { Themes } from "../utils/types";
@@ -13,8 +13,13 @@ export interface Props extends Omit<InputHTMLAttributes<HTMLInputElement>, "size
13
13
  icon?: ReactNode; // to add left side icon if you pass it
14
14
  childrenSide?: ReactNode; // to add action button to the end of the input
15
15
  popoverChildren?: ReactNode; // to add drop down list if you pass it
16
- errorMessage?: string; // to show tooltip component when error_message not null
16
+ /** Marks the field invalid: any non-undefined value turns on the negative border. */
17
+ errorMessage?: string;
17
18
  onTable?: boolean; // to change the border style of the component when it is on table
19
+ /**
20
+ * @deprecated Ignored. The error tooltip was removed — an invalid field is shown by its negative
21
+ * border alone. Kept so existing call sites keep compiling; it will go in a future major.
22
+ */
18
23
  toolTipSide?: ToolTipSide;
19
24
  theme?: Themes;
20
25
  }
@@ -29,6 +34,7 @@ export const InputField = forwardRef<HTMLInputElement, Props>(
29
34
  errorMessage,
30
35
  onTable,
31
36
  variant = "PresentationStyle",
37
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- deprecated no-op, destructured to keep it out of the {...props} spread
32
38
  toolTipSide,
33
39
  theme,
34
40
  className,
@@ -49,54 +55,47 @@ export const InputField = forwardRef<HTMLInputElement, Props>(
49
55
  // TODO: make the user input visible when input is focused
50
56
  return (
51
57
  <Popover onOpenChange={setIsPopoverOpen}>
52
- <Tooltip
53
- theme={theme}
54
- toolTipSide={toolTipSide}
55
- open={errorMessage !== undefined}
56
- text={errorMessage}
57
- >
58
- <PopoverTrigger ref={triggerRef} asChild>
59
- <Group
60
- error={errorMessage !== undefined}
61
- onTable={onTable}
62
- size={size}
63
- variant={variant}
64
- data-theme={theme}
58
+ <PopoverTrigger ref={triggerRef} asChild>
59
+ <Group
60
+ error={errorMessage !== undefined}
61
+ onTable={onTable}
62
+ size={size}
63
+ variant={variant}
64
+ data-theme={theme}
65
+ onFocus={(e) => {
66
+ setPopoverWidth(e.currentTarget.offsetWidth);
67
+ setIsPopoverOpen(!isPopoverOpen);
68
+ inputRef.current?.focus();
69
+ }}
70
+ className={className}
71
+ >
72
+ {icon && <Icon>{icon}</Icon>}
73
+ <Input
74
+ {...props}
65
75
  onFocus={(e) => {
66
- setPopoverWidth(e.currentTarget.offsetWidth);
67
- setIsPopoverOpen(!isPopoverOpen);
68
- inputRef.current?.focus();
76
+ setIsPopoverOpen(true);
77
+ props.onFocus?.(e);
78
+ }}
79
+ onBlur={(e) => {
80
+ setIsPopoverOpen(false);
81
+ props.onBlur?.(e);
69
82
  }}
70
- className={className}
71
- >
72
- {icon && <Icon>{icon}</Icon>}
73
- <Input
74
- {...props}
75
- onFocus={(e) => {
76
- setIsPopoverOpen(true);
77
- props.onFocus?.(e);
78
- }}
79
- onBlur={(e) => {
80
- setIsPopoverOpen(false);
81
- props.onBlur?.(e);
82
- }}
83
- ref={inputRef}
84
- />
83
+ ref={inputRef}
84
+ />
85
85
 
86
- <Trilling>
87
- {childrenSide}
88
- {popoverChildren && (
89
- <PopoverActionButton
90
- size={size}
91
- variant={variant}
92
- isPopoverOpen={isPopoverOpen}
93
- disabled={props.disabled}
94
- />
95
- )}
96
- </Trilling>
97
- </Group>
98
- </PopoverTrigger>
99
- </Tooltip>
86
+ <Trilling>
87
+ {childrenSide}
88
+ {popoverChildren && (
89
+ <PopoverActionButton
90
+ size={size}
91
+ variant={variant}
92
+ isPopoverOpen={isPopoverOpen}
93
+ disabled={props.disabled}
94
+ />
95
+ )}
96
+ </Trilling>
97
+ </Group>
98
+ </PopoverTrigger>
100
99
 
101
100
  {popoverChildren && !props.disabled && (
102
101
  <PopoverContent