@sarunyu/system-one 4.9.6 → 4.9.8

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/llms.txt CHANGED
@@ -330,15 +330,13 @@ Single-select dropdown.
330
330
  options={[
331
331
  { label: "Option A", value: "a" },
332
332
  { label: "Option B", value: "b" },
333
- { label: "Disabled", value: "c", disabled: true },
334
333
  ]}
335
334
  value={selected}
336
335
  onChange={setSelected}
337
336
  />
338
337
  ```
339
338
 
340
- Props: `placeholder`, `options: { label, value, disabled? }[]`, `value`, `onChange(value)`,
341
- `disabled`, `className`.
339
+ Props: `placeholder`, `label`, `required`, `forceState` (`"default"` | `"focus"` | `"error"` | `"disabled"`), `errorMessage`, `helperText`, `options: { label, value }[]`, `value`, `onChange(value)`, `className`. To disable, pass `forceState="disabled"` — there is no top-level `disabled` prop.
342
340
 
343
341
  ### DropdownMultiple
344
342
 
@@ -348,24 +346,24 @@ Multi-select dropdown with checkboxes.
348
346
  <DropdownMultiple
349
347
  placeholder="Select tags"
350
348
  options={tagOptions}
351
- values={selected}
349
+ value={selected}
352
350
  onChange={setSelected}
353
351
  />
354
352
  ```
355
353
 
356
- Props: `placeholder`, `options`, `values: string[]`, `onChange(values)`, `disabled`, `className`.
354
+ Props: `placeholder`, `label`, `required`, `forceState`, `errorMessage`, `helperText`, `options`, `value: string[]`, `onChange(value: string[])`, `className`. The controlled prop is `value` (not `values`). To disable, use `forceState="disabled"`.
357
355
 
358
356
  ---
359
357
 
360
358
  ### Checkbox
361
359
 
362
360
  ```tsx
363
- <Checkbox checked={agreed} onCheckedChange={setAgreed} label="I agree to the terms" />
364
- <Checkbox checked="indeterminate" onCheckedChange={setAll} label="Select all" />
365
- <Checkbox checked={sub} onCheckedChange={setSub} label="Subscribe" disabled />
361
+ <Checkbox checked={agreed} onChange={setAgreed} label="I agree to the terms" />
362
+ <Checkbox checked="indeterminate" onChange={setAll} label="Select all" />
363
+ <Checkbox checked={sub} onChange={setSub} label="Subscribe" disabled />
366
364
  ```
367
365
 
368
- `checked` is `boolean | "indeterminate"`. Always provide `label` — don't wrap Checkbox in a `<label>`.
366
+ `checked` is `boolean | "indeterminate"`. `onChange` receives a boolean (`(next: boolean) => void`) — NOT an event, NOT `onCheckedChange`. Always provide `label` — don't wrap Checkbox in a `<label>`.
369
367
 
370
368
  ### Toggle
371
369
 
@@ -395,11 +393,13 @@ Render a group — use `name` to bind siblings.
395
393
 
396
394
  ```tsx
397
395
  <div className="flex flex-col gap-2">
398
- <Radio name="plan" value="free" checked={plan === "free"} onChange={setPlan} label="Free" />
399
- <Radio name="plan" value="pro" checked={plan === "pro"} onChange={setPlan} label="Pro" />
396
+ <Radio name="plan" value="free" checked={plan === "free"} onChange={() => setPlan("free")} label="Free" />
397
+ <Radio name="plan" value="pro" checked={plan === "pro"} onChange={() => setPlan("pro")} label="Pro" />
400
398
  </div>
401
399
  ```
402
400
 
401
+ `onChange` receives a boolean (`(next: boolean) => void`) — fires with `true` when the radio becomes selected. To track which value was picked, ignore the arg and set the value directly: `onChange={() => setPlan("pro")}`.
402
+
403
403
  ---
404
404
 
405
405
  ### Tag
