@solidrt/components 0.0.18 → 0.0.19
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/AGENTS.md +33 -2
- package/README.md +244 -1
- package/package.json +5 -2
- package/src/badge.tsx +50 -0
- package/src/button.tsx +19 -10
- package/src/card.tsx +48 -0
- package/src/checkbox.tsx +65 -0
- package/src/context-menu.tsx +189 -0
- package/src/divider.tsx +33 -0
- package/src/index.ts +30 -1
- package/src/nav-shell.tsx +160 -0
- package/src/policy.ts +108 -0
- package/src/progress-bar.tsx +81 -0
- package/src/qrcode.tsx +72 -0
- package/src/radio.tsx +100 -0
- package/src/scroll-view.tsx +5 -1
- package/src/select.tsx +203 -0
- package/src/slider.tsx +116 -0
- package/src/spinner.tsx +74 -0
- package/src/split-view.tsx +54 -0
- package/src/switch.tsx +58 -0
- package/src/text-input.tsx +24 -16
- package/src/theme.ts +72 -17
- package/src/tooltip.tsx +112 -0
- package/src/types.ts +4 -4
package/AGENTS.md
CHANGED
|
@@ -43,10 +43,41 @@ Most components group props into two objects, plus top-level event handlers:
|
|
|
43
43
|
hovered }`. No pointer capture: a drag out cancels via onPointerLeave.
|
|
44
44
|
- `Button` - themed Pressable: accent box + label (string/number child), scales
|
|
45
45
|
on press (via reactive style); colors from `theme.color.primary`/`onPrimary`.
|
|
46
|
+
- `Switch` - on/off toggle; `value`/`onChange`, controlled or uncontrolled
|
|
47
|
+
(`defaultValue`). Track fills `primary`/`surfaceAlt`, thumb slides.
|
|
48
|
+
- `Checkbox` - `checked`/`onChange` (or `defaultChecked`); checkmark drawn via a
|
|
49
|
+
core `<d-path>` when checked.
|
|
50
|
+
- `RadioGroup` + `Radio` - single-selection pair; the group owns
|
|
51
|
+
`value`/`onChange` (or `defaultValue`) and shares it to `Radio` children via a
|
|
52
|
+
module-local context (not a cross-component dependency). `Radio value=...`;
|
|
53
|
+
string/number child renders as a themed label.
|
|
54
|
+
- `Slider` - horizontal; `value`/`onChange` (or `defaultValue`), `min`/`max`/
|
|
55
|
+
`step`. Pointer x mapped to value via `getBoundingBox`; uses core
|
|
56
|
+
`setPointerCapture` so a drag keeps tracking off the track (within the window).
|
|
57
|
+
- `Card` - themed surface container: padded column box, `surface` fill, `border`
|
|
58
|
+
stroke, rounded. Optional `title` heading; override paint via `style`.
|
|
59
|
+
- `Divider` - thin rule in `border` color; `orientation` (default horizontal),
|
|
60
|
+
`thickness`. Stretches across the cross axis (full width in a column).
|
|
61
|
+
- `Badge` - small rounded pill for counts/labels/status; `primary`/`onPrimary`
|
|
62
|
+
by default, override via `style.backgroundColor`/`style.color`.
|
|
63
|
+
- `Spinner` - indeterminate rotating arc, driven by core `onFrame`; `size`,
|
|
64
|
+
`thickness`, `speed` (rev/s). Color from `primary`, override via `style.color`.
|
|
65
|
+
- `ProgressBar` - horizontal; determinate with `value` in [0,1] (fill grows from
|
|
66
|
+
the left), indeterminate when `value` is undefined (segment slides, `onFrame`).
|
|
67
|
+
Track `surfaceAlt` / fill `primary`; override via `style.backgroundColor`/`color`.
|
|
68
|
+
- `QrCode` - QR for `data: string`, built from primitives (same-color row runs
|
|
69
|
+
merged into one box on a light panel); grid memoized on `data`/`level`.
|
|
70
|
+
`moduleSize`/`margin`/`radius`/`level` (L/M/Q/H). Paints black-on-white by
|
|
71
|
+
default (NOT the theme) to stay scannable; override `color`/`background` only
|
|
72
|
+
if contrast holds. Deps on `qrcode-generator`.
|
|
46
73
|
- `SafeArea` - pads children clear of system UI (notches, status bars); top and
|
|
47
74
|
bottom on by default, pass `false`/a number per edge.
|
|
48
|
-
- `theme` / `setTheme`
|
|
49
|
-
|
|
75
|
+
- `theme` / `setTheme` / `darkTheme` / `lightTheme` - shared REACTIVE appearance
|
|
76
|
+
(backed by a Solid store, so reads are tracked and switching recolors the live
|
|
77
|
+
UI). `setTheme(lightTheme)`/`setTheme(darkTheme)` switch presets;
|
|
78
|
+
`setTheme({...})` merges a one-level-deep override. Default is dark. Color
|
|
79
|
+
tokens: `background`, `surface`, `surfaceAlt`, `text`, `textMuted`, `border`,
|
|
80
|
+
`primary`, `onPrimary`, `danger`, `scrim`.
|
|
50
81
|
|
|
51
82
|
## Minimal app (verified to render)
|
|
52
83
|
|
package/README.md
CHANGED
|
@@ -12,7 +12,21 @@ bun add @solidrt/components
|
|
|
12
12
|
|
|
13
13
|
## Theming
|
|
14
14
|
|
|
15
|
-
Appearance (colors, spacing, border, font size) is controlled via
|
|
15
|
+
Appearance (colors, spacing, border, font size) is controlled via a shared, reactive theme. Reads are tracked, so switching the theme at runtime recolors the live UI without remounting.
|
|
16
|
+
|
|
17
|
+
Two presets ship out of the box, `darkTheme` and `lightTheme`. The default is dark. Switch by passing a full preset, or apply a targeted override with a partial:
|
|
18
|
+
|
|
19
|
+
```jsx
|
|
20
|
+
import { setTheme, darkTheme, lightTheme } from "@solidrt/components"
|
|
21
|
+
|
|
22
|
+
setTheme(lightTheme) // switch to light
|
|
23
|
+
setTheme(darkTheme) // switch to dark
|
|
24
|
+
setTheme({ color: { primary: "#ff2d55" } }) // override one token
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`setTheme` merges one level deep per category, so a partial only touches the keys you pass.
|
|
28
|
+
|
|
29
|
+
The color tokens are `background` (window fill), `surface` (control/card fill), `surfaceAlt` (subtle raised/track fill), `text`, `textMuted`, `border`, `primary`, `onPrimary`, `danger`, and `scrim` (modal dim). Non-color tokens are `spacing`, `radius`, `borderWidth`, and `text` (font sizes), shared across presets.
|
|
16
30
|
|
|
17
31
|
## Layout and style
|
|
18
32
|
|
|
@@ -293,6 +307,235 @@ import { Button } from "@solidrt/components"
|
|
|
293
307
|
| `style` | `StyleProps` | Overrides background, radius, etc. |
|
|
294
308
|
| `children` | `any` | Label text, or custom content. |
|
|
295
309
|
|
|
310
|
+
### Switch
|
|
311
|
+
|
|
312
|
+
An on/off toggle. The track fills with `primary` when on and `surfaceAlt` when off; the thumb slides across. Controlled via `value`/`onChange`, or uncontrolled via `defaultValue`. Built on `Pressable`, so `disabled` takes no pointer events.
|
|
313
|
+
|
|
314
|
+
```jsx
|
|
315
|
+
import { Switch } from "@solidrt/components"
|
|
316
|
+
import { createSignal } from "@solidjs/signals"
|
|
317
|
+
|
|
318
|
+
function NotifyToggle() {
|
|
319
|
+
let [on, setOn] = createSignal(true)
|
|
320
|
+
return <Switch value={on()} onChange={setOn} />
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
**Props**
|
|
325
|
+
|
|
326
|
+
| Prop | Type | Default | Description |
|
|
327
|
+
| -------------- | ---------------------------- | ------- | ----------------------------------------------- |
|
|
328
|
+
| `value` | `boolean` | - | Controlled state. Omit for uncontrolled. |
|
|
329
|
+
| `defaultValue` | `boolean` | `false` | Initial value for uncontrolled use. |
|
|
330
|
+
| `onChange` | `(value: boolean) => void` | - | Fires with the new value on toggle. |
|
|
331
|
+
| `disabled` | `boolean` | `false` | Takes no pointer events when true. |
|
|
332
|
+
| `layout` | `LayoutProps` | - | Overrides sizing/positioning of the track. |
|
|
333
|
+
| `style` | `StyleProps` | - | Overrides track colors and radius. |
|
|
334
|
+
|
|
335
|
+
### Checkbox
|
|
336
|
+
|
|
337
|
+
A checkbox. When checked it fills with `primary` and draws a checkmark; otherwise it is an empty bordered box. Controlled via `checked`/`onChange`, or uncontrolled via `defaultChecked`.
|
|
338
|
+
|
|
339
|
+
```jsx
|
|
340
|
+
import { Checkbox } from "@solidrt/components"
|
|
341
|
+
|
|
342
|
+
<Checkbox checked={agree()} onChange={setAgree} />
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**Props**
|
|
346
|
+
|
|
347
|
+
| Prop | Type | Default | Description |
|
|
348
|
+
| ---------------- | --------------------------- | ------- | ------------------------------------------ |
|
|
349
|
+
| `checked` | `boolean` | - | Controlled state. Omit for uncontrolled. |
|
|
350
|
+
| `defaultChecked` | `boolean` | `false` | Initial value for uncontrolled use. |
|
|
351
|
+
| `onChange` | `(checked: boolean) => void`| - | Fires with the new state on toggle. |
|
|
352
|
+
| `disabled` | `boolean` | `false` | Takes no pointer events when true. |
|
|
353
|
+
| `layout` | `LayoutProps` | - | Overrides sizing. |
|
|
354
|
+
| `style` | `StyleProps` | - | Overrides box colors, border, and radius. |
|
|
355
|
+
|
|
356
|
+
### RadioGroup / Radio
|
|
357
|
+
|
|
358
|
+
A single-selection group. `RadioGroup` owns the selected value and shares it with its `Radio` children; each `Radio` is a ring with an inner dot when selected. A string/number child of `Radio` renders as a themed label beside the ring. Controlled via `value`/`onChange` on the group, or uncontrolled via `defaultValue`.
|
|
359
|
+
|
|
360
|
+
```jsx
|
|
361
|
+
import { RadioGroup, Radio } from "@solidrt/components"
|
|
362
|
+
|
|
363
|
+
<RadioGroup value={plan()} onChange={setPlan}>
|
|
364
|
+
<Radio value="free">Free</Radio>
|
|
365
|
+
<Radio value="pro">Pro</Radio>
|
|
366
|
+
<Radio value="team">Team</Radio>
|
|
367
|
+
</RadioGroup>
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
**RadioGroup props**
|
|
371
|
+
|
|
372
|
+
| Prop | Type | Default | Description |
|
|
373
|
+
| -------------- | --------------------------- | ------- | ---------------------------------------- |
|
|
374
|
+
| `value` | `unknown` | - | Controlled selected value. |
|
|
375
|
+
| `defaultValue` | `unknown` | - | Initial selection for uncontrolled use. |
|
|
376
|
+
| `onChange` | `(value: unknown) => void` | - | Fires with the newly selected value. |
|
|
377
|
+
| `disabled` | `boolean` | `false` | Disables every `Radio` in the group. |
|
|
378
|
+
| `layout` | `LayoutProps` | - | Layout of the group container. |
|
|
379
|
+
| `children` | `any` | - | `Radio` elements. |
|
|
380
|
+
|
|
381
|
+
**Radio props**
|
|
382
|
+
|
|
383
|
+
| Prop | Type | Description |
|
|
384
|
+
| ---------- | ------------- | --------------------------------------------------- |
|
|
385
|
+
| `value` | `unknown` | This option's value; selecting it sets the group. |
|
|
386
|
+
| `disabled` | `boolean` | Disables this option (also disabled by the group). |
|
|
387
|
+
| `layout` | `LayoutProps` | Layout of the option row. |
|
|
388
|
+
| `style` | `StyleProps` | Paint properties of the option row. |
|
|
389
|
+
| `children` | `any` | A string/number label, or custom content. |
|
|
390
|
+
|
|
391
|
+
### Slider
|
|
392
|
+
|
|
393
|
+
A horizontal slider. The groove fills up to the thumb; pressing or dragging the track sets the value from the pointer position. Controlled via `value`/`onChange`, or uncontrolled via `defaultValue`. The drag uses pointer capture, so it keeps tracking when the pointer drifts off the track (anywhere within the window).
|
|
394
|
+
|
|
395
|
+
```jsx
|
|
396
|
+
import { Slider } from "@solidrt/components"
|
|
397
|
+
|
|
398
|
+
<Slider value={volume()} onChange={setVolume} min={0} max={100} step={1} layout={{ width: 180 }} />
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
**Props**
|
|
402
|
+
|
|
403
|
+
| Prop | Type | Default | Description |
|
|
404
|
+
| -------------- | -------------------------- | ------- | -------------------------------------------- |
|
|
405
|
+
| `value` | `number` | - | Controlled value. Omit for uncontrolled. |
|
|
406
|
+
| `defaultValue` | `number` | `min` | Initial value for uncontrolled use. |
|
|
407
|
+
| `min` | `number` | `0` | Lower bound. |
|
|
408
|
+
| `max` | `number` | `100` | Upper bound. |
|
|
409
|
+
| `step` | `number` | - | Snap increment. Omit for continuous. |
|
|
410
|
+
| `onChange` | `(value: number) => void` | - | Fires with the new value while dragging. |
|
|
411
|
+
| `disabled` | `boolean` | `false` | Takes no pointer events when true. |
|
|
412
|
+
| `layout` | `LayoutProps` | - | Layout of the track (e.g. `width`). |
|
|
413
|
+
| `style` | `StyleProps` | - | Transform only (`x`/`y`/`rotate`/`scale`). |
|
|
414
|
+
|
|
415
|
+
### Card
|
|
416
|
+
|
|
417
|
+
A themed surface container: a padded column box with a `surface` fill, a subtle `border` stroke, and rounded corners. All colors come from the theme, so it recolors live on a theme switch. Pass a `title` for a heading, or lay out the content yourself. Override any paint via `style`, spacing/sizing via `layout`.
|
|
418
|
+
|
|
419
|
+
```jsx
|
|
420
|
+
import { Card } from "@solidrt/components"
|
|
421
|
+
|
|
422
|
+
<Card title="Profile" layout={{ width: 360 }}>
|
|
423
|
+
<Text>Card body content.</Text>
|
|
424
|
+
</Card>
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**Props**
|
|
428
|
+
|
|
429
|
+
| Prop | Type | Default | Description |
|
|
430
|
+
| ---------- | ------------- | --------- | ---------------------------------------------------- |
|
|
431
|
+
| `title` | `string` | - | Optional heading rendered above the content. |
|
|
432
|
+
| `children` | `any` | - | Card content. |
|
|
433
|
+
| `layout` | `LayoutProps` | - | Box layout (e.g. `width`, `gap`, `padding`). |
|
|
434
|
+
| `style` | `StyleProps` | - | Paint overrides: `backgroundColor`, `borderColor`, `borderWidth`, `borderRadius`, transform. |
|
|
435
|
+
|
|
436
|
+
### Divider
|
|
437
|
+
|
|
438
|
+
A thin rule in the theme `border` color. It stretches across its container on the cross axis: full width inside a column, full height inside a row (pass `orientation="vertical"`). Add spacing with `layout` margins, and override the color via `style.backgroundColor`.
|
|
439
|
+
|
|
440
|
+
```jsx
|
|
441
|
+
import { Divider } from "@solidrt/components"
|
|
442
|
+
|
|
443
|
+
<Divider />
|
|
444
|
+
<Divider orientation="vertical" />
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
**Props**
|
|
448
|
+
|
|
449
|
+
| Prop | Type | Default | Description |
|
|
450
|
+
| ------------- | ----------------------------- | -------------- | -------------------------------------- |
|
|
451
|
+
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Rule direction. |
|
|
452
|
+
| `thickness` | `number` | `1` | Line thickness in pixels. |
|
|
453
|
+
| `layout` | `LayoutProps` | - | Layout (e.g. margins for spacing). |
|
|
454
|
+
| `style` | `StyleProps` | - | `backgroundColor` overrides the color. |
|
|
455
|
+
|
|
456
|
+
### Badge
|
|
457
|
+
|
|
458
|
+
A small rounded pill for counts, labels, and status. Accent `primary` fill with `onPrimary` text by default; a string/number child is rendered as the themed label, anything else as-is. Override the fill via `style.backgroundColor` and the label color via `style.color`.
|
|
459
|
+
|
|
460
|
+
```jsx
|
|
461
|
+
import { Badge } from "@solidrt/components"
|
|
462
|
+
|
|
463
|
+
<Badge>New</Badge>
|
|
464
|
+
<Badge style={{ backgroundColor: theme.color.danger }}>Error</Badge>
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
**Props**
|
|
468
|
+
|
|
469
|
+
| Prop | Type | Default | Description |
|
|
470
|
+
| ---------- | ------------- | ------- | -------------------------------------------------- |
|
|
471
|
+
| `children` | `any` | - | String/number renders as the label; else as-is. |
|
|
472
|
+
| `layout` | `LayoutProps` | - | Box layout (e.g. padding overrides). |
|
|
473
|
+
| `style` | `StyleProps` | - | `backgroundColor` (fill), `color` (label), transform. |
|
|
474
|
+
|
|
475
|
+
### Spinner
|
|
476
|
+
|
|
477
|
+
An indeterminate spinner: a 270-degree arc that rotates continuously. It is driven by core `onFrame`, so it participates in demand-driven rendering and stops when unmounted. Color comes from the theme `primary`; override via `style.color`.
|
|
478
|
+
|
|
479
|
+
```jsx
|
|
480
|
+
import { Spinner } from "@solidrt/components"
|
|
481
|
+
|
|
482
|
+
<Spinner />
|
|
483
|
+
<Spinner size={32} thickness={4} speed={1.5} />
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
**Props**
|
|
487
|
+
|
|
488
|
+
| Prop | Type | Default | Description |
|
|
489
|
+
| ----------- | ------------- | ------- | ---------------------------------- |
|
|
490
|
+
| `size` | `number` | `24` | Overall diameter in pixels. |
|
|
491
|
+
| `thickness` | `number` | `3` | Arc stroke width in pixels. |
|
|
492
|
+
| `speed` | `number` | `1` | Revolutions per second. |
|
|
493
|
+
| `layout` | `LayoutProps` | - | Layout of the box. |
|
|
494
|
+
| `style` | `StyleProps` | - | `color` sets the arc; plus transform. |
|
|
495
|
+
|
|
496
|
+
### ProgressBar
|
|
497
|
+
|
|
498
|
+
A horizontal progress bar. Determinate when given a `value` in `[0, 1]` (the fill grows from the left); indeterminate when `value` is undefined (a short segment slides back and forth, driven by core `onFrame`). Track and fill colors come from the theme; override the track via `style.backgroundColor` and the fill via `style.color`.
|
|
499
|
+
|
|
500
|
+
```jsx
|
|
501
|
+
import { ProgressBar } from "@solidrt/components"
|
|
502
|
+
|
|
503
|
+
<ProgressBar value={0.4} /> // determinate
|
|
504
|
+
<ProgressBar /> // indeterminate
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
**Props**
|
|
508
|
+
|
|
509
|
+
| Prop | Type | Default | Description |
|
|
510
|
+
| -------- | ------------- | ------- | --------------------------------------------------- |
|
|
511
|
+
| `value` | `number` | - | Progress in `[0, 1]`. Omit for an indeterminate bar. |
|
|
512
|
+
| `layout` | `LayoutProps` | - | Layout (e.g. `width`, `height`). |
|
|
513
|
+
| `style` | `StyleProps` | - | `backgroundColor` (track), `color` (fill). |
|
|
514
|
+
|
|
515
|
+
### QrCode
|
|
516
|
+
|
|
517
|
+
Renders a QR code for `data` out of primitives: same-color modules in a row collapse into one box, drawn on a light quiet-zone panel. The grid recomputes only when `data` or `level` changes. It paints black on white by default (not the theme) so it stays scannable through a theme switch; override `color`/`background` only if the contrast still holds.
|
|
518
|
+
|
|
519
|
+
```jsx
|
|
520
|
+
import { QrCode } from "@solidrt/components"
|
|
521
|
+
|
|
522
|
+
<QrCode data="https://solidjs.com" />
|
|
523
|
+
<QrCode data={ticket()} moduleSize={8} level="L" />
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
**Props**
|
|
527
|
+
|
|
528
|
+
| Prop | Type | Default | Description |
|
|
529
|
+
| ------------ | -------------------------- | -------------- | -------------------------------------------------------------- |
|
|
530
|
+
| `data` | `string` | - | The string to encode (URL, pairing ticket, text, ...). |
|
|
531
|
+
| `moduleSize` | `number` | `6` | Pixels per module (the smallest square). |
|
|
532
|
+
| `margin` | `number` | `16` | Quiet-zone padding in pixels around the grid; keep non-zero. |
|
|
533
|
+
| `color` | `string` | `"#000000"` | Dark-module color. |
|
|
534
|
+
| `background` | `string` | `"#ffffff"` | Panel/light-module color. |
|
|
535
|
+
| `level` | `"L" \| "M" \| "Q" \| "H"` | `"M"` | Error-correction level; higher tolerates more damage but caps data length sooner. |
|
|
536
|
+
| `radius` | `number` | `8` | Corner radius of the background panel. |
|
|
537
|
+
| `layout` | `LayoutProps` | - | Layout of the outer box. |
|
|
538
|
+
|
|
296
539
|
## License
|
|
297
540
|
|
|
298
541
|
MIT. Copyright (c) 2026 Antoine van Wel.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidrt/components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Antoine van Wel",
|
|
6
6
|
"type": "module",
|
|
@@ -12,8 +12,11 @@
|
|
|
12
12
|
"src/",
|
|
13
13
|
"AGENTS.md"
|
|
14
14
|
],
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"qrcode-generator": "^2.0.4"
|
|
17
|
+
},
|
|
15
18
|
"peerDependencies": {
|
|
16
19
|
"@solidjs/signals": "2.0.0-beta.15",
|
|
17
|
-
"@solidrt/core": "0.0.
|
|
20
|
+
"@solidrt/core": "0.0.19"
|
|
18
21
|
}
|
|
19
22
|
}
|
package/src/badge.tsx
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Show } from "@solidrt/core"
|
|
2
|
+
import type { LayoutProps } from "@solidrt/core"
|
|
3
|
+
import { theme } from "./theme"
|
|
4
|
+
import type { StyleProps } from "./types"
|
|
5
|
+
|
|
6
|
+
export interface BadgeProps {
|
|
7
|
+
// A string/number renders as the themed pill label; anything else is rendered
|
|
8
|
+
// as-is (an icon, a dot, ...).
|
|
9
|
+
children?: any
|
|
10
|
+
layout?: LayoutProps
|
|
11
|
+
style?: StyleProps
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// A rounded radius large enough to fully pill any typical badge height; the
|
|
15
|
+
// renderer clamps it to half the box, so both ends stay round.
|
|
16
|
+
const RADIUS = 999
|
|
17
|
+
|
|
18
|
+
// A small rounded pill for counts, labels, and status. Accent fill with
|
|
19
|
+
// onPrimary text by default; override the fill via style.backgroundColor and the
|
|
20
|
+
// label color via style.color.
|
|
21
|
+
export function Badge(props: BadgeProps) {
|
|
22
|
+
let bg = () => props.style?.backgroundColor ?? theme.color.primary
|
|
23
|
+
let fg = () => props.style?.color ?? theme.color.onPrimary
|
|
24
|
+
let radius = () => props.style?.borderRadius ?? RADIUS
|
|
25
|
+
let isText = () => typeof props.children === "string" || typeof props.children === "number"
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<view
|
|
29
|
+
flexDirection="row"
|
|
30
|
+
alignItems="center"
|
|
31
|
+
justifyContent="center"
|
|
32
|
+
paddingLeft={8}
|
|
33
|
+
paddingRight={8}
|
|
34
|
+
paddingTop={2}
|
|
35
|
+
paddingBottom={2}
|
|
36
|
+
{...props.layout}
|
|
37
|
+
x={props.style?.x}
|
|
38
|
+
y={props.style?.y}
|
|
39
|
+
scale={props.style?.scale}
|
|
40
|
+
rotate={props.style?.rotate}
|
|
41
|
+
>
|
|
42
|
+
<d-rect color={bg()} radius={radius()} />
|
|
43
|
+
<Show when={isText()} fallback={props.children}>
|
|
44
|
+
<text color={fg()} fontSize={12} fontWeight={600}>
|
|
45
|
+
{props.children}
|
|
46
|
+
</text>
|
|
47
|
+
</Show>
|
|
48
|
+
</view>
|
|
49
|
+
)
|
|
50
|
+
}
|
package/src/button.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Show } from "@solidrt/core"
|
|
2
2
|
import { Pressable, type PressState } from "./pressable"
|
|
3
3
|
import { theme } from "./theme"
|
|
4
|
+
import { policy, densityScale } from "./policy"
|
|
4
5
|
import type { LayoutProps } from "@solidrt/core"
|
|
5
6
|
import type { StyleProps } from "./types"
|
|
6
7
|
|
|
@@ -15,11 +16,19 @@ export interface ButtonProps {
|
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
// Themed convenience over Pressable: a padded, centered, accent-colored box with
|
|
18
|
-
// a label. Press feedback is a slight scale,
|
|
19
|
-
//
|
|
20
|
-
// padding/sizing via
|
|
19
|
+
// a label. Press feedback is a slight scale, hover feedback a tint (non-touch
|
|
20
|
+
// interaction policies only), both driven through Pressable's reactive style so
|
|
21
|
+
// no nodes are recreated. Override the box via style and the padding/sizing via
|
|
22
|
+
// layout. A caller-set backgroundColor disables the hover tint: we cannot know
|
|
23
|
+
// its hover variant.
|
|
21
24
|
export function Button(props: ButtonProps) {
|
|
22
|
-
let bg = () =>
|
|
25
|
+
let bg = (s: PressState) =>
|
|
26
|
+
props.style?.backgroundColor ??
|
|
27
|
+
(props.disabled
|
|
28
|
+
? theme.color.surface
|
|
29
|
+
: s.hovered && policy.interaction !== "touch"
|
|
30
|
+
? theme.color.primaryHover
|
|
31
|
+
: theme.color.primary)
|
|
23
32
|
let radius = () => props.style?.borderRadius ?? theme.radius.sm
|
|
24
33
|
let label = () => (props.disabled ? theme.color.textMuted : theme.color.onPrimary)
|
|
25
34
|
let isText = () => typeof props.children === "string" || typeof props.children === "number"
|
|
@@ -32,20 +41,20 @@ export function Button(props: ButtonProps) {
|
|
|
32
41
|
flexDirection: "row",
|
|
33
42
|
alignItems: "center",
|
|
34
43
|
justifyContent: "center",
|
|
35
|
-
paddingTop: theme.spacing.sm,
|
|
36
|
-
paddingBottom: theme.spacing.sm,
|
|
37
|
-
paddingLeft: theme.spacing.md,
|
|
38
|
-
paddingRight: theme.spacing.md,
|
|
44
|
+
paddingTop: Math.round(theme.spacing.sm * densityScale()),
|
|
45
|
+
paddingBottom: Math.round(theme.spacing.sm * densityScale()),
|
|
46
|
+
paddingLeft: Math.round(theme.spacing.md * densityScale()),
|
|
47
|
+
paddingRight: Math.round(theme.spacing.md * densityScale()),
|
|
39
48
|
...props.layout,
|
|
40
49
|
}}
|
|
41
50
|
style={(s: PressState) => ({
|
|
42
51
|
...props.style,
|
|
43
|
-
backgroundColor: bg(),
|
|
52
|
+
backgroundColor: bg(s),
|
|
44
53
|
borderRadius: radius(),
|
|
45
54
|
// Always a number: a scale that flips from a number back to undefined
|
|
46
55
|
// hits the transform decoder, which rejects null. Multiply so a
|
|
47
56
|
// caller-set scale is preserved under the press feedback.
|
|
48
|
-
scale: (props.style?.scale ?? 1) * (s.pressed ? 0.97 : 1),
|
|
57
|
+
scale: (props.style?.scale ?? 1) * (s.pressed && policy.motion !== "none" ? 0.97 : 1),
|
|
49
58
|
})}
|
|
50
59
|
>
|
|
51
60
|
<Show when={isText()} fallback={props.children}>
|
package/src/card.tsx
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Show } from "@solidrt/core"
|
|
2
|
+
import type { LayoutProps } from "@solidrt/core"
|
|
3
|
+
import { theme } from "./theme"
|
|
4
|
+
import type { StyleProps } from "./types"
|
|
5
|
+
|
|
6
|
+
export interface CardProps {
|
|
7
|
+
children?: any
|
|
8
|
+
// Optional heading rendered above the content.
|
|
9
|
+
title?: string
|
|
10
|
+
ref?: (node: { id: number }) => void
|
|
11
|
+
layout?: LayoutProps
|
|
12
|
+
style?: StyleProps
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const RADIUS = 12
|
|
16
|
+
|
|
17
|
+
// A themed surface container: a padded column box with a subtle border and
|
|
18
|
+
// rounded corners, reading its colors from the theme so it recolors live.
|
|
19
|
+
// Override any paint via style, spacing/sizing via layout.
|
|
20
|
+
export function Card(props: CardProps) {
|
|
21
|
+
let bg = () => props.style?.backgroundColor ?? theme.color.surface
|
|
22
|
+
let border = () => props.style?.borderColor ?? theme.color.border
|
|
23
|
+
let width = () => props.style?.borderWidth ?? theme.borderWidth.sm
|
|
24
|
+
let radius = () => props.style?.borderRadius ?? RADIUS
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<view
|
|
28
|
+
ref={props.ref}
|
|
29
|
+
flexDirection="column"
|
|
30
|
+
gap={16}
|
|
31
|
+
padding={20}
|
|
32
|
+
{...props.layout}
|
|
33
|
+
x={props.style?.x}
|
|
34
|
+
y={props.style?.y}
|
|
35
|
+
scale={props.style?.scale}
|
|
36
|
+
rotate={props.style?.rotate}
|
|
37
|
+
>
|
|
38
|
+
<d-rect color={bg()} radius={radius()} />
|
|
39
|
+
<Show when={props.title != null}>
|
|
40
|
+
<text color={theme.color.text} fontSize={18} fontWeight={700}>
|
|
41
|
+
{props.title}
|
|
42
|
+
</text>
|
|
43
|
+
</Show>
|
|
44
|
+
{props.children}
|
|
45
|
+
<d-rect drawStyle="stroke" color={border()} strokeWidth={width()} radius={radius()} />
|
|
46
|
+
</view>
|
|
47
|
+
)
|
|
48
|
+
}
|
package/src/checkbox.tsx
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { createSignal, Show } from "@solidrt/core"
|
|
2
|
+
import type { LayoutProps } from "@solidrt/core"
|
|
3
|
+
import { Pressable } from "./pressable"
|
|
4
|
+
import { theme } from "./theme"
|
|
5
|
+
import { densityScale } from "./policy"
|
|
6
|
+
import type { StyleProps } from "./types"
|
|
7
|
+
|
|
8
|
+
export interface CheckboxProps {
|
|
9
|
+
// Controlled checked state. If omitted, the checkbox is uncontrolled.
|
|
10
|
+
checked?: boolean
|
|
11
|
+
defaultChecked?: boolean
|
|
12
|
+
onChange?: (checked: boolean) => void
|
|
13
|
+
disabled?: boolean
|
|
14
|
+
layout?: LayoutProps
|
|
15
|
+
style?: StyleProps
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const SIZE = 20
|
|
19
|
+
|
|
20
|
+
// A checkbox. When checked, fills with primary and draws a checkmark; otherwise
|
|
21
|
+
// shows an empty bordered box. Controlled via checked/onChange, or uncontrolled
|
|
22
|
+
// via defaultChecked. Built on Pressable.
|
|
23
|
+
export function Checkbox(props: CheckboxProps) {
|
|
24
|
+
let [internal, setInternal] = createSignal(props.defaultChecked ?? false)
|
|
25
|
+
let checked = () => (props.checked !== undefined ? props.checked : internal())
|
|
26
|
+
|
|
27
|
+
let toggle = () => {
|
|
28
|
+
let next = !checked()
|
|
29
|
+
if (props.checked === undefined) setInternal(next)
|
|
30
|
+
props.onChange?.(next)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let size = () => Math.round(SIZE * densityScale())
|
|
34
|
+
// The checkmark in box-relative fractions, so it scales with the density.
|
|
35
|
+
let check = () => {
|
|
36
|
+
let s = size()
|
|
37
|
+
return `M ${0.25 * s} ${0.5 * s} L ${0.45 * s} ${0.7 * s} L ${0.75 * s} ${0.3 * s}`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Pressable
|
|
42
|
+
onPress={toggle}
|
|
43
|
+
disabled={props.disabled}
|
|
44
|
+
layout={{ width: size(), height: size(), ...props.layout }}
|
|
45
|
+
style={{
|
|
46
|
+
backgroundColor: checked() ? theme.color.primary : theme.color.surface,
|
|
47
|
+
borderColor: theme.color.border,
|
|
48
|
+
borderWidth: theme.borderWidth.sm,
|
|
49
|
+
borderRadius: theme.radius.sm,
|
|
50
|
+
...props.style,
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
<Show when={checked()}>
|
|
54
|
+
<d-path
|
|
55
|
+
d={check()}
|
|
56
|
+
drawStyle="stroke"
|
|
57
|
+
color={theme.color.onPrimary}
|
|
58
|
+
strokeWidth={2}
|
|
59
|
+
strokeCap="round"
|
|
60
|
+
strokeJoin="round"
|
|
61
|
+
/>
|
|
62
|
+
</Show>
|
|
63
|
+
</Pressable>
|
|
64
|
+
)
|
|
65
|
+
}
|