torch-glare 2.4.1 → 2.4.2
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/Button.tsx +1 -1
- package/apps/lib/components/Card.tsx +47 -20
- package/apps/lib/components/ColorPicker.tsx +441 -0
- package/apps/lib/components/ConclusionHeader.tsx +148 -0
- package/apps/lib/components/DatePicker.tsx +2 -0
- package/apps/lib/components/Drawer.tsx +66 -24
- package/apps/lib/components/FormBuilder/DisplayField.tsx +40 -0
- package/apps/lib/components/FormBuilder/context.ts +71 -0
- package/apps/lib/components/FormBuilder/fields/ChoiceFields.tsx +65 -0
- package/apps/lib/components/FormBuilder/fields/ColorField.tsx +76 -0
- package/apps/lib/components/FormBuilder/fields/CustomField.tsx +16 -0
- package/apps/lib/components/FormBuilder/fields/DateField.tsx +34 -0
- package/apps/lib/components/FormBuilder/fields/FieldArray.tsx +79 -0
- package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +174 -0
- package/apps/lib/components/FormBuilder/fields/FileField.tsx +40 -0
- package/apps/lib/components/FormBuilder/fields/OptionListFields.tsx +139 -0
- package/apps/lib/components/FormBuilder/fields/OtpField.tsx +32 -0
- package/apps/lib/components/FormBuilder/fields/PhoneField.tsx +82 -0
- package/apps/lib/components/FormBuilder/fields/RichTextEditorField.tsx +37 -0
- package/apps/lib/components/FormBuilder/fields/SelectField.tsx +101 -0
- package/apps/lib/components/FormBuilder/fields/SignatureField.tsx +175 -0
- package/apps/lib/components/FormBuilder/fields/SliderField.tsx +72 -0
- package/apps/lib/components/FormBuilder/fields/SwitchBoxField.tsx +42 -0
- package/apps/lib/components/FormBuilder/fields/TableField.tsx +312 -0
- package/apps/lib/components/FormBuilder/fields/TextField.tsx +222 -0
- package/apps/lib/components/FormBuilder/fields/TreeSelectField.tsx +49 -0
- package/apps/lib/components/FormBuilder/fields/countries.ts +303 -0
- package/apps/lib/components/FormBuilder/fields/index.ts +25 -0
- package/apps/lib/components/FormBuilder/form-builder.tsx +292 -0
- package/apps/lib/components/FormBuilder/header.tsx +105 -0
- package/apps/lib/components/FormBuilder/index.ts +37 -0
- package/apps/lib/components/FormBuilder/numberFormat.ts +16 -0
- package/apps/lib/components/FormBuilder/stepper.tsx +217 -0
- package/apps/lib/components/FormBuilder/submit.tsx +41 -0
- package/apps/lib/components/FormBuilder/types.ts +264 -0
- package/apps/lib/components/FormBuilder/viewFormat.tsx +137 -0
- package/apps/lib/components/FormRenderer/FormDrawer.tsx +121 -0
- package/apps/lib/components/FormRenderer/form-renderer.tsx +113 -0
- package/apps/lib/components/FormRenderer/index.ts +9 -0
- package/apps/lib/components/FormRenderer/types.ts +79 -0
- package/apps/lib/components/FormSummary.tsx +282 -0
- package/apps/lib/components/ImageAttachment.tsx +36 -61
- package/apps/lib/components/Label.tsx +49 -42
- package/apps/lib/components/RadioCard.tsx +2 -0
- package/apps/lib/components/SearchableSelect.tsx +9 -3
- package/apps/lib/components/SectionBlock.tsx +16 -8
- package/apps/lib/components/Select.tsx +41 -120
- package/apps/lib/components/TextEditor/RichTextField.tsx +46 -0
- package/apps/lib/components/{TextEditor.tsx → TextEditor/TextEditor.tsx} +63 -9
- package/apps/lib/components/TextEditor/TextEditorToolbar.tsx +429 -0
- package/apps/lib/components/TextEditor/editor-tools/AlignmentTune.ts +70 -0
- package/apps/lib/components/TextEditor/editor-tools/ColorInlineTool.ts +50 -0
- package/apps/lib/components/TextEditor/editor-tools/StrikethroughInlineTool.ts +48 -0
- package/apps/lib/components/TextEditor/editor-tools/inlineFormat.ts +98 -0
- package/apps/lib/{types → components/TextEditor}/editorjs.d.ts +19 -0
- package/apps/lib/components/TextEditor/index.ts +7 -0
- package/apps/lib/components/Textarea.tsx +1 -1
- package/apps/lib/registry.json +51 -58
- package/apps/lib/tsconfig.tsbuildinfo +1 -0
- package/apps/lib/utils/color.ts +175 -0
- package/docs/components/card.md +4 -2
- package/docs/components/chart-block-tool.md +5 -4
- package/docs/components/color-picker.md +101 -0
- package/docs/components/conclusion-header.md +80 -0
- package/docs/components/drawer.md +153 -102
- package/docs/components/form-builder.md +270 -0
- package/docs/components/form-renderer.md +228 -0
- package/docs/components/form-summary.md +123 -0
- package/docs/components/image-attachment.md +10 -4
- package/docs/components/table-dnd-wrapper.md +5 -3
- package/docs/components/text-editor.md +18 -0
- package/docs/how-to/form-and-list-recipes.md +26 -19
- package/docs/how-to/forms-with-form-builder.md +408 -0
- package/docs/how-to/guides.md +153 -179
- package/docs/tutorials/building-first-form.md +150 -159
- package/package.json +1 -1
- /package/apps/lib/components/{ChartBlockTool.ts → TextEditor/ChartBlockTool.ts} +0 -0
- /package/apps/lib/components/{TableDnDWrapper.ts → TextEditor/TableDnDWrapper.ts} +0 -0
- /package/apps/lib/{utils → components/TextEditor}/markdownParser.ts +0 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure color-math helpers for the `ColorPicker` component (and anything else that
|
|
3
|
+
* needs to move between hex, RGB, HSV and HSL). No dependencies, no DOM — safe to
|
|
4
|
+
* unit-test and to import on the server.
|
|
5
|
+
*
|
|
6
|
+
* Conventions:
|
|
7
|
+
* - `RGBA` channels are 0–255 (`r`,`g`,`b`) and 0–1 (`a`).
|
|
8
|
+
* - `HSV`/`HSL` use `h` 0–360, everything else 0–100.
|
|
9
|
+
* - Hex output is uppercase, `#rrggbb` when fully opaque and `#rrggbbaa` otherwise.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export interface RGBA {
|
|
13
|
+
r: number;
|
|
14
|
+
g: number;
|
|
15
|
+
b: number;
|
|
16
|
+
a: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface HSV {
|
|
20
|
+
h: number;
|
|
21
|
+
s: number;
|
|
22
|
+
v: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface HSL {
|
|
26
|
+
h: number;
|
|
27
|
+
s: number;
|
|
28
|
+
l: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Clamp `n` into the inclusive `[min, max]` range. */
|
|
32
|
+
export function clamp(n: number, min: number, max: number): number {
|
|
33
|
+
return n < min ? min : n > max ? max : n;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Round to `digits` decimal places (default 0). */
|
|
37
|
+
export function round(n: number, digits = 0): number {
|
|
38
|
+
const f = 10 ** digits;
|
|
39
|
+
return Math.round(n * f) / f;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const toHex2 = (n: number) => clamp(Math.round(n), 0, 255).toString(16).padStart(2, "0");
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Parse a hex color into `RGBA`. Accepts `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`
|
|
46
|
+
* with or without the leading `#`. Returns `null` for anything unparseable.
|
|
47
|
+
*/
|
|
48
|
+
export function parseHex(input: string): RGBA | null {
|
|
49
|
+
if (typeof input !== "string") return null;
|
|
50
|
+
let hex = input.trim().replace(/^#/, "");
|
|
51
|
+
|
|
52
|
+
// Expand shorthand (#rgb / #rgba → #rrggbb(aa)).
|
|
53
|
+
if (hex.length === 3 || hex.length === 4) {
|
|
54
|
+
hex = hex
|
|
55
|
+
.split("")
|
|
56
|
+
.map((c) => c + c)
|
|
57
|
+
.join("");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (hex.length !== 6 && hex.length !== 8) return null;
|
|
61
|
+
if (!/^[0-9a-fA-F]+$/.test(hex)) return null;
|
|
62
|
+
|
|
63
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
64
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
65
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
66
|
+
const a = hex.length === 8 ? parseInt(hex.slice(6, 8), 16) / 255 : 1;
|
|
67
|
+
return { r, g, b, a: round(a, 2) };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Format `RGBA` as an uppercase hex string — `#rrggbb` if opaque, else `#rrggbbaa`. */
|
|
71
|
+
export function formatHex({ r, g, b, a }: RGBA): string {
|
|
72
|
+
const base = `#${toHex2(r)}${toHex2(g)}${toHex2(b)}`.toUpperCase();
|
|
73
|
+
if (a >= 1) return base;
|
|
74
|
+
return `${base}${toHex2(clamp(a, 0, 1) * 255).toUpperCase()}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** RGB (0–255) → HSV (h 0–360, s/v 0–100). Alpha is carried through unchanged elsewhere. */
|
|
78
|
+
export function rgbToHsv({ r, g, b }: { r: number; g: number; b: number }): HSV {
|
|
79
|
+
const rn = r / 255;
|
|
80
|
+
const gn = g / 255;
|
|
81
|
+
const bn = b / 255;
|
|
82
|
+
const max = Math.max(rn, gn, bn);
|
|
83
|
+
const min = Math.min(rn, gn, bn);
|
|
84
|
+
const d = max - min;
|
|
85
|
+
|
|
86
|
+
let h = 0;
|
|
87
|
+
if (d !== 0) {
|
|
88
|
+
if (max === rn) h = ((gn - bn) / d) % 6;
|
|
89
|
+
else if (max === gn) h = (bn - rn) / d + 2;
|
|
90
|
+
else h = (rn - gn) / d + 4;
|
|
91
|
+
h *= 60;
|
|
92
|
+
if (h < 0) h += 360;
|
|
93
|
+
}
|
|
94
|
+
const s = max === 0 ? 0 : d / max;
|
|
95
|
+
// Keep 2 decimals: HSV is the picker's internal state, so rounding to whole degrees/percents
|
|
96
|
+
// here would visibly shift a color that was only ever passed in (e.g. #10B981 → #11BA82).
|
|
97
|
+
return { h: round(h, 2), s: round(s * 100, 2), v: round(max * 100, 2) };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** HSV (h 0–360, s/v 0–100) → RGB (0–255). */
|
|
101
|
+
export function hsvToRgb({ h, s, v }: HSV): { r: number; g: number; b: number } {
|
|
102
|
+
const hn = ((h % 360) + 360) % 360;
|
|
103
|
+
const sn = clamp(s, 0, 100) / 100;
|
|
104
|
+
const vn = clamp(v, 0, 100) / 100;
|
|
105
|
+
|
|
106
|
+
const c = vn * sn;
|
|
107
|
+
const x = c * (1 - Math.abs(((hn / 60) % 2) - 1));
|
|
108
|
+
const m = vn - c;
|
|
109
|
+
|
|
110
|
+
let rp = 0;
|
|
111
|
+
let gp = 0;
|
|
112
|
+
let bp = 0;
|
|
113
|
+
if (hn < 60) [rp, gp, bp] = [c, x, 0];
|
|
114
|
+
else if (hn < 120) [rp, gp, bp] = [x, c, 0];
|
|
115
|
+
else if (hn < 180) [rp, gp, bp] = [0, c, x];
|
|
116
|
+
else if (hn < 240) [rp, gp, bp] = [0, x, c];
|
|
117
|
+
else if (hn < 300) [rp, gp, bp] = [x, 0, c];
|
|
118
|
+
else [rp, gp, bp] = [c, 0, x];
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
r: round((rp + m) * 255),
|
|
122
|
+
g: round((gp + m) * 255),
|
|
123
|
+
b: round((bp + m) * 255),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** RGB (0–255) → HSL (h 0–360, s/l 0–100). */
|
|
128
|
+
export function rgbToHsl({ r, g, b }: { r: number; g: number; b: number }): HSL {
|
|
129
|
+
const rn = r / 255;
|
|
130
|
+
const gn = g / 255;
|
|
131
|
+
const bn = b / 255;
|
|
132
|
+
const max = Math.max(rn, gn, bn);
|
|
133
|
+
const min = Math.min(rn, gn, bn);
|
|
134
|
+
const d = max - min;
|
|
135
|
+
const l = (max + min) / 2;
|
|
136
|
+
|
|
137
|
+
let h = 0;
|
|
138
|
+
let s = 0;
|
|
139
|
+
if (d !== 0) {
|
|
140
|
+
s = d / (1 - Math.abs(2 * l - 1));
|
|
141
|
+
if (max === rn) h = ((gn - bn) / d) % 6;
|
|
142
|
+
else if (max === gn) h = (bn - rn) / d + 2;
|
|
143
|
+
else h = (rn - gn) / d + 4;
|
|
144
|
+
h *= 60;
|
|
145
|
+
if (h < 0) h += 360;
|
|
146
|
+
}
|
|
147
|
+
return { h: round(h), s: round(s * 100), l: round(l * 100) };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** HSL (h 0–360, s/l 0–100) → RGB (0–255). */
|
|
151
|
+
export function hslToRgb({ h, s, l }: HSL): { r: number; g: number; b: number } {
|
|
152
|
+
const hn = ((h % 360) + 360) % 360;
|
|
153
|
+
const sn = clamp(s, 0, 100) / 100;
|
|
154
|
+
const ln = clamp(l, 0, 100) / 100;
|
|
155
|
+
|
|
156
|
+
const c = (1 - Math.abs(2 * ln - 1)) * sn;
|
|
157
|
+
const x = c * (1 - Math.abs(((hn / 60) % 2) - 1));
|
|
158
|
+
const m = ln - c / 2;
|
|
159
|
+
|
|
160
|
+
let rp = 0;
|
|
161
|
+
let gp = 0;
|
|
162
|
+
let bp = 0;
|
|
163
|
+
if (hn < 60) [rp, gp, bp] = [c, x, 0];
|
|
164
|
+
else if (hn < 120) [rp, gp, bp] = [x, c, 0];
|
|
165
|
+
else if (hn < 180) [rp, gp, bp] = [0, c, x];
|
|
166
|
+
else if (hn < 240) [rp, gp, bp] = [0, x, c];
|
|
167
|
+
else if (hn < 300) [rp, gp, bp] = [x, 0, c];
|
|
168
|
+
else [rp, gp, bp] = [c, 0, x];
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
r: round((rp + m) * 255),
|
|
172
|
+
g: round((gp + m) * 255),
|
|
173
|
+
b: round((bp + m) * 255),
|
|
174
|
+
};
|
|
175
|
+
}
|
package/docs/components/card.md
CHANGED
|
@@ -12,7 +12,7 @@ dependencies:
|
|
|
12
12
|
|
|
13
13
|
# Card
|
|
14
14
|
|
|
15
|
-
> A flexible container component for grouping and displaying content. Supports compound architecture with header, description, and content sections.
|
|
15
|
+
> A flexible container component for grouping and displaying content. Supports compound architecture with header, description, and content sections, and polymorphic rendering. Static by default — opt into hover states with `variant="clickable"`.
|
|
16
16
|
|
|
17
17
|
## Installation
|
|
18
18
|
|
|
@@ -74,10 +74,11 @@ function Example() {
|
|
|
74
74
|
### Clickable Card
|
|
75
75
|
|
|
76
76
|
```typescript
|
|
77
|
+
// `variant="clickable"` gives the hover highlight + pointer cursor.
|
|
77
78
|
<Card
|
|
78
79
|
as="button"
|
|
80
|
+
variant="clickable"
|
|
79
81
|
onClick={() => console.log('Card clicked')}
|
|
80
|
-
className="cursor-pointer"
|
|
81
82
|
>
|
|
82
83
|
<CardHeader>Interactive Card</CardHeader>
|
|
83
84
|
<CardDescription>Click me to perform an action</CardDescription>
|
|
@@ -205,6 +206,7 @@ function ImageCard() {
|
|
|
205
206
|
|
|
206
207
|
| Prop | Type | Default | Description |
|
|
207
208
|
|------|------|---------|-------------|
|
|
209
|
+
| `variant` | `'default' \| 'clickable'` | `'default'` | `'default'` is a static container with **no hover effect**. `'clickable'` adds the hover border highlight + pointer cursor — use it when the whole card is an interactive target. |
|
|
208
210
|
| `as` | `React.ElementType` | `'section'` | Element type to render as |
|
|
209
211
|
| `asChild` | `boolean` | `false` | Merge props onto child element |
|
|
210
212
|
| `htmlFor` | `string` | - | For label association when as="label" |
|
|
@@ -20,15 +20,16 @@ TORCH Glare is a copy-in library: the CLI copies this module's source into your
|
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
22
|
npx torch-glare@latest init
|
|
23
|
-
npx torch-glare@latest add
|
|
23
|
+
npx torch-glare@latest add TextEditor
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
`ChartBlockTool` ships as part of the `TextEditor` folder, so `add TextEditor` copies it in.
|
|
26
27
|
It depends on the `chart.js` npm package (installed automatically by the CLI).
|
|
27
28
|
|
|
28
29
|
## Import
|
|
29
30
|
|
|
30
31
|
```typescript
|
|
31
|
-
import ChartBlockTool,
|
|
32
|
+
import { ChartBlockTool, type ChartBlockData } from "@/components/TextEditor";
|
|
32
33
|
```
|
|
33
34
|
|
|
34
35
|
## Quick Examples
|
|
@@ -37,7 +38,7 @@ import ChartBlockTool, { ChartBlockData } from "@/components/ChartBlockTool";
|
|
|
37
38
|
|
|
38
39
|
```typescript
|
|
39
40
|
import EditorJS from "@editorjs/editorjs";
|
|
40
|
-
import ChartBlockTool from "@/components/
|
|
41
|
+
import { ChartBlockTool } from "@/components/TextEditor";
|
|
41
42
|
|
|
42
43
|
const editor = new EditorJS({
|
|
43
44
|
holder: "editor",
|
|
@@ -56,7 +57,7 @@ editors. The block persists as `ChartBlockData` in the editor's saved output.
|
|
|
56
57
|
### The saved data shape
|
|
57
58
|
|
|
58
59
|
```typescript
|
|
59
|
-
import { ChartBlockData } from "@/components/
|
|
60
|
+
import type { ChartBlockData } from "@/components/TextEditor";
|
|
60
61
|
|
|
61
62
|
const block: ChartBlockData = {
|
|
62
63
|
chartType: "bar",
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: ColorPicker
|
|
3
|
+
description: A full Figma-style color picker with a saturation/value area, hue and opacity sliders, an eyedropper, switchable HEX/RGB/HSL inputs, and preset swatches
|
|
4
|
+
group: Forms
|
|
5
|
+
keywords: [color-picker, color, palette, hex, rgb, hsl, hsv, opacity, alpha, eyedropper, swatch]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# ColorPicker
|
|
9
|
+
|
|
10
|
+
> A full Figma-style color palette opened from **a trigger you supply**: a draggable
|
|
11
|
+
> saturation/value area, a hue slider, an optional opacity slider, an eyedropper (where the
|
|
12
|
+
> browser supports it), switchable **HEX / RGB / HSL** numeric inputs, and preset swatches.
|
|
13
|
+
> The value is always a hex string.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx torch-glare@latest init
|
|
19
|
+
npx torch-glare@latest add ColorPicker
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`add` also copies its dependencies — `utils/color` (the pure hex/RGB/HSV/HSL math), plus the
|
|
23
|
+
`Popover` and `Input` components.
|
|
24
|
+
|
|
25
|
+
## Import
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { ColorPicker } from "@/components/ColorPicker";
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
ColorPicker renders **no trigger of its own** — you pass one as its child (rendered via `asChild`,
|
|
34
|
+
so it must forward ref/props to a DOM node). The current color is handed to that child based on
|
|
35
|
+
what it is:
|
|
36
|
+
|
|
37
|
+
| Child | Receives |
|
|
38
|
+
|---|---|
|
|
39
|
+
| an **input** — native `<input>`/`<textarea>`, or any element already given a `value` prop | the hex as **`value`** (plus an `onChange` that commits a typed hex, if you didn't supply one) |
|
|
40
|
+
| **anything else** | the hex as **`children`** — unless it already has children, which are left untouched |
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
const [color, setColor] = useState("#3B82F6");
|
|
44
|
+
|
|
45
|
+
// input child → gets `value={color}`
|
|
46
|
+
<ColorPicker value={color} onChange={setColor}>
|
|
47
|
+
<input className="…" />
|
|
48
|
+
</ColorPicker>
|
|
49
|
+
|
|
50
|
+
// non-input child → gets the hex as its content
|
|
51
|
+
<ColorPicker value={color} onChange={setColor}>
|
|
52
|
+
<Button variant="BorderStyle" />
|
|
53
|
+
</ColorPicker>
|
|
54
|
+
|
|
55
|
+
// a child with its own content keeps it — render the value yourself
|
|
56
|
+
<ColorPicker value={color} onChange={setColor}>
|
|
57
|
+
<button type="button">
|
|
58
|
+
<span style={{ backgroundColor: color }} />
|
|
59
|
+
{color}
|
|
60
|
+
</button>
|
|
61
|
+
</ColorPicker>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
With presets and opacity disabled (always emits `#rrggbb`):
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
<ColorPicker
|
|
68
|
+
value={color}
|
|
69
|
+
onChange={setColor}
|
|
70
|
+
alpha={false}
|
|
71
|
+
presets={["#005ECC", "#047854", "#E30C30", "#F5A623"]}
|
|
72
|
+
>
|
|
73
|
+
<Button variant="BorderStyle" />
|
|
74
|
+
</ColorPicker>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Props
|
|
78
|
+
|
|
79
|
+
| Prop | Type | Default | Description |
|
|
80
|
+
| ----------- | -------------------------- | ------------- | --------------------------------------------------------------------------- |
|
|
81
|
+
| `children` | `ReactElement` | — (required) | The trigger. Gets the hex as `value` if it's an input, else as `children`. |
|
|
82
|
+
| `value` | `string` | `"#000000"` | Current color as a hex string (`#rrggbb` or `#rrggbbaa`). |
|
|
83
|
+
| `onChange` | `(hex: string) => void` | — | Called on every change. Emits `#rrggbbaa` only when opacity < 100%. |
|
|
84
|
+
| `presets` | `string[]` | — | Quick-pick swatches shown at the bottom of the panel. |
|
|
85
|
+
| `alpha` | `boolean` | `true` | Show the opacity slider + input and allow 8-digit output. |
|
|
86
|
+
| `disabled` | `boolean` | `false` | Disable the trigger and all controls. |
|
|
87
|
+
| `theme` | `"dark" \| "light" \| "default"` | — | Applied as `data-theme`. |
|
|
88
|
+
|
|
89
|
+
## Value format
|
|
90
|
+
|
|
91
|
+
The value stays `#rrggbb` while fully opaque (backward-compatible with plain hex fields) and is
|
|
92
|
+
promoted to `#rrggbbaa` only when opacity drops below 100%. Feeding a 3-, 6-, or 8-digit hex
|
|
93
|
+
(with or without `#`) back in via `value` is always accepted.
|
|
94
|
+
|
|
95
|
+
## Notes
|
|
96
|
+
|
|
97
|
+
- The **eyedropper** button appears only where the native [`EyeDropper` API](https://developer.mozilla.org/docs/Web/API/EyeDropper)
|
|
98
|
+
is available (Chromium/Edge); it is hidden on Firefox/Safari.
|
|
99
|
+
- Used by `FormBuilder.Color` — see [form-builder.md](./form-builder.md).
|
|
100
|
+
- Color math lives in `utils/color` (`parseHex`, `formatHex`, `rgbToHsv`, `hsvToRgb`,
|
|
101
|
+
`rgbToHsl`, `hslToRgb`) and is reusable on its own.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: ConclusionHeader
|
|
3
|
+
description: A clickable section header with a chevron toggle — white-alpha hover surfaces, chevron down when open and sideways when closed, and a "Disabled" badge when disabled. Direction-aware (LTR/RTL). Used for each FormSummary group title.
|
|
4
|
+
component: true
|
|
5
|
+
group: Layout & Containers
|
|
6
|
+
keywords: [conclusion-header, collapsible, header, disclosure, accordion, toggle, chevron, expand, collapse, section]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# ConclusionHeader
|
|
10
|
+
|
|
11
|
+
A clickable **section header** that toggles a collapsible region, per the
|
|
12
|
+
*Conclusion-Header-2.0* design. Its surfaces are **button overlays** (not an input field): at
|
|
13
|
+
rest the label stands alone, and on hover it gains a `Button/ContStyle-Hover` pill while the
|
|
14
|
+
chevron gains a `Button/Secondary` box. The chevron is hidden at rest while open, shows
|
|
15
|
+
**sideways** when closed (left in LTR, right in RTL), and flips to **down** on hover. When
|
|
16
|
+
`disabled` the label greys out, truncates at 120px, and a gray-subtle `Badge` replaces the
|
|
17
|
+
chevron.
|
|
18
|
+
|
|
19
|
+
It renders only the header — you own the collapsible body. It works **uncontrolled**
|
|
20
|
+
(manages its own open state) or **controlled** (pass `open` + `onOpenChange`):
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
// Uncontrolled — reflect the state with onOpenChange
|
|
24
|
+
const [open, setOpen] = useState(true)
|
|
25
|
+
|
|
26
|
+
<ConclusionHeader label="Invoice" defaultOpen onOpenChange={setOpen} />
|
|
27
|
+
{open && <section>…body…</section>}
|
|
28
|
+
|
|
29
|
+
// Controlled — you drive `open`
|
|
30
|
+
<ConclusionHeader label="Invoice" open={open} onOpenChange={setOpen} />
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx torch-glare@latest init
|
|
37
|
+
npx torch-glare@latest add ConclusionHeader
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Imports
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { ConclusionHeader } from '@/components/ConclusionHeader'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Props
|
|
47
|
+
|
|
48
|
+
| Prop | Type | Default | Notes |
|
|
49
|
+
|---|---|---|---|
|
|
50
|
+
| `label` | `ReactNode` | — | The header text. |
|
|
51
|
+
| `open` | `boolean` | — | **Controlled** open state. Omit to let the header manage its own. |
|
|
52
|
+
| `defaultOpen` | `boolean` | `true` | Initial open state when **uncontrolled**. |
|
|
53
|
+
| `onOpenChange` | `(open: boolean) => void` | — | Called with the next open state on click. |
|
|
54
|
+
| `disabled` | `boolean` | `false` | Greys the label and shows a `Badge` instead of the chevron. |
|
|
55
|
+
| `disabledLabel` | `string` | `"Disabled"` | Badge text when `disabled`. |
|
|
56
|
+
| `theme` | `'dark' \| 'light' \| 'default'` | — | Theme override; otherwise inherits the nearest `data-theme`. |
|
|
57
|
+
|
|
58
|
+
Extra props spread to the underlying `<button>`, which is what the header renders — so it is
|
|
59
|
+
focusable, toggles on <kbd>Enter</kbd>/<kbd>Space</kbd>, and exposes `aria-expanded` for
|
|
60
|
+
free. A `ref` is forwarded to that `HTMLButtonElement`.
|
|
61
|
+
|
|
62
|
+
## Behaviour
|
|
63
|
+
|
|
64
|
+
- **Open** (rest): label only — no surface, no chevron.
|
|
65
|
+
- **Open hover**: `Button/ContStyle-Hover` pill (label inset 4px) + `Button/Secondary`
|
|
66
|
+
chevron box with a **down** chevron.
|
|
67
|
+
- **Closed** (rest): label + a bare **sideways** chevron (`<` LTR / `>` RTL), no box.
|
|
68
|
+
- **Closed hover**: same surfaces as open hover; the chevron flips to down.
|
|
69
|
+
- **Disabled**: greyed label truncated at 120px + a `● Disabled` badge sitting beside it
|
|
70
|
+
(not at the row's end); not clickable.
|
|
71
|
+
|
|
72
|
+
Direction is inherited from the ambient `dir` — the design's RTL variants are the LTR ones
|
|
73
|
+
mirrored, which the component's logical properties produce automatically.
|
|
74
|
+
|
|
75
|
+
## Composition
|
|
76
|
+
|
|
77
|
+
Uses the design-system tokens directly (`Button/Secondary`, `Button/ContStyle-Hover`) rather
|
|
78
|
+
than the input/button components, and reuses [Badge](./badge.md) for the disabled state.
|
|
79
|
+
[FormSummary](./form-summary.md) uses it as each **group** title, so every group of totals
|
|
80
|
+
collapses independently.
|