@sarunyu/system-one 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.cjs +4 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/src/components/tab.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/llms.txt +632 -0
- package/package.json +3 -2
package/llms.txt
ADDED
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
# @sarunyu/system-one
|
|
2
|
+
|
|
3
|
+
React component library. Tailwind CSS v4 + CSS custom properties. 13 production-ready components. Built for AI-powered generation tools (v0, Lovable, Figma Make).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @sarunyu/system-one
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup by Platform
|
|
12
|
+
|
|
13
|
+
### v0 / Next.js (App Router)
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
// app/layout.tsx
|
|
17
|
+
import "@sarunyu/system-one/styles.css"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Components are pre-annotated with `"use client"`. Safe to import anywhere — Server Components, Client Components, pages.
|
|
21
|
+
|
|
22
|
+
### Next.js (Pages Router)
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
// pages/_app.tsx
|
|
26
|
+
import "@sarunyu/system-one/styles.css"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Figma Make / Lovable / Vite
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
// src/main.tsx
|
|
33
|
+
import "@sarunyu/system-one/styles.css"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Import
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
import {
|
|
40
|
+
Button, Input, TextArea, SearchInput,
|
|
41
|
+
Dropdown, DropdownMultiple, OptionList,
|
|
42
|
+
Tag, StatusTag, Chip,
|
|
43
|
+
Tab, TabGroup,
|
|
44
|
+
Card,
|
|
45
|
+
DateInput, TimeInput,
|
|
46
|
+
cn
|
|
47
|
+
} from "@sarunyu/system-one"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Components
|
|
53
|
+
|
|
54
|
+
### Button
|
|
55
|
+
|
|
56
|
+
Variants: `primary` | `outline` | `plain` | `outline-black` | `plain-black`
|
|
57
|
+
Label sizes: `xs` | `sm` | `md` | `lg` | `xl`
|
|
58
|
+
Icon-only sizes: `icon-xs` | `icon-sm` | `icon-md` | `icon-lg` | `icon-xl`
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
<Button variant="primary" size="md">Submit</Button>
|
|
62
|
+
<Button variant="outline" size="md" onClick={fn}>Cancel</Button>
|
|
63
|
+
<Button variant="plain" size="sm">Learn more</Button>
|
|
64
|
+
<Button variant="primary" size="md" leftIcon={<Icon />}>Add item</Button>
|
|
65
|
+
<Button variant="outline" size="md" rightIcon={<Icon />}>Continue</Button>
|
|
66
|
+
<Button size="icon-md" aria-label="Settings"><GearIcon /></Button>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Props: `variant`, `size`, `leftIcon`, `rightIcon`, `disabled`, `onClick`, `className`, `children`
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
### Input
|
|
74
|
+
|
|
75
|
+
Floating-label text input with validation states.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
<Input placeholder="Email" value={email} onChange={setEmail} />
|
|
79
|
+
<Input placeholder="Amount" unit="THB" />
|
|
80
|
+
<Input placeholder="Password" type="password" />
|
|
81
|
+
<Input placeholder="Bio" showCount maxCount={160} />
|
|
82
|
+
<Input forceState="error" errorMessage="This field is required" placeholder="Email" />
|
|
83
|
+
<Input forceState="disabled" placeholder="Read only" />
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Props: `placeholder`, `value`, `onChange`, `type`, `unit`, `showCount`, `maxCount`, `forceState` (`"default"` | `"focus"` | `"error"` | `"disabled"`), `errorMessage`, `required`, `className`
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
### TextArea
|
|
91
|
+
|
|
92
|
+
Multi-line input. API mirrors Input.
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<TextArea placeholder="Description" value={text} onChange={setText} />
|
|
96
|
+
<TextArea placeholder="Tweet" showCount maxCount={280} />
|
|
97
|
+
<TextArea forceState="error" errorMessage="Required" placeholder="Notes" />
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Props: `placeholder`, `value`, `onChange`, `showCount`, `maxCount`, `forceState`, `errorMessage`, `required`, `className`
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
### SearchInput
|
|
105
|
+
|
|
106
|
+
Search field with icon and clear button.
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
<SearchInput placeholder="Search events..." value={q} onChange={setQ} />
|
|
110
|
+
<SearchInput size="sm" placeholder="Filter by name..." />
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Props: `placeholder`, `value`, `onChange`, `size` (`"lg"` | `"sm"`), `className`
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
### Dropdown
|
|
118
|
+
|
|
119
|
+
Single-select dropdown.
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
<Dropdown
|
|
123
|
+
placeholder="Select category"
|
|
124
|
+
options={[
|
|
125
|
+
{ label: "Option A", value: "a" },
|
|
126
|
+
{ label: "Option B", value: "b" },
|
|
127
|
+
{ label: "Disabled", value: "c", disabled: true },
|
|
128
|
+
]}
|
|
129
|
+
value={selected}
|
|
130
|
+
onChange={setSelected}
|
|
131
|
+
/>
|
|
132
|
+
```
|
|
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
|
|
143
|
+
<DropdownMultiple
|
|
144
|
+
placeholder="Select tags"
|
|
145
|
+
options={options}
|
|
146
|
+
values={selected}
|
|
147
|
+
onChange={setSelected}
|
|
148
|
+
/>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Props: `placeholder`, `options`, `values`, `onChange`, `disabled`, `className`
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
### Tag
|
|
156
|
+
|
|
157
|
+
Compact colored label. Use for categories, statuses, filters.
|
|
158
|
+
|
|
159
|
+
Variants: `blue` | `green` | `yellow` | `red` | `gray` | `lime`
|
|
160
|
+
Sizes: `large` (default) | `small`
|
|
161
|
+
|
|
162
|
+
Color semantics: `green` → positive/active, `red` → error/danger, `yellow` → warning/pending, `blue` → informational, `gray` → neutral/inactive
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
<Tag text="Active" variant="green" />
|
|
166
|
+
<Tag text="Draft" variant="yellow" />
|
|
167
|
+
<Tag text="Error" variant="red" size="small" />
|
|
168
|
+
<Tag text="Filter" close onClose={fn} />
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Props: `text`, `variant`, `size`, `close`, `onClose`, `className`
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
### StatusTag
|
|
176
|
+
|
|
177
|
+
Process-flow state indicator with colored dot.
|
|
178
|
+
|
|
179
|
+
Types: `stop` | `success` | `hold` | `processing` | `error`
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
<StatusTag type="success" />
|
|
183
|
+
<StatusTag type="processing" text="In progress" />
|
|
184
|
+
<StatusTag type="hold" />
|
|
185
|
+
<StatusTag type="error" />
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Props: `type`, `text`, `className`
|
|
189
|
+
|
|
190
|
+
Use `StatusTag` for workflow states. Use `Tag` for categorical labels.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
### Chip
|
|
195
|
+
|
|
196
|
+
Toggleable filter/selection chip. Always use in groups of 2+.
|
|
197
|
+
|
|
198
|
+
Types: `single` (default) | `multiple`
|
|
199
|
+
Sizes: `large` | `medium` | `small`
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
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")} />
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
{/* Multi-select */}
|
|
210
|
+
<Chip label="Design" type="multiple" selected={tags.includes("design")} onClick={() => toggle("design")} />
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Props: `label`, `selected`, `onClick`, `type`, `size`, `disabled`, `className`
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
### TabGroup
|
|
218
|
+
|
|
219
|
+
Tabbed navigation. Always use `TabGroup`, not bare `Tab`.
|
|
220
|
+
|
|
221
|
+
Sizes: `lg` | `md` (default) | `sm`
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
<TabGroup
|
|
225
|
+
items={[
|
|
226
|
+
{ id: "overview", title: "Overview" },
|
|
227
|
+
{ id: "details", title: "Details", icon: true },
|
|
228
|
+
{ id: "history", title: "History", notification: 3 },
|
|
229
|
+
{ id: "settings", title: "Settings", disabled: true },
|
|
230
|
+
]}
|
|
231
|
+
activeId={activeTab}
|
|
232
|
+
onChange={setActiveTab}
|
|
233
|
+
size="md"
|
|
234
|
+
/>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Props: `items` (`Array<{ id: string, title: string, icon?: boolean, notification?: number, disabled?: boolean }>`), `activeId`, `onChange`, `size`, `className`
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
### Card
|
|
242
|
+
|
|
243
|
+
Responsive event/content card.
|
|
244
|
+
|
|
245
|
+
Variants: `desktop` (308px wide) | `tablet` (224px wide) | `mobile` (163px wide)
|
|
246
|
+
tagStatus: `not-registered` | `registered` | `full`
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
<Card
|
|
250
|
+
variant="desktop"
|
|
251
|
+
title="Annual Conference 2024"
|
|
252
|
+
date="Jun 23, 2024"
|
|
253
|
+
time="08:30 - 12:00"
|
|
254
|
+
location="Main Hall, Floor 7"
|
|
255
|
+
count="150/200"
|
|
256
|
+
tagStatus="registered"
|
|
257
|
+
image="/banner.jpg"
|
|
258
|
+
/>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Props: `variant`, `title`, `date`, `time`, `location`, `count`, `tagStatus`, `image`, `className`
|
|
262
|
+
|
|
263
|
+
Match variant to viewport: `desktop` on wide layouts, `tablet` / `mobile` on narrow.
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
### DateInput
|
|
268
|
+
|
|
269
|
+
Calendar date picker.
|
|
270
|
+
|
|
271
|
+
Modes: `single` | `range` | `multiple`
|
|
272
|
+
Variants: `popover` (default) | `modal`
|
|
273
|
+
|
|
274
|
+
```tsx
|
|
275
|
+
<DateInput placeholder="Select date" mode="single" value={date} onChange={setDate} />
|
|
276
|
+
<DateInput mode="range" value={range} onChange={setRange} />
|
|
277
|
+
<DateInput mode="multiple" value={dates} onChange={setDates} />
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Props: `placeholder`, `mode`, `value`, `onChange`, `variant`, `disabled`, `className`
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
### TimeInput
|
|
285
|
+
|
|
286
|
+
24-hour time picker.
|
|
287
|
+
|
|
288
|
+
Modes: `single` | `range`
|
|
289
|
+
|
|
290
|
+
```tsx
|
|
291
|
+
<TimeInput placeholder="Start time" value={time} onChange={setTime} />
|
|
292
|
+
<TimeInput mode="range" value={timeRange} onChange={setTimeRange} />
|
|
293
|
+
```
|
|
294
|
+
|
|
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`
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Design Tokens
|
|
324
|
+
|
|
325
|
+
All tokens are CSS custom properties. Override after the stylesheet import:
|
|
326
|
+
|
|
327
|
+
```css
|
|
328
|
+
:root {
|
|
329
|
+
--primary-action: #7c3aed; /* brand color */
|
|
330
|
+
--primary-action-hover: #6d28d9;
|
|
331
|
+
--primary-action-active: #5b21b6;
|
|
332
|
+
--font-sans: "Inter", sans-serif; /* override library default (Noto Sans Thai) */
|
|
333
|
+
--radius: 8px; /* border radius base */
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
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
|
+
---
|
|
348
|
+
|
|
349
|
+
## Dark Mode
|
|
350
|
+
|
|
351
|
+
Add `.dark` to `<html>` or any ancestor:
|
|
352
|
+
|
|
353
|
+
```tsx
|
|
354
|
+
// Next.js
|
|
355
|
+
<html className={isDark ? "dark" : ""}>
|
|
356
|
+
|
|
357
|
+
// Vite
|
|
358
|
+
document.documentElement.classList.toggle("dark", isDark)
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
All components adapt automatically.
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## TypeScript
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
import type { ButtonVariant, ButtonSize, TagVariant, ChipSize } from "@sarunyu/system-one"
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Page Layout Patterns
|
|
374
|
+
|
|
375
|
+
**Components alone do not make a page.** The library styles components but does **not** style page layout. Without the rules below, AI tools produce pages where everything sits flush against neighbors and the result looks broken. Apply these defaults on every page you generate.
|
|
376
|
+
|
|
377
|
+
### Spacing scale
|
|
378
|
+
|
|
379
|
+
Use Tailwind spacing (1 unit = 4px). Pick from these standard gaps — do not invent arbitrary values.
|
|
380
|
+
|
|
381
|
+
| Purpose | Class | Pixels |
|
|
382
|
+
|---|---|---|
|
|
383
|
+
| Between label and input | `gap-1` or `gap-2` | 4–8px |
|
|
384
|
+
| Between fields in a form | `gap-4` | 16px |
|
|
385
|
+
| Between related elements (button row, chip row) | `gap-2` or `gap-3` | 8–12px |
|
|
386
|
+
| Between a heading and its content | `mb-4` or `mb-6` | 16–24px |
|
|
387
|
+
| Between paragraphs | `gap-3` or `gap-4` | 12–16px |
|
|
388
|
+
| Between subsections of a page | `gap-8` or `gap-10` | 32–40px |
|
|
389
|
+
| Between top-level page sections | `gap-12` or `gap-16` | 48–64px |
|
|
390
|
+
| Section vertical padding (hero, feature blocks) | `py-16` or `py-20` | 64–80px |
|
|
391
|
+
|
|
392
|
+
### Page container
|
|
393
|
+
|
|
394
|
+
Every page needs a horizontal container. Default pattern:
|
|
395
|
+
|
|
396
|
+
```tsx
|
|
397
|
+
<main className="mx-auto w-full max-w-[1200px] px-6 md:px-8 py-10">
|
|
398
|
+
{/* page content */}
|
|
399
|
+
</main>
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
- `max-w-[1200px]` for wide apps/marketing. Use `max-w-[960px]` for content-heavy, `max-w-[640px]` for forms/auth.
|
|
403
|
+
- Always pair `mx-auto` with a `max-w-*` — otherwise content stretches edge-to-edge on large screens.
|
|
404
|
+
- `px-6 md:px-8` keeps content off the viewport edge on mobile.
|
|
405
|
+
|
|
406
|
+
### Section rhythm
|
|
407
|
+
|
|
408
|
+
Stack page sections vertically with `flex flex-col gap-12` (or `gap-16` for marketing pages). Never let sections touch.
|
|
409
|
+
|
|
410
|
+
```tsx
|
|
411
|
+
<main className="mx-auto w-full max-w-[1200px] px-6 py-10 flex flex-col gap-12">
|
|
412
|
+
<section>{/* hero */}</section>
|
|
413
|
+
<section>{/* features */}</section>
|
|
414
|
+
<section>{/* cta */}</section>
|
|
415
|
+
</main>
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Typography
|
|
419
|
+
|
|
420
|
+
`h1` / `h2` / `h3` / `h4` are already styled by the library (size + weight + line-height). Just use them — do **not** add `text-2xl font-medium` manually.
|
|
421
|
+
|
|
422
|
+
- `h1` = page title (one per page)
|
|
423
|
+
- `h2` = section heading
|
|
424
|
+
- `h3` = subsection heading
|
|
425
|
+
- `h4` = minor heading / card title
|
|
426
|
+
- Body text = default (no class needed) or `text-muted-foreground` for secondary
|
|
427
|
+
|
|
428
|
+
Standard heading + content block:
|
|
429
|
+
|
|
430
|
+
```tsx
|
|
431
|
+
<section className="flex flex-col gap-6">
|
|
432
|
+
<div className="flex flex-col gap-2">
|
|
433
|
+
<h2>Section title</h2>
|
|
434
|
+
<p className="text-muted-foreground">Short description that explains what this section does.</p>
|
|
435
|
+
</div>
|
|
436
|
+
{/* content */}
|
|
437
|
+
</section>
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### Form layout
|
|
441
|
+
|
|
442
|
+
Vertical stack. Never put inputs side-by-side unless they are logically paired (first/last name, date range).
|
|
443
|
+
|
|
444
|
+
```tsx
|
|
445
|
+
<form className="flex flex-col gap-4 max-w-[480px]">
|
|
446
|
+
<Input placeholder="Email" value={email} onChange={setEmail} />
|
|
447
|
+
<Input placeholder="Password" type="password" value={pw} onChange={setPw} />
|
|
448
|
+
<div className="flex gap-3 pt-2">
|
|
449
|
+
<Button variant="primary" size="md">Sign in</Button>
|
|
450
|
+
<Button variant="outline" size="md">Cancel</Button>
|
|
451
|
+
</div>
|
|
452
|
+
</form>
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Paired inputs:
|
|
456
|
+
|
|
457
|
+
```tsx
|
|
458
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
459
|
+
<Input placeholder="First name" />
|
|
460
|
+
<Input placeholder="Last name" />
|
|
461
|
+
</div>
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Card grid
|
|
465
|
+
|
|
466
|
+
Cards in a responsive grid. Always gap at least `gap-6` between cards.
|
|
467
|
+
|
|
468
|
+
```tsx
|
|
469
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
470
|
+
<Card variant="desktop" title="..." date="..." time="..." location="..." tagStatus="registered" />
|
|
471
|
+
<Card variant="desktop" title="..." date="..." time="..." location="..." tagStatus="not-registered" />
|
|
472
|
+
<Card variant="desktop" title="..." date="..." time="..." location="..." tagStatus="full" />
|
|
473
|
+
</div>
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Toolbar / filter row
|
|
477
|
+
|
|
478
|
+
Chips, search, dropdowns above a list. Use `flex flex-wrap gap-3 items-center`.
|
|
479
|
+
|
|
480
|
+
```tsx
|
|
481
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
482
|
+
<SearchInput placeholder="Search..." value={q} onChange={setQ} className="max-w-[320px]" />
|
|
483
|
+
<Chip label="All" selected={f === "all"} onClick={() => setF("all")} />
|
|
484
|
+
<Chip label="Active" selected={f === "active"} onClick={() => setF("active")} />
|
|
485
|
+
<Chip label="Archived" selected={f === "archived"} onClick={() => setF("archived")} />
|
|
486
|
+
</div>
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### Full page template
|
|
490
|
+
|
|
491
|
+
Copy-paste starting point. Replace content, keep the structure.
|
|
492
|
+
|
|
493
|
+
```tsx
|
|
494
|
+
export default function Page() {
|
|
495
|
+
return (
|
|
496
|
+
<main className="mx-auto w-full max-w-[1200px] px-6 md:px-8 py-10 flex flex-col gap-12">
|
|
497
|
+
{/* Page header */}
|
|
498
|
+
<header className="flex flex-col gap-2">
|
|
499
|
+
<h1>Events</h1>
|
|
500
|
+
<p className="text-muted-foreground">Browse and register for upcoming events.</p>
|
|
501
|
+
</header>
|
|
502
|
+
|
|
503
|
+
{/* Toolbar */}
|
|
504
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
505
|
+
<SearchInput placeholder="Search events..." value={q} onChange={setQ} className="max-w-[320px]" />
|
|
506
|
+
<Chip label="All" selected={filter === "all"} onClick={() => setFilter("all")} />
|
|
507
|
+
<Chip label="This week" selected={filter === "week"} onClick={() => setFilter("week")} />
|
|
508
|
+
</div>
|
|
509
|
+
|
|
510
|
+
{/* Content */}
|
|
511
|
+
<section className="flex flex-col gap-6">
|
|
512
|
+
<h2>Upcoming</h2>
|
|
513
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
514
|
+
{events.map((e) => <Card key={e.id} variant="desktop" {...e} />)}
|
|
515
|
+
</div>
|
|
516
|
+
</section>
|
|
517
|
+
</main>
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
### Layout rules (do / don't)
|
|
523
|
+
|
|
524
|
+
- **Do** wrap every page in a `max-w-* mx-auto` container with horizontal padding.
|
|
525
|
+
- **Do** use `flex flex-col gap-*` to stack sections — never rely on default margins.
|
|
526
|
+
- **Do** group a heading with its content in a smaller `gap-2`/`gap-4` block, then separate groups with `gap-8`+.
|
|
527
|
+
- **Don't** place components directly adjacent without a `gap-*` or padding parent.
|
|
528
|
+
- **Don't** mix arbitrary spacing values (`gap-[13px]`, `mt-7`) — stick to `2 / 3 / 4 / 6 / 8 / 10 / 12 / 16`.
|
|
529
|
+
- **Don't** full-bleed text content on desktop — cap reading widths at `max-w-[720px]` for paragraphs.
|
|
530
|
+
- **Don't** override heading sizes with utility classes — `h1`–`h4` are already sized by the library.
|
|
531
|
+
|
|
532
|
+
---
|
|
533
|
+
|
|
534
|
+
## Notes
|
|
535
|
+
|
|
536
|
+
- No provider or wrapper needed — just import the stylesheet and use components
|
|
537
|
+
- The library ships pre-built CSS — no Tailwind config required in the consuming project
|
|
538
|
+
- If the project already uses Tailwind and you see font differences, override `--font-sans` in your CSS
|
|
539
|
+
- CSS custom properties can be referenced in Tailwind classes: `bg-[var(--primary-action)]`
|
|
540
|
+
|
|
541
|
+
---
|
|
542
|
+
|
|
543
|
+
## Component Usage Guide
|
|
544
|
+
|
|
545
|
+
### Button
|
|
546
|
+
|
|
547
|
+
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.
|
|
548
|
+
|
|
549
|
+
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.
|
|
550
|
+
|
|
551
|
+
Sizes: `base` recommended for Desktop. `xl` recommended for Mobile.
|
|
552
|
+
|
|
553
|
+
States: Default, Hover, Press, Disabled.
|
|
554
|
+
|
|
555
|
+
### Input
|
|
556
|
+
|
|
557
|
+
Floating Label Input is a single-line or multi-line field with a label that floats inside.
|
|
558
|
+
|
|
559
|
+
Types: `Input` — single-line, general text/numeric. `TextArea` — multi-line, for comments or addresses. `Dropdown` — predefined list selection, displays as OptionList.
|
|
560
|
+
|
|
561
|
+
States: Default, Focus, Error, Disabled.
|
|
562
|
+
|
|
563
|
+
### Tab
|
|
564
|
+
|
|
565
|
+
Tabs allow users to switch between different content sections within the same page.
|
|
566
|
+
|
|
567
|
+
Types: Default (text only), Icon (with icon), Notification (with badge).
|
|
568
|
+
|
|
569
|
+
States: Default, Active (selected), Disabled.
|
|
570
|
+
|
|
571
|
+
Sizes: `lg` (default), `md`, `sm`. Always use the same size within one tab group.
|
|
572
|
+
|
|
573
|
+
### Tag
|
|
574
|
+
|
|
575
|
+
Tags display categories, types, or short metadata. Status Tags communicate process state.
|
|
576
|
+
|
|
577
|
+
Tag types: Default (text only), Icon (with icon), Remove (dismissible).
|
|
578
|
+
Tag states: Default, Hover/Press, Disabled.
|
|
579
|
+
Tag sizes: Large, Small.
|
|
580
|
+
|
|
581
|
+
Status Tag variants: `success` — process completed. `processing` — in progress. `hold` — temporarily paused. `stop` — stopped. `error` — failed.
|
|
582
|
+
|
|
583
|
+
### Chip
|
|
584
|
+
|
|
585
|
+
Chips are toggleable filter/selection elements. Always use in groups of 2+.
|
|
586
|
+
|
|
587
|
+
Types: `single` — one selection at a time. `multiple` — multiple selections simultaneously.
|
|
588
|
+
|
|
589
|
+
States: Default, Active, Disabled (Default), Disabled (Active).
|
|
590
|
+
|
|
591
|
+
Sizes: `large`, `medium`, `small`.
|
|
592
|
+
|
|
593
|
+
---
|
|
594
|
+
|
|
595
|
+
## Design Rules
|
|
596
|
+
|
|
597
|
+
### Button Rules
|
|
598
|
+
|
|
599
|
+
- Maximum width is 343px (`max-w-[343px]`). Never remove or override. Do not detach the component.
|
|
600
|
+
- Do not manually recreate or modify padding (`py-[10px] px-[16px]`) or border-radius (`rounded-[8px]`).
|
|
601
|
+
- Use Primary only once per context. Never place two Primary buttons side-by-side. Use Outline/Secondary for supporting actions.
|
|
602
|
+
- Hug width is correct for short labels only. For long labels, use Fill width or set explicit max-w-[343px].
|
|
603
|
+
|
|
604
|
+
### Input Rules
|
|
605
|
+
|
|
606
|
+
- Never override input border or background colors manually — use built-in state variants (Default, Focus, Error, Disabled).
|
|
607
|
+
- Do not recreate input styles manually or adjust gap/height outside the component's tokens.
|
|
608
|
+
- Use Dropdown Tags only for multi-select. For single-select, use standard Dropdown.
|
|
609
|
+
- Keep labelText short enough to fit on one line. Long labels break the floating label layout.
|
|
610
|
+
- Do not show Helper Text and Error Message simultaneously. Error state replaces helper text.
|
|
611
|
+
- Use Option List only when there are multiple selectable values.
|
|
612
|
+
- Always follow the system date format (DD MMM YYYY). Do not mix Thai month names with C.E. year or reorder day/month/year.
|
|
613
|
+
|
|
614
|
+
### Tab Rules
|
|
615
|
+
|
|
616
|
+
- Do not override padding, gap, border-radius, element order, or colors inside the tab bar.
|
|
617
|
+
- All tabs in one group must use the same size prop. Never mix sizes.
|
|
618
|
+
- Do not add extra gap or margin between tabs — use built-in spacing tokens only.
|
|
619
|
+
|
|
620
|
+
### Tag Rules
|
|
621
|
+
|
|
622
|
+
- Do not override height, padding, or layout of Tag or StatusTag.
|
|
623
|
+
- All tags in the same context must use the same size.
|
|
624
|
+
- Do not reorder internal elements (icon, label, badge) inside Tag or StatusTag.
|
|
625
|
+
- Always use the correct StatusTag variant for its semantic meaning. Never override the color.
|
|
626
|
+
|
|
627
|
+
### Chip Rules
|
|
628
|
+
|
|
629
|
+
- Keep chip labels short and single-purpose. Long labels cause overflow.
|
|
630
|
+
- Do not override padding, gap, radius, height, element order, or colors.
|
|
631
|
+
- All chips in the same group must share the same size and spacing.
|
|
632
|
+
- 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": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
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": [
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"*.css"
|
|
39
39
|
],
|
|
40
40
|
"files": [
|
|
41
|
-
"dist"
|
|
41
|
+
"dist",
|
|
42
|
+
"llms.txt"
|
|
42
43
|
],
|
|
43
44
|
"scripts": {
|
|
44
45
|
"dev": "vite",
|