@@ -412,11 +412,13 @@ type TagVariant = "blue" | "green" | "yellow" | "red" | "gray" | "lime";
412
412
 
413
413
  <Tag text="Active" variant="green" />
414
414
  <Tag text="Draft" variant="yellow" />
415
- <Tag text="Filter: Design" close onClose={remove} />
415
+ <Tag text="Filter: Design" close />
416
416
  <Tag text="Small" variant="blue" size="small" />
417
417
  ```
418
418
 
419
- Props: `text`, `variant`, `size` (`"large"` default | `"small"`), `close`, `onClose`, `className`.
419
+ Props: `text`, `variant`, `size` (`"large"` default | `"small"`), `state` (`"default"` | `"hover"` | `"disabled"`), `icon` (boolean — leading dot), `close` (boolean — visual × button only), `className`.
420
+
421
+ **Note** — `close` is purely visual; the Tag does not handle dismiss. To make a removable tag, wrap it in a clickable parent (`<button onClick={remove}><Tag close text="…" /></button>`) or use `<DropdownMultiple>`'s built-in removable chips.
420
422
 
421
423
  ### StatusTag
422
424
 
@@ -674,15 +676,33 @@ Props:
674
676
 
675
677
  ### DateInput / TimeInput
676
678
 
679
+ Modes: `"single"` (default) | `"range"`. There is **no `"multiple"` mode**.
680
+
681
+ Each mode uses different value props — single mode uses `value` + `onChange`, range mode uses `dateRange` + `onRangeChange` (or `startTime`/`endTime` + `onStartChange`/`onEndChange` for TimeInput).
682
+
677
683
  ```tsx
684
+ // DateInput — single
678
685
  <DateInput placeholder="Select date" mode="single" value={date} onChange={setDate} />
679
- <DateInput mode="range" value={range} onChange={setRange} />
680
- <DateInput mode="multiple" value={dates} onChange={setDates} />
681
686
 
687
+ // DateInput — range (different prop names!)
688
+ <DateInput mode="range" dateRange={range} onRangeChange={setRange} />
689
+
690
+ // TimeInput — single
682
691
  <TimeInput placeholder="Start time" value={time} onChange={setTime} />
683
- <TimeInput mode="range" value={timeRange} onChange={setTimeRange} />
692
+
693
+ // TimeInput — range (different prop names!)
694
+ <TimeInput
695
+ mode="range"
696
+ startTime={start}
697
+ endTime={end}
698
+ onStartChange={setStart}
699
+ onEndChange={setEnd}
700
+ />
684
701
  ```
685
702
 
703
+ DateInput types: `value: Date | undefined`, `dateRange: DateRange` (re-exported from react-day-picker — `{ from?: Date; to?: Date }`).
704
+ TimeInput types: `value: TimeValue` where `TimeValue = { hour: 0–23, minute: 0–59 }`.
705
+
686
706
  Always format `"DD MMM YYYY"` for display (system format). Don't mix Thai month names with C.E. year.
687
707
 
688
708
  ### OptionList
@@ -825,13 +845,23 @@ Centered overlay surface for confirmations, richer content, or status alerts.
825
845
  Caller owns open/close state and supplies the backdrop — `<Modal>` only renders
826
846
  the dialog panel.
827
847
 
848
+ **CRITICAL — choose the correct variant before writing any code:**
849
+
850
+ | Variant | Use when | Has children? |
851
+ |---|---|---|
852
+ | `"dialog"` | Simple confirmation: title + description text + up to 2 buttons. NO custom UI. | No |
853
+ | `"content"` | Any modal with custom content: filters, forms, inputs, lists, date pickers, etc. | Yes — required |
854
+ | `"alert"` | Status moments only: success / warning / danger with icon. | No |
855
+
856
+ **Rule: if the modal contains ANYTHING other than a title + plain description text → use `variant="content"` and pass the body as `children`. Never use `variant="dialog"` with custom UI inside.**
857
+
828
858
  ```tsx
