@sarunyu/system-one 2.0.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -3
- package/llms.txt +258 -202
- package/package.json +1 -1
package/llms.txt
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# @sarunyu/system-one
|
|
2
2
|
|
|
3
|
-
React
|
|
4
|
-
|
|
5
|
-
---
|
|
3
|
+
React component library. Tailwind CSS v4 + CSS custom properties. 13 production-ready components. Built for AI-powered generation tools (v0, Lovable, Figma Make).
|
|
6
4
|
|
|
7
5
|
## Install
|
|
8
6
|
|
|
@@ -10,30 +8,32 @@ React UI component library for AI generation tools (Figma Make, v0, Lovable). Yo
|
|
|
10
8
|
npm install @sarunyu/system-one
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
## Setup
|
|
14
|
-
|
|
15
|
-
**You MUST import the stylesheet once at the app root. Without this, components render unstyled and layout will break.**
|
|
11
|
+
## Setup by Platform
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
### v0 / Next.js (App Router)
|
|
18
14
|
|
|
19
15
|
```tsx
|
|
20
|
-
//
|
|
16
|
+
// app/layout.tsx
|
|
21
17
|
import "@sarunyu/system-one/styles.css"
|
|
18
|
+
```
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
Components are pre-annotated with `"use client"`. Safe to import anywhere — Server Components, Client Components, pages.
|
|
21
|
+
|
|
22
|
+
### Next.js (Pages Router)
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
```tsx
|
|
25
|
+
// pages/_app.tsx
|
|
27
26
|
import "@sarunyu/system-one/styles.css"
|
|
28
27
|
```
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
### Figma Make / Lovable / Vite
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
// src/main.tsx
|
|
33
|
+
import "@sarunyu/system-one/styles.css"
|
|
34
|
+
```
|
|
35
35
|
|
|
36
|
-
## Import
|
|
36
|
+
## Import
|
|
37
37
|
|
|
38
38
|
```tsx
|
|
39
39
|
import {
|
|
@@ -49,129 +49,6 @@ import {
|
|
|
49
49
|
|
|
50
50
|
---
|
|
51
51
|
|
|
52
|
-
## Layout Anchors — read before composing any page
|
|
53
|
-
|
|
54
|
-
The library is unopinionated about page chrome, but AI tools produce ugly layouts without anchors. Use these defaults unless the user explicitly asks for something else:
|
|
55
|
-
|
|
56
|
-
### Container
|
|
57
|
-
|
|
58
|
-
- Page wrapper: `max-w-[1200px] mx-auto px-6` (desktop). On mobile (`<640px`) reduce to `px-4`.
|
|
59
|
-
- Form wrapper: `max-w-[480px] mx-auto`.
|
|
60
|
-
- Centered auth/onboarding card: `max-w-[400px] mx-auto`.
|
|
61
|
-
- Never let content stretch edge-to-edge on desktop.
|
|
62
|
-
|
|
63
|
-
### Vertical rhythm
|
|
64
|
-
|
|
65
|
-
- Page top padding: `py-10` to `py-16`.
|
|
66
|
-
- Between major sections: `space-y-12` or `mt-12`.
|
|
67
|
-
- Between related blocks inside a section: `space-y-6`.
|
|
68
|
-
- Between form fields: `space-y-4` (tight) or `space-y-5` (comfortable).
|
|
69
|
-
- Card grid gap: `gap-6`.
|
|
70
|
-
|
|
71
|
-
### Typography scale (library does NOT preset h1–h6 — set them yourself)
|
|
72
|
-
|
|
73
|
-
Use Tailwind utilities with these target sizes. Pair with `--foreground` for body, `--muted-foreground` for secondary text.
|
|
74
|
-
|
|
75
|
-
- Page title (h1): `text-3xl md:text-4xl font-semibold tracking-tight`
|
|
76
|
-
- Section title (h2): `text-2xl font-semibold`
|
|
77
|
-
- Subsection (h3): `text-lg font-semibold`
|
|
78
|
-
- Body: `text-base` (default) — never smaller than `text-sm` for reading content
|
|
79
|
-
- Caption / meta: `text-sm text-[var(--muted-foreground)]`
|
|
80
|
-
|
|
81
|
-
### Color rules (prevent theme drift)
|
|
82
|
-
|
|
83
|
-
Use design tokens for anything brand/semantic. **Never** use Tailwind's literal color utilities (`bg-blue-500`, `text-orange-400`, `border-gray-200`) for these roles.
|
|
84
|
-
|
|
85
|
-
- Brand / CTA → `var(--primary-action)` (hover `--primary-action-hover`, active `--primary-action-active`)
|
|
86
|
-
- Body text → `var(--foreground)` · muted → `var(--muted-foreground)`
|
|
87
|
-
- Page surface → `var(--background)` · card surface → `var(--card)` · subtle → `var(--muted)`
|
|
88
|
-
- Success → `var(--success)` · error/danger → `var(--destructive)` · warning → `var(--accent-orange)`
|
|
89
|
-
- Border → `var(--border)` · subtle divider → `var(--divider)`
|
|
90
|
-
|
|
91
|
-
Usage: `className="bg-[var(--card)] text-[var(--foreground)] border-[var(--border)]"` or `style={{ color: "var(--primary-action)" }}`.
|
|
92
|
-
|
|
93
|
-
### Radius
|
|
94
|
-
|
|
95
|
-
- Inputs, buttons, small elements: `rounded-md` (6px)
|
|
96
|
-
- Cards, panels: `rounded-xl` (12px) or `rounded-2xl` (16px)
|
|
97
|
-
- Pills / avatars: `rounded-full`
|
|
98
|
-
|
|
99
|
-
---
|
|
100
|
-
|
|
101
|
-
## Starter Page Template — copy, then adapt
|
|
102
|
-
|
|
103
|
-
This scaffold produces a well-proportioned page every time. Replace contents but keep the container/spacing shape.
|
|
104
|
-
|
|
105
|
-
```tsx
|
|
106
|
-
import { Button, Input, Tag, TabGroup } from "@sarunyu/system-one"
|
|
107
|
-
|
|
108
|
-
export default function Page() {
|
|
109
|
-
return (
|
|
110
|
-
<main className="min-h-screen bg-[var(--background)] text-[var(--foreground)]">
|
|
111
|
-
{/* Top bar — optional */}
|
|
112
|
-
<header className="border-b border-[var(--border)]">
|
|
113
|
-
<div className="max-w-[1200px] mx-auto px-6 h-16 flex items-center justify-between">
|
|
114
|
-
<span className="text-lg font-semibold">Brand</span>
|
|
115
|
-
<Button variant="primary" size="md">Sign in</Button>
|
|
116
|
-
</div>
|
|
117
|
-
</header>
|
|
118
|
-
|
|
119
|
-
{/* Page body */}
|
|
120
|
-
<div className="max-w-[1200px] mx-auto px-6 py-12 space-y-12">
|
|
121
|
-
{/* Page header */}
|
|
122
|
-
<section className="space-y-3">
|
|
123
|
-
<h1 className="text-3xl md:text-4xl font-semibold tracking-tight">
|
|
124
|
-
Page title
|
|
125
|
-
</h1>
|
|
126
|
-
<p className="text-base text-[var(--muted-foreground)] max-w-[640px]">
|
|
127
|
-
One-line supporting description that sets context for the page.
|
|
128
|
-
</p>
|
|
129
|
-
</section>
|
|
130
|
-
|
|
131
|
-
{/* Filters */}
|
|
132
|
-
<section className="flex flex-wrap items-center gap-3">
|
|
133
|
-
<TabGroup
|
|
134
|
-
items={[{ id: "all", title: "All" }, { id: "mine", title: "Mine" }]}
|
|
135
|
-
activeId="all"
|
|
136
|
-
onChange={() => {}}
|
|
137
|
-
size="md"
|
|
138
|
-
/>
|
|
139
|
-
</section>
|
|
140
|
-
|
|
141
|
-
{/* Card grid */}
|
|
142
|
-
<section className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
143
|
-
{/* use <Card /> here */}
|
|
144
|
-
</section>
|
|
145
|
-
</div>
|
|
146
|
-
</main>
|
|
147
|
-
)
|
|
148
|
-
}
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
Form page variant: swap the grid for `<div className="max-w-[480px] mx-auto space-y-5">` and stack `<Input />` fields followed by a single full-width primary `<Button />`.
|
|
152
|
-
|
|
153
|
-
---
|
|
154
|
-
|
|
155
|
-
## The One Rule: use library components, never recreate them
|
|
156
|
-
|
|
157
|
-
If a UI element matches a component in the list below → import it. Do not rebuild it with raw HTML + Tailwind. Do not add classes that restyle its internal look (colors, border, padding, radius, focus ring). The component already owns those.
|
|
158
|
-
|
|
159
|
-
```tsx
|
|
160
|
-
// ✅ CORRECT
|
|
161
|
-
<Input placeholder="Email" value={email} onChange={setEmail} />
|
|
162
|
-
<Button variant="primary" size="md">Sign in</Button>
|
|
163
|
-
<Tag text="Active" variant="green" />
|
|
164
|
-
|
|
165
|
-
// ❌ WRONG — never build your own
|
|
166
|
-
<input className="border rounded-xl px-4 py-3 w-full" placeholder="Email" />
|
|
167
|
-
<button className="bg-blue-600 text-white px-6 py-2 rounded-lg">Sign in</button>
|
|
168
|
-
<span className="text-green-500 bg-green-500/10 px-2 py-1 rounded">Active</span>
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
**If the element is NOT in the list → build it yourself** with Tailwind and tokens. The library does NOT provide: modal/dialog, sidebar, table, accordion, tooltip, progress bar, avatar, breadcrumb, slider, toggle/switch, file upload, chart, carousel, skeleton, pagination, toast/banner, stepper, menu. Build these with plain HTML + Tailwind + the design tokens above.
|
|
172
|
-
|
|
173
|
-
---
|
|
174
|
-
|
|
175
52
|
## Components
|
|
176
53
|
|
|
177
54
|
### Button
|
|
@@ -185,19 +62,17 @@ Icon-only sizes: `icon-xs` | `icon-sm` | `icon-md` | `icon-lg` | `icon-xl`
|
|
|
185
62
|
<Button variant="outline" size="md" onClick={fn}>Cancel</Button>
|
|
186
63
|
<Button variant="plain" size="sm">Learn more</Button>
|
|
187
64
|
<Button variant="primary" size="md" leftIcon={<Icon />}>Add item</Button>
|
|
65
|
+
<Button variant="outline" size="md" rightIcon={<Icon />}>Continue</Button>
|
|
188
66
|
<Button size="icon-md" aria-label="Settings"><GearIcon /></Button>
|
|
189
67
|
```
|
|
190
68
|
|
|
191
69
|
Props: `variant`, `size`, `leftIcon`, `rightIcon`, `disabled`, `onClick`, `className`, `children`
|
|
192
70
|
|
|
193
|
-
|
|
194
|
-
- One `primary` per context. Supporting actions use `outline` or `plain`.
|
|
195
|
-
- For mobile full-width CTAs use `className="w-full"`. Default hug width fits short labels.
|
|
196
|
-
- Do not add classes that change background, border, padding, or radius.
|
|
71
|
+
---
|
|
197
72
|
|
|
198
73
|
### Input
|
|
199
74
|
|
|
200
|
-
Floating-label text input.
|
|
75
|
+
Floating-label text input with validation states.
|
|
201
76
|
|
|
202
77
|
```tsx
|
|
203
78
|
<Input placeholder="Email" value={email} onChange={setEmail} />
|
|
@@ -210,10 +85,7 @@ Floating-label text input.
|
|
|
210
85
|
|
|
211
86
|
Props: `placeholder`, `value`, `onChange`, `type`, `unit`, `showCount`, `maxCount`, `forceState` (`"default"` | `"focus"` | `"error"` | `"disabled"`), `errorMessage`, `required`, `className`
|
|
212
87
|
|
|
213
|
-
|
|
214
|
-
- Keep `placeholder` short — long text breaks the floating label.
|
|
215
|
-
- Never show helper text and `errorMessage` at the same time.
|
|
216
|
-
- Do not override border or background — use `forceState` instead.
|
|
88
|
+
---
|
|
217
89
|
|
|
218
90
|
### TextArea
|
|
219
91
|
|
|
@@ -227,8 +99,12 @@ Multi-line input. API mirrors Input.
|
|
|
227
99
|
|
|
228
100
|
Props: `placeholder`, `value`, `onChange`, `showCount`, `maxCount`, `forceState`, `errorMessage`, `required`, `className`
|
|
229
101
|
|
|
102
|
+
---
|
|
103
|
+
|
|
230
104
|
### SearchInput
|
|
231
105
|
|
|
106
|
+
Search field with icon and clear button.
|
|
107
|
+
|
|
232
108
|
```tsx
|
|
233
109
|
<SearchInput placeholder="Search events..." value={q} onChange={setQ} />
|
|
234
110
|
<SearchInput size="sm" placeholder="Filter by name..." />
|
|
@@ -236,7 +112,11 @@ Props: `placeholder`, `value`, `onChange`, `showCount`, `maxCount`, `forceState`
|
|
|
236
112
|
|
|
237
113
|
Props: `placeholder`, `value`, `onChange`, `size` (`"lg"` | `"sm"`), `className`
|
|
238
114
|
|
|
239
|
-
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
### Dropdown
|
|
118
|
+
|
|
119
|
+
Single-select dropdown.
|
|
240
120
|
|
|
241
121
|
```tsx
|
|
242
122
|
<Dropdown
|
|
@@ -249,7 +129,17 @@ Props: `placeholder`, `value`, `onChange`, `size` (`"lg"` | `"sm"`), `className`
|
|
|
249
129
|
value={selected}
|
|
250
130
|
onChange={setSelected}
|
|
251
131
|
/>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Props: `placeholder`, `options` (`Array<{ label: string, value: string, disabled?: boolean }>`), `value`, `onChange`, `disabled`, `className`
|
|
252
135
|
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### DropdownMultiple
|
|
139
|
+
|
|
140
|
+
Multi-select dropdown with checkboxes.
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
253
143
|
<DropdownMultiple
|
|
254
144
|
placeholder="Select tags"
|
|
255
145
|
options={options}
|
|
@@ -258,32 +148,18 @@ Props: `placeholder`, `value`, `onChange`, `size` (`"lg"` | `"sm"`), `className`
|
|
|
258
148
|
/>
|
|
259
149
|
```
|
|
260
150
|
|
|
261
|
-
Props
|
|
262
|
-
Props (multi): `placeholder`, `options`, `values`, `onChange`, `disabled`, `className`
|
|
151
|
+
Props: `placeholder`, `options`, `values`, `onChange`, `disabled`, `className`
|
|
263
152
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
### OptionList
|
|
267
|
-
|
|
268
|
-
Scrollable option list for custom popups.
|
|
269
|
-
|
|
270
|
-
```tsx
|
|
271
|
-
<OptionList
|
|
272
|
-
options={[{ label: "Item A", value: "a" }, { label: "Item B", value: "b" }]}
|
|
273
|
-
selectedValue={value}
|
|
274
|
-
onSelect={setValue}
|
|
275
|
-
/>
|
|
276
|
-
|
|
277
|
-
<OptionList options={options} selectedValues={values} onToggle={toggleValue} />
|
|
278
|
-
```
|
|
153
|
+
---
|
|
279
154
|
|
|
280
155
|
### Tag
|
|
281
156
|
|
|
282
|
-
Compact colored label for categories
|
|
157
|
+
Compact colored label. Use for categories, statuses, filters.
|
|
283
158
|
|
|
284
159
|
Variants: `blue` | `green` | `yellow` | `red` | `gray` | `lime`
|
|
285
160
|
Sizes: `large` (default) | `small`
|
|
286
|
-
|
|
161
|
+
|
|
162
|
+
Color semantics: `green` → positive/active, `red` → error/danger, `yellow` → warning/pending, `blue` → informational, `gray` → neutral/inactive
|
|
287
163
|
|
|
288
164
|
```tsx
|
|
289
165
|
<Tag text="Active" variant="green" />
|
|
@@ -294,11 +170,11 @@ Semantics: `green` = positive, `red` = error, `yellow` = warning/pending, `blue`
|
|
|
294
170
|
|
|
295
171
|
Props: `text`, `variant`, `size`, `close`, `onClose`, `className`
|
|
296
172
|
|
|
297
|
-
|
|
173
|
+
---
|
|
298
174
|
|
|
299
175
|
### StatusTag
|
|
300
176
|
|
|
301
|
-
|
|
177
|
+
Process-flow state indicator with colored dot.
|
|
302
178
|
|
|
303
179
|
Types: `stop` | `success` | `hold` | `processing` | `error`
|
|
304
180
|
|
|
@@ -306,36 +182,41 @@ Types: `stop` | `success` | `hold` | `processing` | `error`
|
|
|
306
182
|
<StatusTag type="success" />
|
|
307
183
|
<StatusTag type="processing" text="In progress" />
|
|
308
184
|
<StatusTag type="hold" />
|
|
185
|
+
<StatusTag type="error" />
|
|
309
186
|
```
|
|
310
187
|
|
|
311
188
|
Props: `type`, `text`, `className`
|
|
312
189
|
|
|
313
|
-
Use `StatusTag` for
|
|
190
|
+
Use `StatusTag` for workflow states. Use `Tag` for categorical labels.
|
|
191
|
+
|
|
192
|
+
---
|
|
314
193
|
|
|
315
194
|
### Chip
|
|
316
195
|
|
|
317
|
-
Toggleable filter chip. Always use in groups of 2+.
|
|
196
|
+
Toggleable filter/selection chip. Always use in groups of 2+.
|
|
318
197
|
|
|
319
198
|
Types: `single` (default) | `multiple`
|
|
320
199
|
Sizes: `large` | `medium` | `small`
|
|
321
200
|
|
|
322
201
|
```tsx
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
<Chip label="
|
|
326
|
-
<Chip label="
|
|
202
|
+
{/* Single-select group */}
|
|
203
|
+
<div className="flex gap-2">
|
|
204
|
+
<Chip label="All" selected={filter === "all"} onClick={() => setFilter("all")} />
|
|
205
|
+
<Chip label="Active" selected={filter === "active"} onClick={() => setFilter("active")} />
|
|
206
|
+
<Chip label="Archived" selected={filter === "archived"} onClick={() => setFilter("archived")} />
|
|
327
207
|
</div>
|
|
328
208
|
|
|
209
|
+
{/* Multi-select */}
|
|
329
210
|
<Chip label="Design" type="multiple" selected={tags.includes("design")} onClick={() => toggle("design")} />
|
|
330
211
|
```
|
|
331
212
|
|
|
332
213
|
Props: `label`, `selected`, `onClick`, `type`, `size`, `disabled`, `className`
|
|
333
214
|
|
|
334
|
-
|
|
215
|
+
---
|
|
335
216
|
|
|
336
217
|
### TabGroup
|
|
337
218
|
|
|
338
|
-
Tabbed navigation. Always use `TabGroup
|
|
219
|
+
Tabbed navigation. Always use `TabGroup`, not bare `Tab`.
|
|
339
220
|
|
|
340
221
|
Sizes: `lg` | `md` (default) | `sm`
|
|
341
222
|
|
|
@@ -353,13 +234,15 @@ Sizes: `lg` | `md` (default) | `sm`
|
|
|
353
234
|
/>
|
|
354
235
|
```
|
|
355
236
|
|
|
356
|
-
Props: `items` (`{ id, title, icon
|
|
237
|
+
Props: `items` (`Array<{ id: string, title: string, icon?: boolean, notification?: number, disabled?: boolean }>`), `activeId`, `onChange`, `size`, `className`
|
|
238
|
+
|
|
239
|
+
---
|
|
357
240
|
|
|
358
241
|
### Card
|
|
359
242
|
|
|
360
|
-
|
|
243
|
+
Responsive event/content card.
|
|
361
244
|
|
|
362
|
-
Variants: `desktop` (308px) | `tablet` (224px) | `mobile` (163px)
|
|
245
|
+
Variants: `desktop` (308px wide) | `tablet` (224px wide) | `mobile` (163px wide)
|
|
363
246
|
tagStatus: `not-registered` | `registered` | `full`
|
|
364
247
|
|
|
365
248
|
```tsx
|
|
@@ -377,7 +260,9 @@ tagStatus: `not-registered` | `registered` | `full`
|
|
|
377
260
|
|
|
378
261
|
Props: `variant`, `title`, `date`, `time`, `location`, `count`, `tagStatus`, `image`, `className`
|
|
379
262
|
|
|
380
|
-
Match
|
|
263
|
+
Match variant to viewport: `desktop` on wide layouts, `tablet` / `mobile` on narrow.
|
|
264
|
+
|
|
265
|
+
---
|
|
381
266
|
|
|
382
267
|
### DateInput
|
|
383
268
|
|
|
@@ -394,46 +279,90 @@ Variants: `popover` (default) | `modal`
|
|
|
394
279
|
|
|
395
280
|
Props: `placeholder`, `mode`, `value`, `onChange`, `variant`, `disabled`, `className`
|
|
396
281
|
|
|
397
|
-
|
|
282
|
+
---
|
|
398
283
|
|
|
399
284
|
### TimeInput
|
|
400
285
|
|
|
401
286
|
24-hour time picker.
|
|
402
287
|
|
|
288
|
+
Modes: `single` | `range`
|
|
289
|
+
|
|
403
290
|
```tsx
|
|
404
291
|
<TimeInput placeholder="Start time" value={time} onChange={setTime} />
|
|
405
292
|
<TimeInput mode="range" value={timeRange} onChange={setTimeRange} />
|
|
406
293
|
```
|
|
407
294
|
|
|
408
|
-
Props: `placeholder`, `mode
|
|
295
|
+
Props: `placeholder`, `mode`, `value`, `onChange`, `disabled`, `className`
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
### OptionList
|
|
300
|
+
|
|
301
|
+
Scrollable option list for custom dropdowns.
|
|
302
|
+
|
|
303
|
+
```tsx
|
|
304
|
+
{/* Single-select */}
|
|
305
|
+
<OptionList
|
|
306
|
+
options={[{ label: "Item A", value: "a" }, { label: "Item B", value: "b" }]}
|
|
307
|
+
selectedValue={value}
|
|
308
|
+
onSelect={setValue}
|
|
309
|
+
/>
|
|
310
|
+
|
|
311
|
+
{/* Multi-select */}
|
|
312
|
+
<OptionList
|
|
313
|
+
options={options}
|
|
314
|
+
selectedValues={values}
|
|
315
|
+
onToggle={toggleValue}
|
|
316
|
+
/>
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Props: `options` (`Array<{ label: string, value: string, disabled?: boolean }>`), `selectedValue`, `onSelect`, `selectedValues`, `onToggle`, `className`
|
|
409
320
|
|
|
410
321
|
---
|
|
411
322
|
|
|
412
|
-
##
|
|
323
|
+
## Design Tokens
|
|
413
324
|
|
|
414
325
|
All tokens are CSS custom properties. Override after the stylesheet import:
|
|
415
326
|
|
|
416
327
|
```css
|
|
417
328
|
:root {
|
|
418
|
-
--primary-action: #7c3aed;
|
|
329
|
+
--primary-action: #7c3aed; /* brand color */
|
|
419
330
|
--primary-action-hover: #6d28d9;
|
|
420
331
|
--primary-action-active: #5b21b6;
|
|
421
|
-
--font-sans: "Inter", sans-serif;
|
|
422
|
-
--radius: 8px;
|
|
332
|
+
--font-sans: "Inter", sans-serif; /* override library default (Noto Sans Thai) */
|
|
333
|
+
--radius: 8px; /* border radius base */
|
|
423
334
|
}
|
|
424
335
|
```
|
|
425
336
|
|
|
426
|
-
Key tokens:
|
|
337
|
+
Key tokens:
|
|
338
|
+
- `--primary-action` — brand color (primary buttons, links, active states)
|
|
339
|
+
- `--background` / `--foreground` — page surface and text
|
|
340
|
+
- `--border` — default border color
|
|
341
|
+
- `--muted-foreground` — placeholder and secondary text
|
|
342
|
+
- `--destructive` — error/danger red
|
|
343
|
+
- `--success` — success green
|
|
344
|
+
- `--font-sans` — font family (default: Noto Sans Thai)
|
|
345
|
+
- `--radius` — base border radius (default: 4px)
|
|
427
346
|
|
|
428
|
-
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Dark Mode
|
|
429
350
|
|
|
430
|
-
Add `.dark` to `<html>`
|
|
351
|
+
Add `.dark` to `<html>` or any ancestor:
|
|
431
352
|
|
|
432
353
|
```tsx
|
|
354
|
+
// Next.js
|
|
355
|
+
<html className={isDark ? "dark" : ""}>
|
|
356
|
+
|
|
357
|
+
// Vite
|
|
433
358
|
document.documentElement.classList.toggle("dark", isDark)
|
|
434
359
|
```
|
|
435
360
|
|
|
436
|
-
|
|
361
|
+
All components adapt automatically.
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## TypeScript
|
|
437
366
|
|
|
438
367
|
```ts
|
|
439
368
|
import type { ButtonVariant, ButtonSize, TagVariant, ChipSize } from "@sarunyu/system-one"
|
|
@@ -441,14 +370,141 @@ import type { ButtonVariant, ButtonSize, TagVariant, ChipSize } from "@sarunyu/s
|
|
|
441
370
|
|
|
442
371
|
---
|
|
443
372
|
|
|
444
|
-
##
|
|
373
|
+
## Design Guidance
|
|
374
|
+
|
|
375
|
+
**You design the layout. The library provides the components.** Compose, arrange, and style page structure freely using Tailwind — the library does not dictate layout. Focus instead on using the components below correctly (right variants, right semantics, right props) so the visual language stays consistent with the design system.
|
|
376
|
+
|
|
377
|
+
### Anchors the library provides
|
|
378
|
+
|
|
379
|
+
These are all you need to keep a page visually coherent with the system:
|
|
380
|
+
|
|
381
|
+
- **Typography** — `<h1>`–`<h4>` are pre-styled (size, weight, line-height). Use them as-is; do not add `text-*` or `font-*` classes to override.
|
|
382
|
+
- **Text color** — body text inherits from `--foreground`. Secondary text: `text-muted-foreground`. Don't hard-code hex colors.
|
|
383
|
+
- **Surfaces** — use `bg-background` / `bg-card` / `bg-muted` so dark mode works automatically.
|
|
384
|
+
- **Brand color** — reference via `--primary-action`, or Tailwind `text-primary-action` / `bg-primary-action`.
|
|
385
|
+
- **Spacing** — Tailwind spacing scale (4px units). Pick whatever gaps/paddings look right; the design system has no prescribed rhythm.
|
|
386
|
+
- **Radius** — `rounded-md` (6px), `rounded-lg` (8px), `rounded-xl` (12px), `rounded-full`.
|
|
387
|
+
|
|
388
|
+
### Component usage — the only hard rules
|
|
389
|
+
|
|
390
|
+
Use the library's components for these elements. Do **not** recreate them with raw HTML or Tailwind:
|
|
391
|
+
|
|
392
|
+
- Buttons → `<Button>` (never `<button>` with utility classes)
|
|
393
|
+
- Text inputs / textareas / search → `<Input>`, `<TextArea>`, `<SearchInput>`
|
|
394
|
+
- Single / multi select → `<Dropdown>`, `<DropdownMultiple>`, `<OptionList>`
|
|
395
|
+
- Date / time pickers → `<DateInput>`, `<TimeInput>`
|
|
396
|
+
- Labels / statuses / filters → `<Tag>`, `<StatusTag>`, `<Chip>`
|
|
397
|
+
- Tabs → `<TabGroup>` (never `<Tab>` alone)
|
|
398
|
+
- Event/content cards → `<Card>`
|
|
399
|
+
|
|
400
|
+
Pick the correct variant / prop for the semantic role (e.g. `StatusTag type="success"` for success, `Button variant="primary"` once per context). The per-component rules below in "Design Rules" are the full reference.
|
|
401
|
+
|
|
402
|
+
### Layout freedom
|
|
403
|
+
|
|
404
|
+
Structure the page however you like — any combination of flex, grid, max-width containers, custom spacing, hero compositions, sidebars, split layouts, dashboards, etc. Use aesthetic judgement: generous padding, clear hierarchy, balanced whitespace. The only layout constraint is that content shouldn't sit flush against viewport edges or adjacent blocks without breathing room.
|
|
405
|
+
|
|
406
|
+
### Optional: layout helper components
|
|
407
|
+
|
|
408
|
+
The library also ships a small set of layout primitives (`Page`, `PageHeader`, `Section`, `Toolbar`, `CardGrid`, `Stack`) that some users prefer for rapid scaffolding. They are entirely optional — skip them and design your own layout if that produces a better result. If you do use them, they apply sensible defaults (centered container, 48px section gap, responsive card grid). Full API is in the TypeScript types.
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## Notes
|
|
413
|
+
|
|
414
|
+
- No provider or wrapper needed — just import the stylesheet and use components
|
|
415
|
+
- The library ships pre-built CSS — no Tailwind config required in the consuming project
|
|
416
|
+
- If the project already uses Tailwind and you see font differences, override `--font-sans` in your CSS
|
|
417
|
+
- CSS custom properties can be referenced in Tailwind classes: `bg-[var(--primary-action)]`
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## Component Usage Guide
|
|
422
|
+
|
|
423
|
+
### Button
|
|
424
|
+
|
|
425
|
+
Buttons are interactive elements that allow users to click or tap to perform actions — such as submitting a form, saving data, navigating to another page, or opening a popup.
|
|
426
|
+
|
|
427
|
+
Types: `primary` — main CTA, use once per context. `outline` — secondary action, supports primary without affecting main flow. `plain` — no background or border, low-priority actions. `secondary` (outline-black) — same as outline but black, use when blue is not appropriate.
|
|
428
|
+
|
|
429
|
+
Sizes: `base` recommended for Desktop. `xl` recommended for Mobile.
|
|
430
|
+
|
|
431
|
+
States: Default, Hover, Press, Disabled.
|
|
432
|
+
|
|
433
|
+
### Input
|
|
434
|
+
|
|
435
|
+
Floating Label Input is a single-line or multi-line field with a label that floats inside.
|
|
436
|
+
|
|
437
|
+
Types: `Input` — single-line, general text/numeric. `TextArea` — multi-line, for comments or addresses. `Dropdown` — predefined list selection, displays as OptionList.
|
|
438
|
+
|
|
439
|
+
States: Default, Focus, Error, Disabled.
|
|
440
|
+
|
|
441
|
+
### Tab
|
|
442
|
+
|
|
443
|
+
Tabs allow users to switch between different content sections within the same page.
|
|
444
|
+
|
|
445
|
+
Types: Default (text only), Icon (with icon), Notification (with badge).
|
|
446
|
+
|
|
447
|
+
States: Default, Active (selected), Disabled.
|
|
448
|
+
|
|
449
|
+
Sizes: `lg` (default), `md`, `sm`. Always use the same size within one tab group.
|
|
450
|
+
|
|
451
|
+
### Tag
|
|
452
|
+
|
|
453
|
+
Tags display categories, types, or short metadata. Status Tags communicate process state.
|
|
454
|
+
|
|
455
|
+
Tag types: Default (text only), Icon (with icon), Remove (dismissible).
|
|
456
|
+
Tag states: Default, Hover/Press, Disabled.
|
|
457
|
+
Tag sizes: Large, Small.
|
|
458
|
+
|
|
459
|
+
Status Tag variants: `success` — process completed. `processing` — in progress. `hold` — temporarily paused. `stop` — stopped. `error` — failed.
|
|
460
|
+
|
|
461
|
+
### Chip
|
|
462
|
+
|
|
463
|
+
Chips are toggleable filter/selection elements. Always use in groups of 2+.
|
|
464
|
+
|
|
465
|
+
Types: `single` — one selection at a time. `multiple` — multiple selections simultaneously.
|
|
466
|
+
|
|
467
|
+
States: Default, Active, Disabled (Default), Disabled (Active).
|
|
468
|
+
|
|
469
|
+
Sizes: `large`, `medium`, `small`.
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## Design Rules
|
|
474
|
+
|
|
475
|
+
### Button Rules
|
|
476
|
+
|
|
477
|
+
- Maximum width is 343px (`max-w-[343px]`). Never remove or override. Do not detach the component.
|
|
478
|
+
- Do not manually recreate or modify padding (`py-[10px] px-[16px]`) or border-radius (`rounded-[8px]`).
|
|
479
|
+
- Use Primary only once per context. Never place two Primary buttons side-by-side. Use Outline/Secondary for supporting actions.
|
|
480
|
+
- Hug width is correct for short labels only. For long labels, use Fill width or set explicit max-w-[343px].
|
|
481
|
+
|
|
482
|
+
### Input Rules
|
|
483
|
+
|
|
484
|
+
- Never override input border or background colors manually — use built-in state variants (Default, Focus, Error, Disabled).
|
|
485
|
+
- Do not recreate input styles manually or adjust gap/height outside the component's tokens.
|
|
486
|
+
- Use Dropdown Tags only for multi-select. For single-select, use standard Dropdown.
|
|
487
|
+
- Keep labelText short enough to fit on one line. Long labels break the floating label layout.
|
|
488
|
+
- Do not show Helper Text and Error Message simultaneously. Error state replaces helper text.
|
|
489
|
+
- Use Option List only when there are multiple selectable values.
|
|
490
|
+
- Always follow the system date format (DD MMM YYYY). Do not mix Thai month names with C.E. year or reorder day/month/year.
|
|
491
|
+
|
|
492
|
+
### Tab Rules
|
|
493
|
+
|
|
494
|
+
- Do not override padding, gap, border-radius, element order, or colors inside the tab bar.
|
|
495
|
+
- All tabs in one group must use the same size prop. Never mix sizes.
|
|
496
|
+
- Do not add extra gap or margin between tabs — use built-in spacing tokens only.
|
|
497
|
+
|
|
498
|
+
### Tag Rules
|
|
499
|
+
|
|
500
|
+
- Do not override height, padding, or layout of Tag or StatusTag.
|
|
501
|
+
- All tags in the same context must use the same size.
|
|
502
|
+
- Do not reorder internal elements (icon, label, badge) inside Tag or StatusTag.
|
|
503
|
+
- Always use the correct StatusTag variant for its semantic meaning. Never override the color.
|
|
445
504
|
|
|
446
|
-
|
|
447
|
-
2. Every UI element that matches a library component uses that component, not raw HTML + Tailwind?
|
|
448
|
-
3. Page wrapped in `max-w-[1200px] mx-auto px-6` (or form variant)?
|
|
449
|
-
4. Section rhythm uses `space-y-12` / `space-y-6` / `space-y-4`?
|
|
450
|
-
5. h1 / h2 / h3 sized from the typography scale above?
|
|
451
|
-
6. All brand + semantic colors use `var(--...)` tokens, not `bg-blue-500` etc.?
|
|
452
|
-
7. Only one `Button variant="primary"` per context?
|
|
505
|
+
### Chip Rules
|
|
453
506
|
|
|
454
|
-
|
|
507
|
+
- Keep chip labels short and single-purpose. Long labels cause overflow.
|
|
508
|
+
- Do not override padding, gap, radius, height, element order, or colors.
|
|
509
|
+
- All chips in the same group must share the same size and spacing.
|
|
510
|
+
- Use Chip only for groups with 2+ options. For single options, use a toggle or checkbox instead.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sarunyu/system-one",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A production-ready React design system built for AI-powered web generation tools (Figma Make, Lovable, V0). Tailwind CSS v4 + CSS custom properties for full theming support.",
|
|
6
6
|
"keywords": [
|