@solidrt/components 0.0.52 → 0.0.54

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidrt/components",
3
- "version": "0.0.52",
3
+ "version": "0.0.54",
4
4
  "license": "MIT",
5
5
  "funding": "https://github.com/sponsors/wellawaretech",
6
6
  "author": "Antoine van Wel",
@@ -14,14 +14,13 @@
14
14
  "src/",
15
15
  "docs/",
16
16
  "examples/",
17
- "demos/",
18
17
  "AGENTS.md"
19
18
  ],
20
19
  "dependencies": {
21
20
  "qrcode-generator": "^2.0.4"
22
21
  },
23
22
  "peerDependencies": {
24
- "@solidjs/signals": "2.0.0-rc.1",
25
- "@solidrt/core": "0.0.52"
23
+ "@solidjs/signals": "2.0.0-rc.3",
24
+ "@solidrt/core": "0.0.54"
26
25
  }
27
26
  }
package/demos/README.md DELETED
@@ -1,24 +0,0 @@
1
- # @solidrt/components demos
2
-
3
- Demos to run, not snippets to copy - `examples/` next door is the one-feature
4
- set. List them with `bunx srt demo` and start one by its number.
5
-
6
- This folder is ONE project: the demos share this package.json, this
7
- tsconfig.json and this `assets/` folder, and `srt demo` serves the project
8
- with the chosen `src/*.tsx` as its entry.
9
-
10
- Two rules hold it together:
11
-
12
- - One file per demo. A demo is a single `src/<name>.tsx` so it can be read
13
- top to bottom and copied out in one piece.
14
- - One package per demo. A demo here uses @solidrt/components and
15
- @solidrt/core, and nothing else: it shows what this package is for, on its
16
- own.
17
-
18
- ## Demos
19
- - `gallery.tsx` - every component (the rich text editor excepted, until it is
20
- finished), grouped: a SplitView whose list pane picks a group and whose
21
- detail pane shows its cards (two-pane wide, one pane at a time with a back
22
- arrow narrow). The sun/moon icon recolors everything live; the Environment
23
- group exposes the environment -> capabilities -> policies cascade as
24
- overrides to watch each control adapt.
Binary file
@@ -1,23 +0,0 @@
1
- <svg width="100" height="100" viewBox="-6 -6 112 112" xmlns="http://www.w3.org/2000/svg">
2
- <defs>
3
- <linearGradient id="logo-a" x1="0" y1="0" x2="1" y2="1">
4
- <stop offset="0" stop-color="#3f5494"/>
5
- <stop offset="1" stop-color="#162b6c"/>
6
- </linearGradient>
7
- <linearGradient id="logo-b" x1="0" y1="0" x2="1" y2="1">
8
- <stop offset="0" stop-color="#547ebf"/>
9
- <stop offset="1" stop-color="#2b5696"/>
10
- </linearGradient>
11
- <linearGradient id="logo-c" x1="0" y1="0" x2="1" y2="1">
12
- <stop offset="0" stop-color="#7ea9ea"/>
13
- <stop offset="1" stop-color="#5681c1"/>
14
- </linearGradient>
15
- </defs>
16
- <path d="M50.000 50.000 L28.330 50.000 C28.330 48.810 27.695 47.711 26.665 47.116 C25.635 46.521 24.365 46.521 23.335 47.116 C22.305 47.711 21.670 48.810 21.670 50.000 L0.000 50.000 L50.000 0.000 L50.000 9.170 C48.810 9.170 47.711 9.805 47.116 10.835 C46.521 11.865 46.521 13.135 47.116 14.165 C47.711 15.195 48.810 15.830 50.000 15.830 L50.000 25.000 L50.000 34.170 C48.810 34.170 47.711 34.805 47.116 35.835 C46.521 36.865 46.521 38.135 47.116 39.165 C47.711 40.195 48.810 40.830 50.000 40.830 L50.000 50.000 Z" fill="url(#logo-a)"/>
17
- <path d="M50.000 50.000 L50.000 59.170 C48.810 59.170 47.711 59.805 47.116 60.835 C46.521 61.865 46.521 63.135 47.116 64.165 C47.711 65.195 48.810 65.830 50.000 65.830 L50.000 75.000 L50.000 84.170 C48.810 84.170 47.711 84.805 47.116 85.835 C46.521 86.865 46.521 88.135 47.116 89.165 C47.711 90.195 48.810 90.830 50.000 90.830 L50.000 100.000 L0.000 50.000 L21.670 50.000 C21.670 48.810 22.305 47.711 23.335 47.116 C24.365 46.521 25.635 46.521 26.665 47.116 C27.695 47.711 28.330 48.810 28.330 50.000 L50.000 50.000 Z" fill="url(#logo-b)"/>
18
- <path d="M50.000 25.000 L50.000 15.830 C48.810 15.830 47.711 15.195 47.116 14.165 C46.521 13.135 46.521 11.865 47.116 10.835 C47.711 9.805 48.810 9.170 50.000 9.170 L50.000 0.000 L75.000 25.000 L65.830 25.000 C65.830 26.190 65.195 27.289 64.165 27.884 C63.135 28.479 61.865 28.479 60.835 27.884 C59.805 27.289 59.170 26.190 59.170 25.000 L50.000 25.000 Z" fill="url(#logo-c)"/>
19
- <path d="M50.000 25.000 L59.170 25.000 C59.170 26.190 59.805 27.289 60.835 27.884 C61.865 28.479 63.135 28.479 64.165 27.884 C65.195 27.289 65.830 26.190 65.830 25.000 L75.000 25.000 L75.000 34.170 C73.810 34.170 72.711 34.805 72.116 35.835 C71.521 36.865 71.521 38.135 72.116 39.165 C72.711 40.195 73.810 40.830 75.000 40.830 L75.000 50.000 L65.830 50.000 C65.830 48.810 65.195 47.711 64.165 47.116 C63.135 46.521 61.865 46.521 60.835 47.116 C59.805 47.711 59.170 48.810 59.170 50.000 L50.000 50.000 L50.000 40.830 C48.810 40.830 47.711 40.195 47.116 39.165 C46.521 38.135 46.521 36.865 47.116 35.835 C47.711 34.805 48.810 34.170 50.000 34.170 L50.000 25.000 Z" fill="url(#logo-b)"/>
20
- <path d="M50.000 50.000 L59.170 50.000 C59.170 48.810 59.805 47.711 60.835 47.116 C61.865 46.521 63.135 46.521 64.165 47.116 C65.195 47.711 65.830 48.810 65.830 50.000 L75.000 50.000 L64.855 60.145 C64.013 59.304 62.787 58.976 61.638 59.283 C60.489 59.591 59.591 60.489 59.283 61.638 C58.976 62.787 59.304 64.013 60.145 64.855 L50.000 75.000 L50.000 65.830 C48.810 65.830 47.711 65.195 47.116 64.165 C46.521 63.135 46.521 61.865 47.116 60.835 C47.711 59.805 48.810 59.170 50.000 59.170 L50.000 50.000 Z" fill="url(#logo-c)"/>
21
- <path d="M75.000 50.000 L75.000 59.170 C73.810 59.170 72.711 59.805 72.116 60.835 C71.521 61.865 71.521 63.135 72.116 64.165 C72.711 65.195 73.810 65.830 75.000 65.830 L75.000 75.000 L50.000 100.000 L50.000 90.830 C48.810 90.830 47.711 90.195 47.116 89.165 C46.521 88.135 46.521 86.865 47.116 85.835 C47.711 84.805 48.810 84.170 50.000 84.170 L50.000 75.000 L60.145 64.855 C59.304 64.013 58.976 62.787 59.283 61.638 C59.591 60.489 60.489 59.591 61.638 59.283 C62.787 58.976 64.013 59.304 64.855 60.145 L75.000 50.000 Z" fill="url(#logo-a)"/>
22
- <path d="M100.000 50.000 L75.000 75.000 L75.000 65.830 C73.810 65.830 72.711 65.195 72.116 64.165 C71.521 63.135 71.521 61.865 72.116 60.835 C72.711 59.805 73.810 59.170 75.000 59.170 L75.000 50.000 L75.000 40.830 C73.810 40.830 72.711 40.195 72.116 39.165 C71.521 38.135 71.521 36.865 72.116 35.835 C72.711 34.805 73.810 34.170 75.000 34.170 L75.000 25.000 L100.000 50.000 Z" fill="url(#logo-c)"/>
23
- </svg>
@@ -1,9 +0,0 @@
1
- {
2
- "name": "solidrt-components-demos",
3
- "type": "module",
4
- "private": true,
5
- "solidrt": {
6
- "appId": "dev.solidrt.demos.components",
7
- "displayName": "Gallery"
8
- }
9
- }
@@ -1,866 +0,0 @@
1
- import {
2
- render,
3
- createSignal,
4
- createEffect,
5
- untrack,
6
- env,
7
- capabilities,
8
- For,
9
- Show,
10
- pct,
11
- } from "@solidrt/core"
12
- import {
13
- policy,
14
- setPolicy,
15
- type DensityPolicy,
16
- type MotionPolicy,
17
- Window,
18
- View,
19
- Text,
20
- Image,
21
- Button,
22
- TextInput,
23
- Switch,
24
- Checkbox,
25
- RadioGroup,
26
- Radio,
27
- Slider,
28
- QrCode,
29
- Card,
30
- Divider,
31
- Badge,
32
- Spinner,
33
- ProgressBar,
34
- Tooltip,
35
- Select,
36
- ContextMenu,
37
- Icon,
38
- ScrollView,
39
- SplitView,
40
- Item,
41
- Pressable,
42
- type PressState,
43
- Modal,
44
- SegmentedControl,
45
- Field,
46
- Density,
47
- theme,
48
- space,
49
- typeWeight,
50
- setTheme,
51
- darkTheme,
52
- lightTheme,
53
- SafeArea,
54
- } from "@solidrt/components"
55
-
56
- import icon from "../assets/icon.png" with { type: "binary" }
57
- // The puzzle mark, the demos project's app icon: a multi-color SVG keeps its
58
- // own fills through Icon.
59
- import mark from "../assets/icon.svg" with { type: "text" }
60
-
61
- // A gallery of @solidrt/components, itself built from them: a SplitView whose
62
- // list pane picks a group of components and whose detail pane shows that
63
- // group's cards. Two-pane on a wide window, one pane at a time (with a Back
64
- // button) on a narrow one - resize to watch policy.layout flip the shell.
65
- // The whole thing recolors live when the theme switches: every component
66
- // reads theme.* reactively, so the "Dark theme" switch reflows the palette
67
- // with no remount. The "Environment" group shows the environment ->
68
- // capabilities -> policies cascade and lets you force density/motion
69
- // overrides to watch every control adapt.
70
-
71
- // Lucide icons are SVG documents (strings). In a real app you import them as
72
- // assets (`import House from "lucide-static/icons/house.svg"`) or from a string
73
- // export; we inline a few here so the example stays self-contained. Each strokes
74
- // with currentColor, so Icon recolors them from the theme.
75
- const LUCIDE = (body: string) =>
76
- `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"` +
77
- ` stroke="currentColor" stroke-width="2" stroke-linecap="round"` +
78
- ` stroke-linejoin="round">${body}</svg>`
79
- const HOUSE = LUCIDE(
80
- `<path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"/>` +
81
- `<path d="M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>`,
82
- )
83
- const HEART = LUCIDE(
84
- `<path d="M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5"/>`,
85
- )
86
- const STAR = LUCIDE(
87
- `<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"/>`,
88
- )
89
- const SETTINGS = LUCIDE(
90
- `<path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915"/>` +
91
- `<circle cx="12" cy="12" r="3"/>`,
92
- )
93
- const SUN = LUCIDE(
94
- `<circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/>` +
95
- `<path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/>` +
96
- `<path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/>`,
97
- )
98
- const MOON = LUCIDE(`<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>`)
99
- const ARROW_LEFT = LUCIDE(`<path d="m12 19-7-7 7-7"/><path d="M19 12H5"/>`)
100
- const BELL = LUCIDE(
101
- `<path d="M10.268 21a2 2 0 0 0 3.464 0"/>` +
102
- `<path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/>`,
103
- )
104
-
105
- const TAP_TARGET = 44
106
- // Breathing room between the list's cards and the scroller's clip edge, so a
107
- // focus ring on the outermost card is not cut off.
108
- const LIST_GUTTER = 2
109
- // Reading widths, as the launcher sets them: single-pane runs up to the
110
- // expanded breakpoint (840), so past these the column is centered rather than
111
- // stretched. The detail column is wider than the list's because it holds
112
- // label-and-value rows and controls, not prose.
113
- const COLUMN_MAX_WIDTH = 440
114
- const DETAIL_MAX_WIDTH = 640
115
-
116
- // An icon-only press target, as the launcher's back arrow: a square tap
117
- // target with a hover tint and no fill, no label.
118
- function IconButton(props: { src: string; onPress: () => void }) {
119
- return (
120
- <Pressable
121
- focusable
122
- onPress={props.onPress}
123
- layout={{
124
- width: TAP_TARGET,
125
- height: TAP_TARGET,
126
- alignItems: "center",
127
- justifyContent: "center",
128
- }}
129
- style={(s: PressState) => ({
130
- backgroundColor: s.hovered ? theme.color.overlayHover : "transparent",
131
- borderRadius: theme.radius.md,
132
- })}
133
- >
134
- <Icon src={props.src} size={22} />
135
- </Pressable>
136
- )
137
- }
138
-
139
- // A group in the list, shaped like the launcher's app rows: a pressable Card
140
- // with a title and a muted line, filled while selected or hovered. Only
141
- // two-pane shows the selection - single-pane shows the list without the
142
- // detail, and a highlighted row with nothing selected on screen reads as a
143
- // stray highlight.
144
- function GroupCard(props: {
145
- label: string
146
- description: string
147
- active: boolean
148
- onPress: () => void
149
- }) {
150
- return (
151
- <Pressable focusable onPress={props.onPress}>
152
- {(s: PressState) => (
153
- <Card
154
- layout={{ flexDirection: "row", alignItems: "center", gap: space("lg") }}
155
- style={{
156
- backgroundColor:
157
- props.active || s.hovered ? theme.color.surfaceAlt : theme.color.surface,
158
- }}
159
- >
160
- <View layout={{ flexDirection: "column", flexGrow: 1, gap: 2 }}>
161
- <Text variant="title">{props.label}</Text>
162
- <Text variant="body" muted>
163
- {props.description}
164
- </Text>
165
- </View>
166
- </Card>
167
- )}
168
- </Pressable>
169
- )
170
- }
171
-
172
- function Row(props: { label: string; children?: any }) {
173
- return (
174
- <View
175
- layout={{
176
- flexDirection: "row",
177
- alignItems: "center",
178
- justifyContent: "space-between",
179
- gap: space("lg"),
180
- }}
181
- >
182
- <Text muted>{props.label}</Text>
183
- {props.children}
184
- </View>
185
- )
186
- }
187
-
188
- function Value(props: { children?: any }) {
189
- return <Text>{props.children}</Text>
190
- }
191
-
192
- // Joins the names whose flag is true, e.g. list(["mouse", env.mouseSeen], ...).
193
- function list(...entries: [string, boolean][]) {
194
- let names = entries.filter(([, on]) => on).map(([name]) => name)
195
- return names.length > 0 ? names.join(", ") : "none yet"
196
- }
197
-
198
- function App() {
199
- let [dark, setDark] = createSignal(true)
200
- let [name, setName] = createSignal("")
201
- let [notify, setNotify] = createSignal(true)
202
- let [agree, setAgree] = createSignal(false)
203
- let [plan, setPlan] = createSignal<unknown>("pro")
204
- let [volume, setVolume] = createSignal(40)
205
- let [fruit, setFruit] = createSignal<unknown>(undefined)
206
- let [notes, setNotes] = createSignal("")
207
- let [fixedNotes, setFixedNotes] = createSignal("")
208
- let [range, setRange] = createSignal<unknown>("week")
209
- // Starts false: a Modal is a portal, and portals cannot mount during the
210
- // initial render.
211
- let [modalOpen, setModalOpen] = createSignal(false)
212
- let [rowDensity, setRowDensity] = createSignal<unknown>("comfortable")
213
- let [currentIssue, setCurrentIssue] = createSignal("SRT-12")
214
- let issues = [
215
- { id: "SRT-12", title: "Tooltip lingers after the anchor unmounts", assignee: "Antoine" },
216
- { id: "SRT-15", title: "Select sheet ignores safe area on Android", assignee: "unassigned" },
217
- { id: "SRT-19", title: "Slider thumb misses the first pixel", assignee: "Antoine" },
218
- ]
219
-
220
- // The QR code follows the name field debounced, not per keystroke: each data
221
- // change regenerates the whole module grid, which is too heavy to run at
222
- // typing frequency.
223
- // untrack: seed with the current name once; the debounced effect below owns
224
- // keeping it in sync, and a bare name() read here would warn (strict mode
225
- // flags top-level reactive reads in component bodies as one-shot).
226
- let [qrData, setQrData] = createSignal(untrack(name))
227
- createEffect(
228
- () => name(),
229
- (v) => {
230
- let timer = setTimeout(() => setQrData(v), 250)
231
- return () => clearTimeout(timer)
232
- },
233
- )
234
-
235
- // Follow the OS dark/light preference until the user toggles manually; the
236
- // manual toggle then owns the choice for the rest of the session.
237
- let userToggledTheme = false
238
- let toggleTheme = () => {
239
- userToggledTheme = true
240
- let on = !dark()
241
- setDark(on)
242
- setTheme(on ? darkTheme : lightTheme)
243
- }
244
- createEffect(
245
- () => env.systemTheme,
246
- (t) => {
247
- if (userToggledTheme || t === "unknown") return
248
- let isDark = t === "dark"
249
- setDark(isDark)
250
- setTheme(isDark ? darkTheme : lightTheme)
251
- },
252
- )
253
-
254
- // Policy override choices; "auto" hands the policy back to the resolver.
255
- let [densityChoice, setDensityChoice] = createSignal<unknown>("auto")
256
- let [motionChoice, setMotionChoice] = createSignal<unknown>("auto")
257
- let chooseDensity = (v: unknown) => {
258
- setDensityChoice(v)
259
- setPolicy({ density: v === "auto" ? undefined : (v as DensityPolicy) })
260
- }
261
- let chooseMotion = (v: unknown) => {
262
- setMotionChoice(v)
263
- setPolicy({ motion: v === "auto" ? undefined : (v as MotionPolicy) })
264
- }
265
- let [textScaleChoice, setTextScaleChoice] = createSignal<unknown>("auto")
266
- let chooseTextScale = (v: unknown) => {
267
- setTextScaleChoice(v)
268
- setPolicy({ textScale: v === "auto" ? undefined : (v as number) })
269
- }
270
- let [weightDeltaChoice, setWeightDeltaChoice] = createSignal<unknown>("auto")
271
- let chooseWeightDelta = (v: unknown) => {
272
- setWeightDeltaChoice(v)
273
- setPolicy({ textWeightDelta: v === "auto" ? undefined : (v as number) })
274
- }
275
-
276
- // The gallery's groups: one list row each, one set of cards each. The
277
- // cards read the signals above, so a group's content is built fresh each
278
- // time it is shown (the detail reads content() once per selection) and
279
- // picks up the current state.
280
- let sections = [
281
- {
282
- value: "environment",
283
- label: "Environment",
284
- description: "Facts, capabilities and policies",
285
- content: () => (
286
- <>
287
- <Card title="Environment">
288
- <Row label="Window">
289
- <Value>
290
- {`${env.windowSize.width} x ${env.windowSize.height} @ ${env.displayScale}x`}
291
- </Value>
292
- </Row>
293
- <Row label="System">
294
- <Value>{`${env.systemTheme} theme, ${env.orientation}`}</Value>
295
- </Row>
296
- <Row label="Devices">
297
- <Value>
298
- {env.inputDevices
299
- ? list(
300
- ["mouse", env.inputDevices.mouse],
301
- ["touch", env.inputDevices.touch],
302
- ["keyboard", env.inputDevices.keyboard],
303
- )
304
- : "not reported"}
305
- </Value>
306
- </Row>
307
- <Row label="Inputs seen">
308
- <Value>
309
- {list(
310
- ["mouse", env.mouseSeen],
311
- ["touch", env.touchSeen],
312
- ["keyboard", env.keyboardSeen],
313
- )}
314
- </Value>
315
- </Row>
316
- <Row label="Capabilities">
317
- <Value>
318
- {list(
319
- ["hover", capabilities.hover],
320
- ["touch", capabilities.touch],
321
- ["keyboard nav", capabilities.keyboardNav],
322
- )}
323
- </Value>
324
- </Row>
325
- <Row label="Size class">
326
- <Value>{capabilities.windowSizeClass}</Value>
327
- </Row>
328
- </Card>
329
- <Card title="Policies">
330
- <Row label="Interaction policy">
331
- <Value>{policy.interaction}</Value>
332
- </Row>
333
- <Row label="Focus ring">
334
- <Value>{policy.focusRing ? "visible" : "hidden"}</Value>
335
- </Row>
336
- <Row label="App policies">
337
- <Value>{`${policy.navigation}, ${policy.layout}`}</Value>
338
- </Row>
339
- <Divider />
340
- <Row label="Density policy">
341
- <Value>{policy.density}</Value>
342
- </Row>
343
- <RadioGroup
344
- value={densityChoice()}
345
- onChange={chooseDensity}
346
- layout={{ flexDirection: "row", flexWrap: "wrap", gap: space("md") }}
347
- >
348
- <Radio value="auto">Auto</Radio>
349
- <Radio value="comfortable">Comfortable</Radio>
350
- <Radio value="compact">Compact</Radio>
351
- <Radio value="dense">Dense</Radio>
352
- </RadioGroup>
353
- <Divider />
354
- <Row label="Motion policy">
355
- <Value>{policy.motion}</Value>
356
- </Row>
357
- <RadioGroup
358
- value={motionChoice()}
359
- onChange={chooseMotion}
360
- layout={{ flexDirection: "row", flexWrap: "wrap", gap: space("md") }}
361
- >
362
- <Radio value="auto">Auto</Radio>
363
- <Radio value="normal">Normal</Radio>
364
- <Radio value="reduced">Reduced</Radio>
365
- <Radio value="none">None</Radio>
366
- </RadioGroup>
367
- <Divider />
368
- <Row label="Text scale">
369
- <Value>{`${policy.textScale}x`}</Value>
370
- </Row>
371
- <RadioGroup
372
- value={textScaleChoice()}
373
- onChange={chooseTextScale}
374
- layout={{ flexDirection: "row", flexWrap: "wrap", gap: space("md") }}
375
- >
376
- <Radio value="auto">Auto</Radio>
377
- <Radio value={0.9}>0.9</Radio>
378
- <Radio value={1.0}>1.0</Radio>
379
- <Radio value={1.1}>1.1</Radio>
380
- </RadioGroup>
381
- <Divider />
382
- <Row label="Text weight delta">
383
- <Value>
384
- {`base +${policy.textWeightDelta}, body ${typeWeight(
385
- theme.text.body.weight,
386
- theme.text.body.size * policy.textScale,
387
- )}, title ${typeWeight(theme.text.title.weight, theme.text.title.size * policy.textScale)}`}
388
- </Value>
389
- </Row>
390
- <RadioGroup
391
- value={weightDeltaChoice()}
392
- onChange={chooseWeightDelta}
393
- layout={{ flexDirection: "row", flexWrap: "wrap", gap: space("md") }}
394
- >
395
- <Radio value="auto">Auto</Radio>
396
- <Radio value={0}>0</Radio>
397
- <Radio value={100}>+100</Radio>
398
- <Radio value={200}>+200</Radio>
399
- </RadioGroup>
400
- </Card>
401
- </>
402
- ),
403
- },
404
- {
405
- value: "buttons",
406
- label: "Buttons and badges",
407
- description: "Buttons, badges, dividers, icons",
408
- content: () => (
409
- <>
410
- <Card title="Buttons">
411
- <View
412
- layout={{
413
- flexDirection: "row",
414
- flexWrap: "wrap",
415
- gap: space("md"),
416
- alignItems: "center",
417
- }}
418
- >
419
- <Button>Primary</Button>
420
- <Button variant="secondary">Secondary</Button>
421
- <Button variant="ghost">Ghost</Button>
422
- <Button variant="danger">Danger</Button>
423
- <Button disabled>Disabled</Button>
424
- </View>
425
- </Card>
426
- <Card title="Badges and divider">
427
- <Row label="Status">
428
- <View layout={{ flexDirection: "row", gap: space("md"), alignItems: "center" }}>
429
- <Badge>New</Badge>
430
- <Badge variant="neutral">3</Badge>
431
- <Badge variant="danger">Error</Badge>
432
- </View>
433
- </Row>
434
- <Divider />
435
- <Text muted>A divider separates content within a card.</Text>
436
- </Card>
437
- <Card title="Pressable">
438
- <Text muted>
439
- The primitive under every button: style and children can read the live press state.
440
- </Text>
441
- <Pressable
442
- onPress={() => console.log("pressed")}
443
- layout={{ padding: space("lg"), alignItems: "center" }}
444
- style={(s: PressState) => ({
445
- backgroundColor: s.pressed
446
- ? theme.color.primary
447
- : s.hovered
448
- ? theme.color.surfaceAlt
449
- : theme.color.surface,
450
- borderRadius: theme.radius.md,
451
- })}
452
- >
453
- {(s: PressState) => (
454
- <Text color={s.pressed ? "onPrimary" : "text"}>
455
- {s.pressed ? "Pressed" : s.hovered ? "Hovered" : "Idle"}
456
- </Text>
457
- )}
458
- </Pressable>
459
- </Card>
460
- <Card title="Icons">
461
- <Text muted>
462
- Lucide SVGs parsed by the core parseSvg primitive into d-path draws. currentColor
463
- follows the theme; the last two are recolored explicitly.
464
- </Text>
465
- <View layout={{ flexDirection: "row", gap: space("lg"), alignItems: "center" }}>
466
- <Icon src={HOUSE} />
467
- <Icon src={SETTINGS} />
468
- <Icon src={BELL} />
469
- <Icon src={HEART} color={theme.color.danger} />
470
- <Icon src={STAR} color={theme.color.primary} />
471
- </View>
472
- </Card>
473
- </>
474
- ),
475
- },
476
- {
477
- value: "selection",
478
- label: "Selection",
479
- description: "Toggles, radio, segments, select",
480
- content: () => (
481
- <>
482
- <Card title="Switch and checkbox">
483
- <Row label="Notifications">
484
- <Switch value={notify()} onChange={setNotify} />
485
- </Row>
486
- <Row label="I agree to the terms">
487
- <Checkbox checked={agree()} onChange={setAgree} />
488
- </Row>
489
- </Card>
490
- <Card title="Radio group">
491
- <RadioGroup value={plan()} onChange={setPlan}>
492
- <Radio value="free">Free</Radio>
493
- <Radio value="pro">Pro</Radio>
494
- <Radio value="team">Team</Radio>
495
- </RadioGroup>
496
- </Card>
497
- <Card title="Segmented control">
498
- <SegmentedControl
499
- options={[
500
- { value: "day", label: "Day" },
501
- { value: "week", label: "Week" },
502
- { value: "month", label: "Month" },
503
- ]}
504
- value={range()}
505
- onChange={setRange}
506
- />
507
- </Card>
508
- <Card title="Select">
509
- <Text muted>
510
- Forks on the interaction policy: desktop and hybrid anchor a dropdown, touch opens a
511
- bottom sheet.
512
- </Text>
513
- <Select
514
- value={fruit()}
515
- onChange={setFruit}
516
- placeholder="Pick a fruit"
517
- options={[
518
- { value: "apple", label: "Apple" },
519
- { value: "banana", label: "Banana" },
520
- { value: "cherry", label: "Cherry" },
521
- { value: "dragonfruit", label: "Dragonfruit" },
522
- ]}
523
- layout={{ width: 200 }}
524
- />
525
- </Card>
526
- </>
527
- ),
528
- },
529
- {
530
- value: "text",
531
- label: "Text",
532
- description: "A formatted paragraph and text fields",
533
- content: () => (
534
- <>
535
- <Card title="Paragraph">
536
- <Text>
537
- Lorem ipsum dolor sit amet,{" "}
538
- <span textDecoration="underline">consectetur adipiscing elit</span>, sed do eiusmod
539
- tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
540
- nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis
541
- aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat
542
- nulla pariatur. Excepteur sint occaecat cupidatat non proident, <span fontStyle="italic">sunt in culpa qui
543
- officia deserunt mollit anim id est laborum</span>.
544
- </Text>
545
- </Card>
546
- <Card title="Text input">
547
- <Field label="Single line" description="Enter submits.">
548
- <TextInput
549
- value={name()}
550
- onInput={setName}
551
- onSubmit={(v) => console.log("submitted", v)}
552
- placeholder="Your name"
553
- layout={{ width: pct(100) }}
554
- />
555
- </Field>
556
- <Field
557
- label="Multiline"
558
- description="A layout.height pins the box; it scrolls to the caret."
559
- >
560
- <TextInput
561
- multiline
562
- value={fixedNotes()}
563
- onInput={setFixedNotes}
564
- placeholder="Notes in a fixed box"
565
- layout={{ width: pct(100), height: 72 }}
566
- />
567
- </Field>
568
- <Field
569
- label="Multiline, auto-grow"
570
- description="Grows with the text up to 4 rows, then scrolls."
571
- >
572
- <TextInput
573
- multiline
574
- maxRows={4}
575
- value={notes()}
576
- onInput={setNotes}
577
- placeholder="Notes"
578
- layout={{ width: pct(100) }}
579
- />
580
- </Field>
581
- </Card>
582
- </>
583
- ),
584
- },
585
- {
586
- value: "slider",
587
- label: "Slider and progress",
588
- description: "Slider, progress bar, spinner",
589
- content: () => (
590
- <>
591
- <Card title="Slider">
592
- <Row label={`Volume: ${Math.round(volume())}`}>
593
- <Slider
594
- value={volume()}
595
- onChange={setVolume}
596
- min={0}
597
- max={100}
598
- step={1}
599
- layout={{ width: 180 }}
600
- />
601
- </Row>
602
- </Card>
603
- <Card title="Progress and spinner">
604
- <Row label="Loading">
605
- <Spinner />
606
- </Row>
607
- <Text muted>Determinate, tracking the volume slider:</Text>
608
- <ProgressBar value={volume() / 100} />
609
- <Text muted>Indeterminate:</Text>
610
- <ProgressBar />
611
- </Card>
612
- </>
613
- ),
614
- },
615
- {
616
- value: "overlays",
617
- label: "Overlays",
618
- description: "Context menu, tooltip and modal",
619
- content: () => (
620
- <>
621
- <Card title="Context menu">
622
- <Text muted>
623
- Right-click (mouse) or long-press (touch) the box below. Touch policy presents a
624
- bottom sheet, desktop and hybrid a menu at the pointer.
625
- </Text>
626
- <ContextMenu
627
- items={[
628
- { label: "Copy", onSelect: () => console.log("copy") },
629
- { label: "Paste", onSelect: () => console.log("paste") },
630
- { label: "Rename", disabled: true },
631
- { label: "Delete", onSelect: () => console.log("delete") },
632
- ]}
633
- >
634
- <View
635
- layout={{ padding: space("lg"), alignItems: "center" }}
636
- style={{
637
- backgroundColor: theme.color.surfaceAlt,
638
- borderRadius: theme.radius.sm,
639
- }}
640
- >
641
- <Text>Secondary actions live here</Text>
642
- </View>
643
- </ContextMenu>
644
- </Card>
645
- <Card title="Modal">
646
- <Text muted>
647
- A centered card over a scrim, portaled to the window root. The backdrop closes it.
648
- </Text>
649
- <Button onPress={() => setModalOpen(true)}>Open modal</Button>
650
- <Show when={modalOpen()}>
651
- <Modal onClose={() => setModalOpen(false)}>
652
- <Card title="Modal" layout={{ width: 360 }}>
653
- <Text muted>Focus navigation stays inside while this is open.</Text>
654
- <Button onPress={() => setModalOpen(false)}>Close</Button>
655
- </Card>
656
- </Modal>
657
- </Show>
658
- </Card>
659
- <Card title="Tooltip">
660
- <Text muted>
661
- Rest the mouse on a button. Shows under desktop and hybrid interaction policies, never
662
- under touch.
663
- </Text>
664
- <View layout={{ flexDirection: "row", gap: space("md") }}>
665
- <Tooltip content="Saves your changes">
666
- <Button>Hover me</Button>
667
- </Tooltip>
668
- <Tooltip content="Appears below the anchor" placement="bottom">
669
- <Button>Below</Button>
670
- </Tooltip>
671
- </View>
672
- </Card>
673
- </>
674
- ),
675
- },
676
- {
677
- value: "lists",
678
- label: "Lists",
679
- description: "Item rows under a Density region",
680
- content: () => (
681
- <>
682
- <Card title="Items">
683
- <Text muted>
684
- A column of Item rows is the list; the segments set the Density of the region around
685
- them.
686
- </Text>
687
- <SegmentedControl
688
- options={[
689
- { value: "comfortable", label: "Comfortable" },
690
- { value: "compact", label: "Compact" },
691
- { value: "dense", label: "Dense" },
692
- ]}
693
- value={rowDensity()}
694
- onChange={setRowDensity}
695
- />
696
- <Density value={rowDensity() as DensityPolicy}>
697
- <View layout={{ flexDirection: "column" }}>
698
- <For each={issues}>
699
- {(issue) => (
700
- <Item
701
- startContent={<Icon src={BELL} />}
702
- label={issue.title}
703
- description={issue.assignee}
704
- endContent={<Badge variant="neutral">{issue.id}</Badge>}
705
- selected={issue.id === currentIssue()}
706
- onPress={() => setCurrentIssue(issue.id)}
707
- />
708
- )}
709
- </For>
710
- <Item
711
- label="Disabled row"
712
- description="Takes no pointer events"
713
- disabled
714
- onPress={() => {}}
715
- />
716
- </View>
717
- </Density>
718
- </Card>
719
- </>
720
- ),
721
- },
722
- {
723
- value: "misc",
724
- label: "Misc",
725
- description: "QR code and image",
726
- content: () => (
727
- <>
728
- <Card title="QR code">
729
- <View layout={{ alignItems: "center" }}>
730
- <QrCode data="https://solidjs.com" />
731
- </View>
732
- </Card>
733
- <Card title="Image">
734
- <Text muted>
735
- Fetches, decodes, and uploads an image, then shows it as a GPU texture. Rounded via a
736
- clipping border radius.
737
- </Text>
738
- <View layout={{ alignItems: "center" }}>
739
- <Image src={icon} style={{ borderRadius: theme.radius.md }} />
740
- </View>
741
- </Card>
742
- </>
743
- ),
744
- },
745
- ]
746
-
747
- // Selection lives here, not in the panes: SplitView remounts them when the
748
- // layout policy crosses a breakpoint. showDetail only matters single-pane.
749
- // Nothing starts selected: a preselected row with no visible detail reads
750
- // as a stray highlight in single-pane.
751
- let [current, setCurrent] = createSignal<string | undefined>(undefined)
752
- let [showDetail, setShowDetail] = createSignal(false)
753
- let open = (value: string) => {
754
- setCurrent(value)
755
- setShowDetail(true)
756
- }
757
- let section = () => sections.find((s) => s.value === current())
758
- let twoPane = () => policy.layout === "twoPane"
759
-
760
- return (
761
- <Window
762
- title="Components gallery"
763
- layout={{ flexDirection: "column" }}
764
- style={{ backgroundColor: theme.color.background }}
765
- >
766
- <SafeArea>
767
- <SplitView
768
- layout={{ flex: 1 }}
769
- listWidth={380}
770
- showDetail={showDetail()}
771
- list={
772
- <View layout={{ flexGrow: 1, flexDirection: "column", alignItems: "center" }}>
773
- <View
774
- layout={{
775
- flexDirection: "column",
776
- flexGrow: 1,
777
- width: pct(100),
778
- maxWidth: twoPane() ? undefined : COLUMN_MAX_WIDTH,
779
- padding: space("xl"),
780
- gap: space("xl"),
781
- }}
782
- >
783
- <View
784
- layout={{
785
- flexDirection: "row",
786
- justifyContent: "space-between",
787
- alignItems: "center",
788
- }}
789
- >
790
- <View layout={{ flexDirection: "row", alignItems: "center", gap: space("md") }}>
791
- <Icon src={mark} size={40} />
792
- <Text variant="heading">Gallery</Text>
793
- </View>
794
- <Tooltip content={dark() ? "Light theme" : "Dark theme"}>
795
- <IconButton src={dark() ? SUN : MOON} onPress={toggleTheme} />
796
- </Tooltip>
797
- </View>
798
- <ScrollView layout={{ flexGrow: 1 }}>
799
- <View
800
- layout={{ flexDirection: "column", gap: space("md"), padding: LIST_GUTTER }}
801
- >
802
- <For each={sections}>
803
- {(s) => (
804
- <GroupCard
805
- label={s.label}
806
- description={s.description}
807
- active={twoPane() && s.value === current()}
808
- onPress={() => open(s.value)}
809
- />
810
- )}
811
- </For>
812
- </View>
813
- </ScrollView>
814
- </View>
815
- </View>
816
- }
817
- detail={
818
- <Show
819
- when={section()}
820
- fallback={
821
- <View layout={{ flexGrow: 1, alignItems: "center", justifyContent: "center" }}>
822
- <Text variant="heading" muted>
823
- Pick a group
824
- </Text>
825
- </View>
826
- }
827
- >
828
- {(s) => (
829
- <ScrollView layout={{ flexGrow: 1 }}>
830
- <View layout={{ flexGrow: 1, alignItems: twoPane() ? "flex-start" : "center" }}>
831
- <View
832
- layout={{
833
- flexDirection: "column",
834
- gap: space("lg"),
835
- width: pct(100),
836
- maxWidth: DETAIL_MAX_WIDTH,
837
- padding: space("xl"),
838
- }}
839
- >
840
- <View
841
- layout={{ flexDirection: "row", alignItems: "center", gap: space("md") }}
842
- >
843
- <Show when={!twoPane()}>
844
- <IconButton src={ARROW_LEFT} onPress={() => setShowDetail(false)} />
845
- </Show>
846
- <View layout={{ flexDirection: "column", flexGrow: 1, gap: 2 }}>
847
- <Text variant="heading">{s().label}</Text>
848
- <Text variant="body" muted>
849
- {s().description}
850
- </Text>
851
- </View>
852
- </View>
853
- {s().content()}
854
- </View>
855
- </View>
856
- </ScrollView>
857
- )}
858
- </Show>
859
- }
860
- />
861
- </SafeArea>
862
- </Window>
863
- )
864
- }
865
-
866
- render(() => <App />)
@@ -1,15 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ESNext",
4
- "lib": ["ESNext"],
5
- "jsx": "preserve",
6
- "jsxImportSource": "@solidrt/core",
7
- "moduleResolution": "bundler",
8
- "allowImportingTsExtensions": true,
9
- "noEmit": true,
10
- "strict": true,
11
- "skipLibCheck": true,
12
- "types": ["@solidrt/flux-types"]
13
- },
14
- "include": ["src"]
15
- }