829
859
  type ModalVariant = "dialog" | "content" | "alert";
830
860
  type ModalActionLayout = "none" | "single" | "double";
831
861
  type ModalResponsive = "mobile" | "desktop";
832
862
  type ModalAlertStatus = "warning" | "success" | "danger";
833
863
 
834
- // Dialog — short confirmation (title + text + up to 2 buttons)
864
+ // Dialog — ONLY for title + plain text + buttons. No custom children.
835
865
  <Modal
836
866
  variant="dialog"
837
867
  actionLayout="double"
@@ -844,7 +874,24 @@ type ModalAlertStatus = "warning" | "success" | "danger";
844
874
  onClose={close}
845
875
  />
846
876
 
847
- // Content — custom body (pass children)
877
+ // Content — filters, forms, inputs, any custom UI. Always pass children.
878
+ <Modal
879
+ variant="content"
880
+ actionLayout="double"
881
+ title="Filters"
882
+ primaryLabel="Apply"
883
+ secondaryLabel="Reset"
884
+ onPrimaryClick={apply}
885
+ onSecondaryClick={reset}
886
+ onClose={close}
887
+ >
888
+ <div className="flex flex-col gap-4">
889
+ <Dropdown placeholder="Category" ... />
890
+ <DateInput mode="range" ... />
891
+ </div>
892
+ </Modal>
893
+
894
+ // Content desktop — right-aligns action buttons
848
895
  <Modal variant="content" actionLayout="single" responsive="desktop" title="Edit profile" onClose={close}>
849
896
  <Input placeholder="Name" value={name} onChange={setName} />
850
897
  </Modal>
@@ -866,7 +913,9 @@ type ModalAlertStatus = "warning" | "success" | "danger";
866
913
  ```tsx
867
914
  {open ? (
868
915
  <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/30 p-4">
869
- <Modal variant="dialog" actionLayout="double" onClose={close} {...rest} />
916
+ <Modal variant="content" title="Filters" actionLayout="double" onClose={close}>
917
+ {/* your content */}
918
+ </Modal>
870
919
  </div>
871
920
  ) : null}
872
921
  ```
@@ -987,7 +1036,7 @@ export function LoginSheet({
987
1036
  <div className="flex flex-col gap-4">
988
1037
  <Input placeholder="Email" value={email} onChange={setEmail} required />
989
1038
  <Input placeholder="Password" value={pw} onChange={setPw} type="password" required />
990
- <Checkbox checked={remember} onCheckedChange={setRmb} label="Remember me" />
1039
+ <Checkbox checked={remember} onChange={setRmb} label="Remember me" />
991
1040
  <Button variant="primary" size="xl" className="w-full" onClick={() => onOpenChange(false)}>
992
1041
  Sign in
993
1042
  </Button>
@@ -1109,7 +1158,7 @@ export function LoginPage() {
1109
1158
  <div className="flex flex-col gap-4">
1110
1159
  <Input placeholder="Email" type="email" value={email} onChange={setEmail} required />
1111
1160
  <Input placeholder="Password" type="password" value={pw} onChange={setPw} required />
1112
- <Checkbox checked={remember} onCheckedChange={setRmb} label="Remember me" />
1161
+ <Checkbox checked={remember} onChange={setRmb} label="Remember me" />
1113
1162
  </div>
1114
1163
  <Button variant="primary" size="lg" className="w-full">Sign in</Button>
1115
1164
  <p className="text-center text-sm text-muted-foreground">
@@ -1199,7 +1248,7 @@ export function SettingsPage() {
1199
1248
  <DropdownMultiple
1200
1249
  placeholder="Interests"
1201
1250
  options={interests}
1202
- values={picked}
1251
+ value={picked}
1203
1252
  onChange={setPicked}
1204
1253
  />
1205
1254
  <DateInput placeholder="Birthday" mode="single" value={dob} onChange={setDob} />
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarunyu/system-one",
3
- "version": "4.9.6",
3
+ "version": "4.9.8",
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": [