@sarunyu/system-one 2.0.2 → 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 +259 -274
- 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,36 +8,35 @@ 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 {
|
|
40
|
-
// Layout
|
|
41
|
-
Page, PageHeader, Section, Toolbar, Stack, CardGrid,
|
|
42
|
-
// UI
|
|
43
40
|
Button, Input, TextArea, SearchInput,
|
|
44
41
|
Dropdown, DropdownMultiple, OptionList,
|
|
45
42
|
Tag, StatusTag, Chip,
|
|
@@ -52,199 +49,6 @@ import {
|
|
|
52
49
|
|
|
53
50
|
---
|
|
54
51
|
|
|
55
|
-
## Color rules — read this before touching any className
|
|
56
|
-
|
|
57
|
-
**Never** use Tailwind's literal color utilities for brand or semantic roles. Only use design token CSS variables.
|
|
58
|
-
|
|
59
|
-
| Role | ✅ Use this token | ❌ Never this |
|
|
60
|
-
|---|---|---|
|
|
61
|
-
| Brand / CTA | `var(--primary-action)` | `bg-blue-600`, `text-blue-500` |
|
|
62
|
-
| Hover / active | `var(--primary-action-hover)`, `var(--primary-action-active)` | `hover:bg-blue-700` |
|
|
63
|
-
| Body text | `var(--foreground)` | `text-gray-900` |
|
|
64
|
-
| Secondary text | `var(--muted-foreground)` | `text-gray-500` |
|
|
65
|
-
| Page background | `var(--background)` | `bg-white`, `bg-gray-50` |
|
|
66
|
-
| Card surface | `var(--card)` | `bg-white` |
|
|
67
|
-
| Subtle surface | `var(--muted)` | `bg-gray-100` |
|
|
68
|
-
| Border | `var(--border)` | `border-gray-200` |
|
|
69
|
-
| Subtle divider | `var(--divider)` | `border-gray-100` |
|
|
70
|
-
| Success | `var(--success)` | `text-green-600` |
|
|
71
|
-
| Error / danger | `var(--destructive)` | `text-red-500` |
|
|
72
|
-
| Warning | `var(--accent-orange)` | `text-orange-400` |
|
|
73
|
-
|
|
74
|
-
Usage in className: `className="bg-[var(--card)] text-[var(--foreground)] border-[var(--border)]"`
|
|
75
|
-
Usage in style: `style={{ color: "var(--primary-action)" }}`
|
|
76
|
-
|
|
77
|
-
---
|
|
78
|
-
|
|
79
|
-
## Layout — design freely, but color with tokens
|
|
80
|
-
|
|
81
|
-
**Layout is yours to design.** Use any structure, spacing, grid, or visual treatment you want. The two hard rules are:
|
|
82
|
-
|
|
83
|
-
1. **Colors** — every color must come from a design token (see Color rules above). No `bg-blue-500`, no `text-gray-900`.
|
|
84
|
-
2. **UI components** — if the element matches a library component, use it (see The One Rule below).
|
|
85
|
-
|
|
86
|
-
Everything else — container widths, grids, flex layouts, padding, hero sections, sidebars, split-pane, masonry, sticky headers, full-bleed backgrounds — is open. Design whatever looks great.
|
|
87
|
-
|
|
88
|
-
### Layout convenience components (optional)
|
|
89
|
-
|
|
90
|
-
The library exports these if you want quick, consistent scaffolding. Use them when they fit; skip them and write raw Tailwind when they don't.
|
|
91
|
-
|
|
92
|
-
| Component | What it gives you |
|
|
93
|
-
|---|---|
|
|
94
|
-
| `<Page width="lg">` | `max-w-[1200px] mx-auto px-6 py-10 flex flex-col gap-12` |
|
|
95
|
-
| `<PageHeader title actions eyebrow>` | Responsive title + description + right-aligned actions |
|
|
96
|
-
| `<Section title description actions>` | Content group with optional header, `flex flex-col gap-6` |
|
|
97
|
-
| `<Toolbar end>` | Left cluster + right cluster split with `ml-auto` |
|
|
98
|
-
| `<Stack direction gap align justify wrap>` | Flex col/row with typed gap scale |
|
|
99
|
-
| `<CardGrid cols>` | Responsive grid, always `gap-6` |
|
|
100
|
-
|
|
101
|
-
```tsx
|
|
102
|
-
// Using convenience components
|
|
103
|
-
<Page width="lg">
|
|
104
|
-
<PageHeader title="Events" actions={<Button variant="primary" size="md">Add</Button>} />
|
|
105
|
-
<Section title="All Events">
|
|
106
|
-
<CardGrid cols={3}><Card ... /></CardGrid>
|
|
107
|
-
</Section>
|
|
108
|
-
</Page>
|
|
109
|
-
|
|
110
|
-
// Or go fully custom — both are fine
|
|
111
|
-
<div className="min-h-screen bg-[var(--background)]">
|
|
112
|
-
<div className="relative h-[480px] bg-[var(--primary-action)] flex items-end pb-16 px-12">
|
|
113
|
-
<h1 className="text-5xl font-bold text-[var(--on-primary-action)]">Events</h1>
|
|
114
|
-
</div>
|
|
115
|
-
<div className="max-w-[1400px] mx-auto px-8 -mt-12 grid grid-cols-4 gap-6">
|
|
116
|
-
<Card variant="desktop" ... />
|
|
117
|
-
</div>
|
|
118
|
-
</div>
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
**Stack gap scale** (if used): `1`=4px · `2`=8px · `3`=12px · `4`=16px · `6`=24px · `8`=32px · `10`=40px · `12`=48px
|
|
122
|
-
|
|
123
|
-
---
|
|
124
|
-
|
|
125
|
-
## The One Rule: use library UI components, never recreate them
|
|
126
|
-
|
|
127
|
-
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.
|
|
128
|
-
|
|
129
|
-
```tsx
|
|
130
|
-
// ✅ CORRECT
|
|
131
|
-
<Input placeholder="Email" value={email} onChange={setEmail} />
|
|
132
|
-
<Button variant="primary" size="md">Sign in</Button>
|
|
133
|
-
<Tag text="Active" variant="green" />
|
|
134
|
-
|
|
135
|
-
// ❌ WRONG — never recreate library components
|
|
136
|
-
<input className="border rounded-xl px-4 py-3 w-full" placeholder="Email" />
|
|
137
|
-
<button className="bg-blue-600 text-white px-6 py-2 rounded-lg">Sign in</button>
|
|
138
|
-
<span className="bg-green-100 text-green-700 px-2 py-1 rounded">Active</span>
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
**If the element is NOT in the library → build it freely with Tailwind + 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.
|
|
142
|
-
|
|
143
|
-
---
|
|
144
|
-
|
|
145
|
-
## Starter snippets
|
|
146
|
-
|
|
147
|
-
These are minimal correct examples. Layout structure is up to you — the important parts are the component imports and the `var(--...)` tokens.
|
|
148
|
-
|
|
149
|
-
**List page (using convenience components):**
|
|
150
|
-
|
|
151
|
-
```tsx
|
|
152
|
-
import { Page, PageHeader, Section, Toolbar, CardGrid, Button, SearchInput, TabGroup, Card } from "@sarunyu/system-one"
|
|
153
|
-
|
|
154
|
-
export default function EventsPage() {
|
|
155
|
-
return (
|
|
156
|
-
<div className="min-h-screen bg-[var(--background)] text-[var(--foreground)]">
|
|
157
|
-
<Page width="lg">
|
|
158
|
-
<PageHeader
|
|
159
|
-
title="Events"
|
|
160
|
-
description="Browse and manage your events."
|
|
161
|
-
actions={<Button variant="primary" size="md">New Event</Button>}
|
|
162
|
-
/>
|
|
163
|
-
<Section title="All Events">
|
|
164
|
-
<Toolbar end={<SearchInput size="sm" placeholder="Search..." value="" onChange={() => {}} />}>
|
|
165
|
-
<TabGroup items={[{ id: "all", title: "All" }, { id: "mine", title: "Mine" }]} activeId="all" onChange={() => {}} size="md" />
|
|
166
|
-
</Toolbar>
|
|
167
|
-
<CardGrid cols={3}>
|
|
168
|
-
<Card variant="desktop" title="Annual Conference" date="Jun 23, 2024" time="08:30 - 12:00" location="Main Hall" count="150/200" tagStatus="registered" image="/banner.jpg" />
|
|
169
|
-
</CardGrid>
|
|
170
|
-
</Section>
|
|
171
|
-
</Page>
|
|
172
|
-
</div>
|
|
173
|
-
)
|
|
174
|
-
}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
**List page (custom layout — equally valid):**
|
|
178
|
-
|
|
179
|
-
```tsx
|
|
180
|
-
import { Button, SearchInput, TabGroup, Card } from "@sarunyu/system-one"
|
|
181
|
-
|
|
182
|
-
export default function EventsPage() {
|
|
183
|
-
return (
|
|
184
|
-
<div className="min-h-screen bg-[var(--background)]">
|
|
185
|
-
{/* Hero */}
|
|
186
|
-
<div className="bg-[var(--primary-action)] px-12 pt-20 pb-32">
|
|
187
|
-
<h1 className="text-5xl font-bold text-[var(--on-primary-action)]">Events</h1>
|
|
188
|
-
<p className="mt-3 text-lg text-[var(--on-primary-action)] opacity-80">Browse and manage your events.</p>
|
|
189
|
-
</div>
|
|
190
|
-
{/* Content pulled up over hero */}
|
|
191
|
-
<div className="max-w-[1400px] mx-auto px-8 -mt-16">
|
|
192
|
-
<div className="flex items-center justify-between mb-6">
|
|
193
|
-
<TabGroup items={[{ id: "all", title: "All" }, { id: "mine", title: "Mine" }]} activeId="all" onChange={() => {}} size="md" />
|
|
194
|
-
<div className="flex gap-3">
|
|
195
|
-
<SearchInput size="sm" placeholder="Search..." value="" onChange={() => {}} />
|
|
196
|
-
<Button variant="primary" size="md">New Event</Button>
|
|
197
|
-
</div>
|
|
198
|
-
</div>
|
|
199
|
-
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6">
|
|
200
|
-
<Card variant="desktop" title="Annual Conference" date="Jun 23, 2024" time="08:30 - 12:00" location="Main Hall" count="150/200" tagStatus="registered" image="/banner.jpg" />
|
|
201
|
-
</div>
|
|
202
|
-
</div>
|
|
203
|
-
</div>
|
|
204
|
-
)
|
|
205
|
-
}
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
**Form page:**
|
|
209
|
-
|
|
210
|
-
```tsx
|
|
211
|
-
import { Stack, Input, TextArea, DateInput, TimeInput, Dropdown, Button } from "@sarunyu/system-one"
|
|
212
|
-
|
|
213
|
-
<div className="max-w-[480px] mx-auto px-6 py-12 space-y-6">
|
|
214
|
-
<h1 className="text-3xl font-semibold text-[var(--foreground)]">Create Event</h1>
|
|
215
|
-
<Stack gap={5}>
|
|
216
|
-
<Input placeholder="Event Name" value={name} onChange={setName} />
|
|
217
|
-
<TextArea placeholder="Description" value={desc} onChange={setDesc} />
|
|
218
|
-
<div className="grid grid-cols-2 gap-3">
|
|
219
|
-
<DateInput placeholder="Start date" mode="single" value={date} onChange={setDate} />
|
|
220
|
-
<TimeInput placeholder="Start time" value={time} onChange={setTime} />
|
|
221
|
-
</div>
|
|
222
|
-
<Dropdown placeholder="Category" options={cats} value={cat} onChange={setCat} />
|
|
223
|
-
<Button variant="primary" size="md" className="w-full">Create Event</Button>
|
|
224
|
-
</Stack>
|
|
225
|
-
</div>
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
---
|
|
229
|
-
|
|
230
|
-
## Typography scale
|
|
231
|
-
|
|
232
|
-
The library does NOT preset h1–h6 — set them yourself with Tailwind. Always pair with token colors.
|
|
233
|
-
|
|
234
|
-
- Page title (h1): `text-3xl md:text-4xl font-semibold tracking-tight text-[var(--foreground)]`
|
|
235
|
-
- Section title (h2): `text-2xl font-semibold text-[var(--foreground)]`
|
|
236
|
-
- Subsection (h3): `text-lg font-semibold text-[var(--foreground)]`
|
|
237
|
-
- Body: `text-base text-[var(--foreground)]` — never smaller than `text-sm` for reading content
|
|
238
|
-
- Caption / meta: `text-sm text-[var(--muted-foreground)]`
|
|
239
|
-
|
|
240
|
-
### Radius
|
|
241
|
-
|
|
242
|
-
- Inputs, buttons, small elements: `rounded-md` (6px)
|
|
243
|
-
- Cards, panels: `rounded-xl` (12px) or `rounded-2xl` (16px)
|
|
244
|
-
- Pills / avatars: `rounded-full`
|
|
245
|
-
|
|
246
|
-
---
|
|
247
|
-
|
|
248
52
|
## Components
|
|
249
53
|
|
|
250
54
|
### Button
|
|
@@ -258,19 +62,17 @@ Icon-only sizes: `icon-xs` | `icon-sm` | `icon-md` | `icon-lg` | `icon-xl`
|
|
|
258
62
|
<Button variant="outline" size="md" onClick={fn}>Cancel</Button>
|
|
259
63
|
<Button variant="plain" size="sm">Learn more</Button>
|
|
260
64
|
<Button variant="primary" size="md" leftIcon={<Icon />}>Add item</Button>
|
|
65
|
+
<Button variant="outline" size="md" rightIcon={<Icon />}>Continue</Button>
|
|
261
66
|
<Button size="icon-md" aria-label="Settings"><GearIcon /></Button>
|
|
262
67
|
```
|
|
263
68
|
|
|
264
69
|
Props: `variant`, `size`, `leftIcon`, `rightIcon`, `disabled`, `onClick`, `className`, `children`
|
|
265
70
|
|
|
266
|
-
|
|
267
|
-
- One `primary` per context. Supporting actions use `outline` or `plain`.
|
|
268
|
-
- For mobile full-width CTAs use `className="w-full"`. Default hug width fits short labels.
|
|
269
|
-
- Do not add classes that change background, border, padding, or radius.
|
|
71
|
+
---
|
|
270
72
|
|
|
271
73
|
### Input
|
|
272
74
|
|
|
273
|
-
Floating-label text input.
|
|
75
|
+
Floating-label text input with validation states.
|
|
274
76
|
|
|
275
77
|
```tsx
|
|
276
78
|
<Input placeholder="Email" value={email} onChange={setEmail} />
|
|
@@ -283,10 +85,7 @@ Floating-label text input.
|
|
|
283
85
|
|
|
284
86
|
Props: `placeholder`, `value`, `onChange`, `type`, `unit`, `showCount`, `maxCount`, `forceState` (`"default"` | `"focus"` | `"error"` | `"disabled"`), `errorMessage`, `required`, `className`
|
|
285
87
|
|
|
286
|
-
|
|
287
|
-
- Keep `placeholder` short — long text breaks the floating label.
|
|
288
|
-
- Never show helper text and `errorMessage` at the same time.
|
|
289
|
-
- Do not override border or background — use `forceState` instead.
|
|
88
|
+
---
|
|
290
89
|
|
|
291
90
|
### TextArea
|
|
292
91
|
|
|
@@ -300,8 +99,12 @@ Multi-line input. API mirrors Input.
|
|
|
300
99
|
|
|
301
100
|
Props: `placeholder`, `value`, `onChange`, `showCount`, `maxCount`, `forceState`, `errorMessage`, `required`, `className`
|
|
302
101
|
|
|
102
|
+
---
|
|
103
|
+
|
|
303
104
|
### SearchInput
|
|
304
105
|
|
|
106
|
+
Search field with icon and clear button.
|
|
107
|
+
|
|
305
108
|
```tsx
|
|
306
109
|
<SearchInput placeholder="Search events..." value={q} onChange={setQ} />
|
|
307
110
|
<SearchInput size="sm" placeholder="Filter by name..." />
|
|
@@ -309,7 +112,11 @@ Props: `placeholder`, `value`, `onChange`, `showCount`, `maxCount`, `forceState`
|
|
|
309
112
|
|
|
310
113
|
Props: `placeholder`, `value`, `onChange`, `size` (`"lg"` | `"sm"`), `className`
|
|
311
114
|
|
|
312
|
-
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
### Dropdown
|
|
118
|
+
|
|
119
|
+
Single-select dropdown.
|
|
313
120
|
|
|
314
121
|
```tsx
|
|
315
122
|
<Dropdown
|
|
@@ -322,7 +129,17 @@ Props: `placeholder`, `value`, `onChange`, `size` (`"lg"` | `"sm"`), `className`
|
|
|
322
129
|
value={selected}
|
|
323
130
|
onChange={setSelected}
|
|
324
131
|
/>
|
|
132
|
+
```
|
|
325
133
|
|
|
134
|
+
Props: `placeholder`, `options` (`Array<{ label: string, value: string, disabled?: boolean }>`), `value`, `onChange`, `disabled`, `className`
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### DropdownMultiple
|
|
139
|
+
|
|
140
|
+
Multi-select dropdown with checkboxes.
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
326
143
|
<DropdownMultiple
|
|
327
144
|
placeholder="Select tags"
|
|
328
145
|
options={options}
|
|
@@ -331,32 +148,18 @@ Props: `placeholder`, `value`, `onChange`, `size` (`"lg"` | `"sm"`), `className`
|
|
|
331
148
|
/>
|
|
332
149
|
```
|
|
333
150
|
|
|
334
|
-
Props
|
|
335
|
-
Props (multi): `placeholder`, `options`, `values`, `onChange`, `disabled`, `className`
|
|
336
|
-
|
|
337
|
-
Use `Dropdown` for single-select, `DropdownMultiple` for multi-select.
|
|
338
|
-
|
|
339
|
-
### OptionList
|
|
340
|
-
|
|
341
|
-
Scrollable option list for custom popups.
|
|
342
|
-
|
|
343
|
-
```tsx
|
|
344
|
-
<OptionList
|
|
345
|
-
options={[{ label: "Item A", value: "a" }, { label: "Item B", value: "b" }]}
|
|
346
|
-
selectedValue={value}
|
|
347
|
-
onSelect={setValue}
|
|
348
|
-
/>
|
|
151
|
+
Props: `placeholder`, `options`, `values`, `onChange`, `disabled`, `className`
|
|
349
152
|
|
|
350
|
-
|
|
351
|
-
```
|
|
153
|
+
---
|
|
352
154
|
|
|
353
155
|
### Tag
|
|
354
156
|
|
|
355
|
-
Compact colored label for categories
|
|
157
|
+
Compact colored label. Use for categories, statuses, filters.
|
|
356
158
|
|
|
357
159
|
Variants: `blue` | `green` | `yellow` | `red` | `gray` | `lime`
|
|
358
160
|
Sizes: `large` (default) | `small`
|
|
359
|
-
|
|
161
|
+
|
|
162
|
+
Color semantics: `green` → positive/active, `red` → error/danger, `yellow` → warning/pending, `blue` → informational, `gray` → neutral/inactive
|
|
360
163
|
|
|
361
164
|
```tsx
|
|
362
165
|
<Tag text="Active" variant="green" />
|
|
@@ -367,11 +170,11 @@ Semantics: `green` = positive, `red` = error, `yellow` = warning/pending, `blue`
|
|
|
367
170
|
|
|
368
171
|
Props: `text`, `variant`, `size`, `close`, `onClose`, `className`
|
|
369
172
|
|
|
370
|
-
|
|
173
|
+
---
|
|
371
174
|
|
|
372
175
|
### StatusTag
|
|
373
176
|
|
|
374
|
-
|
|
177
|
+
Process-flow state indicator with colored dot.
|
|
375
178
|
|
|
376
179
|
Types: `stop` | `success` | `hold` | `processing` | `error`
|
|
377
180
|
|
|
@@ -379,36 +182,41 @@ Types: `stop` | `success` | `hold` | `processing` | `error`
|
|
|
379
182
|
<StatusTag type="success" />
|
|
380
183
|
<StatusTag type="processing" text="In progress" />
|
|
381
184
|
<StatusTag type="hold" />
|
|
185
|
+
<StatusTag type="error" />
|
|
382
186
|
```
|
|
383
187
|
|
|
384
188
|
Props: `type`, `text`, `className`
|
|
385
189
|
|
|
386
|
-
Use `StatusTag` for
|
|
190
|
+
Use `StatusTag` for workflow states. Use `Tag` for categorical labels.
|
|
191
|
+
|
|
192
|
+
---
|
|
387
193
|
|
|
388
194
|
### Chip
|
|
389
195
|
|
|
390
|
-
Toggleable filter chip. Always use in groups of 2+.
|
|
196
|
+
Toggleable filter/selection chip. Always use in groups of 2+.
|
|
391
197
|
|
|
392
198
|
Types: `single` (default) | `multiple`
|
|
393
199
|
Sizes: `large` | `medium` | `small`
|
|
394
200
|
|
|
395
201
|
```tsx
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
<Chip label="
|
|
399
|
-
<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")} />
|
|
400
207
|
</div>
|
|
401
208
|
|
|
209
|
+
{/* Multi-select */}
|
|
402
210
|
<Chip label="Design" type="multiple" selected={tags.includes("design")} onClick={() => toggle("design")} />
|
|
403
211
|
```
|
|
404
212
|
|
|
405
213
|
Props: `label`, `selected`, `onClick`, `type`, `size`, `disabled`, `className`
|
|
406
214
|
|
|
407
|
-
|
|
215
|
+
---
|
|
408
216
|
|
|
409
217
|
### TabGroup
|
|
410
218
|
|
|
411
|
-
Tabbed navigation. Always use `TabGroup
|
|
219
|
+
Tabbed navigation. Always use `TabGroup`, not bare `Tab`.
|
|
412
220
|
|
|
413
221
|
Sizes: `lg` | `md` (default) | `sm`
|
|
414
222
|
|
|
@@ -426,13 +234,15 @@ Sizes: `lg` | `md` (default) | `sm`
|
|
|
426
234
|
/>
|
|
427
235
|
```
|
|
428
236
|
|
|
429
|
-
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
|
+
---
|
|
430
240
|
|
|
431
241
|
### Card
|
|
432
242
|
|
|
433
|
-
|
|
243
|
+
Responsive event/content card.
|
|
434
244
|
|
|
435
|
-
Variants: `desktop` (308px) | `tablet` (224px) | `mobile` (163px)
|
|
245
|
+
Variants: `desktop` (308px wide) | `tablet` (224px wide) | `mobile` (163px wide)
|
|
436
246
|
tagStatus: `not-registered` | `registered` | `full`
|
|
437
247
|
|
|
438
248
|
```tsx
|
|
@@ -450,7 +260,9 @@ tagStatus: `not-registered` | `registered` | `full`
|
|
|
450
260
|
|
|
451
261
|
Props: `variant`, `title`, `date`, `time`, `location`, `count`, `tagStatus`, `image`, `className`
|
|
452
262
|
|
|
453
|
-
Match
|
|
263
|
+
Match variant to viewport: `desktop` on wide layouts, `tablet` / `mobile` on narrow.
|
|
264
|
+
|
|
265
|
+
---
|
|
454
266
|
|
|
455
267
|
### DateInput
|
|
456
268
|
|
|
@@ -467,59 +279,232 @@ Variants: `popover` (default) | `modal`
|
|
|
467
279
|
|
|
468
280
|
Props: `placeholder`, `mode`, `value`, `onChange`, `variant`, `disabled`, `className`
|
|
469
281
|
|
|
470
|
-
|
|
282
|
+
---
|
|
471
283
|
|
|
472
284
|
### TimeInput
|
|
473
285
|
|
|
474
286
|
24-hour time picker.
|
|
475
287
|
|
|
288
|
+
Modes: `single` | `range`
|
|
289
|
+
|
|
476
290
|
```tsx
|
|
477
291
|
<TimeInput placeholder="Start time" value={time} onChange={setTime} />
|
|
478
292
|
<TimeInput mode="range" value={timeRange} onChange={setTimeRange} />
|
|
479
293
|
```
|
|
480
294
|
|
|
481
|
-
Props: `placeholder`, `mode
|
|
295
|
+
Props: `placeholder`, `mode`, `value`, `onChange`, `disabled`, `className`
|
|
482
296
|
|
|
483
297
|
---
|
|
484
298
|
|
|
485
|
-
|
|
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`
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Design Tokens
|
|
486
324
|
|
|
487
325
|
All tokens are CSS custom properties. Override after the stylesheet import:
|
|
488
326
|
|
|
489
327
|
```css
|
|
490
328
|
:root {
|
|
491
|
-
--primary-action: #7c3aed;
|
|
329
|
+
--primary-action: #7c3aed; /* brand color */
|
|
492
330
|
--primary-action-hover: #6d28d9;
|
|
493
331
|
--primary-action-active: #5b21b6;
|
|
494
|
-
--font-sans: "Inter", sans-serif;
|
|
495
|
-
--radius: 8px;
|
|
332
|
+
--font-sans: "Inter", sans-serif; /* override library default (Noto Sans Thai) */
|
|
333
|
+
--radius: 8px; /* border radius base */
|
|
496
334
|
}
|
|
497
335
|
```
|
|
498
336
|
|
|
499
|
-
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)
|
|
346
|
+
|
|
347
|
+
---
|
|
500
348
|
|
|
501
|
-
|
|
349
|
+
## Dark Mode
|
|
502
350
|
|
|
503
|
-
Add `.dark` to `<html>`
|
|
351
|
+
Add `.dark` to `<html>` or any ancestor:
|
|
504
352
|
|
|
505
353
|
```tsx
|
|
354
|
+
// Next.js
|
|
355
|
+
<html className={isDark ? "dark" : ""}>
|
|
356
|
+
|
|
357
|
+
// Vite
|
|
506
358
|
document.documentElement.classList.toggle("dark", isDark)
|
|
507
359
|
```
|
|
508
360
|
|
|
509
|
-
|
|
361
|
+
All components adapt automatically.
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## TypeScript
|
|
510
366
|
|
|
511
367
|
```ts
|
|
512
|
-
import type { ButtonVariant, ButtonSize, TagVariant, ChipSize
|
|
368
|
+
import type { ButtonVariant, ButtonSize, TagVariant, ChipSize } from "@sarunyu/system-one"
|
|
513
369
|
```
|
|
514
370
|
|
|
515
371
|
---
|
|
516
372
|
|
|
517
|
-
##
|
|
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.
|
|
518
504
|
|
|
519
|
-
|
|
520
|
-
2. Every UI element that matches a library component (Button, Input, Tag, etc.) uses that component — not raw HTML?
|
|
521
|
-
3. All colors use `var(--...)` tokens, not Tailwind literal color utilities (`bg-blue-500`, `text-gray-900`, etc.)?
|
|
522
|
-
4. Only one `Button variant="primary"` per context?
|
|
523
|
-
5. h1 / h2 / h3 use `text-[var(--foreground)]` (or `text-[var(--on-primary-action)]` on dark surfaces)?
|
|
505
|
+
### Chip Rules
|
|
524
506
|
|
|
525
|
-
|
|
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.
|