@sarunyu/system-one 4.1.1 → 4.2.1
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/AGENTS.md +15 -0
- package/dist/index.cjs +364 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +366 -16
- package/dist/index.js.map +1 -1
- package/dist/src/components/bottom-sheet.d.ts +23 -0
- package/dist/src/components/bottom-sheet.d.ts.map +1 -0
- package/dist/src/components/date-input.d.ts.map +1 -1
- package/dist/src/components/modal.d.ts +23 -0
- package/dist/src/components/modal.d.ts.map +1 -0
- package/dist/src/components/time-input.d.ts.map +1 -1
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/llms.txt +214 -3
- package/package.json +1 -1
package/llms.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @sarunyu/system-one — AI usage guide
|
|
2
2
|
|
|
3
|
-
React component library. Tailwind CSS v4 + CSS custom properties.
|
|
3
|
+
React component library. Tailwind CSS v4 + CSS custom properties. 19 components.
|
|
4
4
|
Built for AI-powered UI generation (v0, Lovable, Figma Make, Cursor).
|
|
5
5
|
|
|
6
6
|
**This file is the contract.** Read it top-to-bottom before generating any screen
|
|
@@ -12,7 +12,7 @@ that uses this library. The rules are non-negotiable.
|
|
|
12
12
|
|
|
13
13
|
1. **Use library components for every element it provides.** Never recreate
|
|
14
14
|
Button, Input, Tag, Dropdown, Card, Tab, Checkbox, Radio, DateInput, TimeInput,
|
|
15
|
-
Table, SearchInput, TextArea, Chip as raw HTML.
|
|
15
|
+
Table, SearchInput, TextArea, Chip, Modal, BottomSheet as raw HTML.
|
|
16
16
|
2. **Use design-token classes for color and typography.** Never `text-blue-600`,
|
|
17
17
|
`bg-gray-100`, `text-[#3b82f6]`. The token table below is exhaustive — if a
|
|
18
18
|
color you need is not in it, use `text-foreground` / `bg-card`.
|
|
@@ -68,8 +68,10 @@ import {
|
|
|
68
68
|
Tab, TabGroup,
|
|
69
69
|
Card,
|
|
70
70
|
Table, TableRow, TableHeaderCell, TableCell,
|
|
71
|
+
// Overlay
|
|
72
|
+
Modal, BottomSheet,
|
|
71
73
|
// Utility
|
|
72
|
-
cn,
|
|
74
|
+
cn, useIsMobile,
|
|
73
75
|
} from "@sarunyu/system-one";
|
|
74
76
|
```
|
|
75
77
|
|
|
@@ -579,6 +581,213 @@ Raw option rows. Use when you need a custom dropdown or a sidebar menu — other
|
|
|
579
581
|
|
|
580
582
|
---
|
|
581
583
|
|
|
584
|
+
### Modal
|
|
585
|
+
|
|
586
|
+
Centered overlay surface for confirmations, richer content, or status alerts.
|
|
587
|
+
Caller owns open/close state and supplies the backdrop — `<Modal>` only renders
|
|
588
|
+
the dialog panel.
|
|
589
|
+
|
|
590
|
+
```tsx
|
|
591
|
+
type ModalVariant = "dialog" | "content" | "alert";
|
|
592
|
+
type ModalActionLayout = "none" | "single" | "double";
|
|
593
|
+
type ModalResponsive = "mobile" | "desktop";
|
|
594
|
+
type ModalAlertStatus = "warning" | "success" | "danger";
|
|
595
|
+
|
|
596
|
+
// Dialog — short confirmation (title + text + up to 2 buttons)
|
|
597
|
+
<Modal
|
|
598
|
+
variant="dialog"
|
|
599
|
+
actionLayout="double"
|
|
600
|
+
title="Delete item?"
|
|
601
|
+
description="This can't be undone."
|
|
602
|
+
primaryLabel="Delete"
|
|
603
|
+
secondaryLabel="Cancel"
|
|
604
|
+
onPrimaryClick={confirm}
|
|
605
|
+
onSecondaryClick={close}
|
|
606
|
+
onClose={close}
|
|
607
|
+
/>
|
|
608
|
+
|
|
609
|
+
// Content — custom body (pass children)
|
|
610
|
+
<Modal variant="content" actionLayout="single" responsive="desktop" title="Edit profile" onClose={close}>
|
|
611
|
+
<Input placeholder="Name" value={name} onChange={setName} />
|
|
612
|
+
</Modal>
|
|
613
|
+
|
|
614
|
+
// Alert — status moment (warning / success / danger)
|
|
615
|
+
<Modal
|
|
616
|
+
variant="alert"
|
|
617
|
+
alertStatus="success"
|
|
618
|
+
actionLayout="single"
|
|
619
|
+
title="Saved"
|
|
620
|
+
description="Your changes are live."
|
|
621
|
+
primaryLabel="Done"
|
|
622
|
+
onPrimaryClick={close}
|
|
623
|
+
/>
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
**Rendering it onscreen.** Wrap `<Modal>` in your own backdrop/portal:
|
|
627
|
+
|
|
628
|
+
```tsx
|
|
629
|
+
{open ? (
|
|
630
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/30 p-4">
|
|
631
|
+
<Modal variant="dialog" actionLayout="double" onClose={close} {...rest} />
|
|
632
|
+
</div>
|
|
633
|
+
) : null}
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
**Widths are fixed by variant** — `dialog` 375px, `content`/`alert` 343px.
|
|
637
|
+
Don't override `className` to enlarge them. Mobile by default; set
|
|
638
|
+
`responsive="desktop"` on `variant="content"` to right-align actions.
|
|
639
|
+
|
|
640
|
+
**Action layout** — `none` (no buttons), `single` (primary only, full width on
|
|
641
|
+
mobile), `double` (secondary + primary). Only one primary per modal.
|
|
642
|
+
|
|
643
|
+
Props: `variant`, `actionLayout`, `responsive`, `alertStatus`, `showClose`,
|
|
644
|
+
`title`, `description`, `primaryLabel`, `secondaryLabel`, `children`,
|
|
645
|
+
`onClose`, `onPrimaryClick`, `onSecondaryClick`, `className`.
|
|
646
|
+
|
|
647
|
+
---
|
|
648
|
+
|
|
649
|
+
### BottomSheet
|
|
650
|
+
|
|
651
|
+
Mobile-first bottom-anchored sheet. Built on Vaul — it ships its own backdrop,
|
|
652
|
+
drag-to-dismiss, and portal. Pass a `trigger` or control it via `open` /
|
|
653
|
+
`onOpenChange`.
|
|
654
|
+
|
|
655
|
+
```tsx
|
|
656
|
+
type BottomSheetHeaderType = "text" | "icon" | "image";
|
|
657
|
+
type BottomSheetRightSide = "icon" | "action" | "none";
|
|
658
|
+
|
|
659
|
+
// Controlled — opens from the bottom with title + close icon
|
|
660
|
+
<BottomSheet
|
|
661
|
+
open={open}
|
|
662
|
+
onOpenChange={setOpen}
|
|
663
|
+
title="Filters"
|
|
664
|
+
rightSide="icon"
|
|
665
|
+
>
|
|
666
|
+
<div className="flex flex-col gap-3">{/* filter controls */}</div>
|
|
667
|
+
</BottomSheet>
|
|
668
|
+
|
|
669
|
+
// Uncontrolled with a trigger
|
|
670
|
+
<BottomSheet
|
|
671
|
+
trigger={<Button variant="outline" size="md">Show options</Button>}
|
|
672
|
+
title="Playlist"
|
|
673
|
+
headerType="image"
|
|
674
|
+
imageSrc="/cover.jpg"
|
|
675
|
+
rightSide="action"
|
|
676
|
+
actionLabel="Save"
|
|
677
|
+
onActionClick={save}
|
|
678
|
+
>
|
|
679
|
+
<OptionList options={tracks} selectedValue={pick} onSelect={setPick} />
|
|
680
|
+
</BottomSheet>
|
|
681
|
+
|
|
682
|
+
// Content-only sheet (no header)
|
|
683
|
+
<BottomSheet open={open} onOpenChange={setOpen} showHeader={false}>
|
|
684
|
+
<p className="text-sm text-muted-foreground">Quick tip body</p>
|
|
685
|
+
</BottomSheet>
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
**Header layout**
|
|
689
|
+
- `headerType="text"` — title only.
|
|
690
|
+
- `headerType="icon"` — leading icon + title (pass `leftIcon`).
|
|
691
|
+
- `headerType="image"` — leading thumbnail + title (pass `imageSrc`).
|
|
692
|
+
|
|
693
|
+
**Right side**
|
|
694
|
+
- `rightSide="icon"` — close button, auto-wired to `DrawerClose`. Custom glyph via `rightIcon`.
|
|
695
|
+
- `rightSide="action"` — inline text button (e.g., "Save"), fires `onActionClick`.
|
|
696
|
+
- `rightSide="none"` — nothing on the right.
|
|
697
|
+
|
|
698
|
+
`showHandle` (default `true`) shows the grab handle at the top. Hide it only
|
|
699
|
+
when you want a fully formal surface. Do not render a `<BottomSheet>` on desktop
|
|
700
|
+
layouts — use `<Modal>` instead.
|
|
701
|
+
|
|
702
|
+
Props: `open`, `onOpenChange`, `trigger`, `headerType`, `showHeader`,
|
|
703
|
+
`rightSide`, `title`, `actionLabel`, `imageSrc`, `leftIcon`, `rightIcon`,
|
|
704
|
+
`onActionClick`, `showHandle`, `children`, `className`, `contentClassName`.
|
|
705
|
+
|
|
706
|
+
---
|
|
707
|
+
|
|
708
|
+
### Modal vs BottomSheet — responsive rule
|
|
709
|
+
|
|
710
|
+
**On mobile (< 768px), content-heavy / action-heavy modals MUST render as
|
|
711
|
+
`<BottomSheet>`, not `<Modal>`.** This is not optional — it is the system
|
|
712
|
+
default for every flow that wraps a form or an option list.
|
|
713
|
+
|
|
714
|
+
Mobile → `<BottomSheet>`:
|
|
715
|
+
- Login / signup / account forms
|
|
716
|
+
- Settings panels and profile editors
|
|
717
|
+
- Any multi-field form or multi-step flow
|
|
718
|
+
- Long option / picker lists (e.g. country picker, category filter)
|
|
719
|
+
- Action menus ("Share to…", "Move to…")
|
|
720
|
+
|
|
721
|
+
Mobile → `<Modal>` stays OK:
|
|
722
|
+
- `variant="alert"` (success / warning / danger notifications)
|
|
723
|
+
- Short `variant="dialog"` confirmations (title + one line of text + 1–2 buttons, no input)
|
|
724
|
+
|
|
725
|
+
Desktop (≥ 768px) → always `<Modal>`.
|
|
726
|
+
|
|
727
|
+
Use the library's `useIsMobile()` hook to branch. It ships with the same
|
|
728
|
+
768px breakpoint the library uses internally (DateInput/TimeInput already
|
|
729
|
+
swap to `<BottomSheet>` on mobile via this hook).
|
|
730
|
+
|
|
731
|
+
```tsx
|
|
732
|
+
import { useState } from "react";
|
|
733
|
+
import {
|
|
734
|
+
useIsMobile,
|
|
735
|
+
Modal, BottomSheet,
|
|
736
|
+
Input, Button, Checkbox,
|
|
737
|
+
} from "@sarunyu/system-one";
|
|
738
|
+
|
|
739
|
+
export function LoginSheet({
|
|
740
|
+
open,
|
|
741
|
+
onOpenChange,
|
|
742
|
+
}: { open: boolean; onOpenChange: (open: boolean) => void }) {
|
|
743
|
+
const isMobile = useIsMobile();
|
|
744
|
+
const [email, setEmail] = useState("");
|
|
745
|
+
const [pw, setPw] = useState("");
|
|
746
|
+
const [remember, setRmb] = useState(false);
|
|
747
|
+
|
|
748
|
+
const body = (
|
|
749
|
+
<div className="flex flex-col gap-4">
|
|
750
|
+
<Input placeholder="Email" value={email} onChange={setEmail} required />
|
|
751
|
+
<Input placeholder="Password" value={pw} onChange={setPw} type="password" required />
|
|
752
|
+
<Checkbox checked={remember} onCheckedChange={setRmb} label="Remember me" />
|
|
753
|
+
<Button variant="primary" size="xl" className="w-full" onClick={() => onOpenChange(false)}>
|
|
754
|
+
Sign in
|
|
755
|
+
</Button>
|
|
756
|
+
</div>
|
|
757
|
+
);
|
|
758
|
+
|
|
759
|
+
if (isMobile) {
|
|
760
|
+
return (
|
|
761
|
+
<BottomSheet open={open} onOpenChange={onOpenChange} title="Sign in" rightSide="icon">
|
|
762
|
+
{body}
|
|
763
|
+
</BottomSheet>
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
if (!open) return null;
|
|
768
|
+
return (
|
|
769
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/30 p-4">
|
|
770
|
+
<Modal
|
|
771
|
+
variant="content"
|
|
772
|
+
responsive="desktop"
|
|
773
|
+
title="Sign in"
|
|
774
|
+
onClose={() => onOpenChange(false)}
|
|
775
|
+
>
|
|
776
|
+
{body}
|
|
777
|
+
</Modal>
|
|
778
|
+
</div>
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
**Key points in the pattern**
|
|
784
|
+
- Extract the body into a single `const body = (...)` so both branches share it. Do not duplicate the form.
|
|
785
|
+
- `useIsMobile()` returns `false` on the server / first paint — the initial render shows the `<Modal>` branch. That is correct; the component is portalled and non-interactive until hydrated.
|
|
786
|
+
- `onOpenChange(false)` closes both branches. Do not hand-roll separate close handlers.
|
|
787
|
+
- Do NOT build a custom `<ResponsiveModal>` wrapper component. Branching inline is the sanctioned pattern — wrappers hide the decision and lead to mis-use.
|
|
788
|
+
|
|
789
|
+
---
|
|
790
|
+
|
|
582
791
|
## Layout — you design it
|
|
583
792
|
|
|
584
793
|
The library ships zero layout components. Compose page structure with plain
|
|
@@ -632,6 +841,8 @@ Tailwind. Example scaffolds:
|
|
|
632
841
|
| `bg-primary-action` | `bg-blue-600` / `bg-[#3b82f6]` |
|
|
633
842
|
| `<h1>Title</h1>` | `<h1 className="text-3xl font-bold">Title</h1>` |
|
|
634
843
|
| `<div className="flex flex-col gap-6">` | `import { Stack } from "@sarunyu/system-one"` |
|
|
844
|
+
| `<Modal variant="dialog" …>` inside your own `fixed inset-0` backdrop | `<div className="fixed inset-0 bg-white rounded p-6">…` |
|
|
845
|
+
| `<BottomSheet open={open} onOpenChange={setOpen}>` | Hand-rolled sheet with `translate-y` + overlay divs |
|
|
635
846
|
| One `variant="primary"` per context | Two primary buttons side-by-side |
|
|
636
847
|
| `onChange={setValue}` (value, not event) | `onChange={e => setValue(e.target.value)}` |
|
|
637
848
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sarunyu/system-one",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.1",
|
|
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": [
|