@devalok/shilp-sutra 0.40.1 → 0.42.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/AGENTS.md +23 -1
- package/BREAKING.json +66 -0
- package/BREAKING.schema.json +184 -0
- package/MIGRATION.md +16 -0
- package/docs/recipes/install-next-app-router.md +54 -14
- package/docs/recipes/upgrading.md +19 -0
- package/llms-full.txt +1 -1
- package/llms-quick.txt +3 -1
- package/llms.txt +1 -0
- package/make-kit/Guidelines.md +71 -0
- package/make-kit/components/badge.md +162 -0
- package/make-kit/components/button.md +125 -0
- package/make-kit/components/card.md +147 -0
- package/make-kit/components/dialog.md +167 -0
- package/make-kit/components/dropdown-menu.md +205 -0
- package/make-kit/components/form.md +189 -0
- package/make-kit/components/icon.md +152 -0
- package/make-kit/components/input.md +154 -0
- package/make-kit/components/overview.md +308 -0
- package/make-kit/components/popover.md +201 -0
- package/make-kit/components/select.md +148 -0
- package/make-kit/components/stack.md +165 -0
- package/make-kit/components/table.md +215 -0
- package/make-kit/components/tabs.md +162 -0
- package/make-kit/components/text.md +139 -0
- package/make-kit/components/toast.md +193 -0
- package/make-kit/foundations/color.md +128 -0
- package/make-kit/foundations/dark-mode.md +81 -0
- package/make-kit/foundations/icons.md +107 -0
- package/make-kit/foundations/motion.md +134 -0
- package/make-kit/foundations/radius.md +78 -0
- package/make-kit/foundations/spacing.md +110 -0
- package/make-kit/foundations/surfaces.md +121 -0
- package/make-kit/foundations/typography.md +120 -0
- package/make-kit/setup.md +130 -0
- package/package.json +9 -2
- package/skill/SKILL.md +3 -3
- package/skill/references/components-full.md +1 -1
- package/skill/references/components.md +1 -0
- package/skill/references/setup-next-app-router.md +54 -14
- package/skill/references/upgrading.md +19 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Motion
|
|
2
|
+
|
|
3
|
+
framer-motion is the kit's animation engine. **Don't write CSS keyframes** — use motion primitives.
|
|
4
|
+
|
|
5
|
+
## Required setup
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { MotionProvider } from '@devalok/shilp-sutra/motion'
|
|
9
|
+
|
|
10
|
+
<MotionProvider reducedMotion="user">
|
|
11
|
+
<App />
|
|
12
|
+
</MotionProvider>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`reducedMotion="user"` respects the OS's "reduce motion" preference. Without `MotionProvider`, the primitives still work but reduced-motion is ignored — accessibility regression.
|
|
16
|
+
|
|
17
|
+
## Primitives
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import {
|
|
21
|
+
MotionFade,
|
|
22
|
+
MotionCollapse,
|
|
23
|
+
MotionSlide,
|
|
24
|
+
MotionPop,
|
|
25
|
+
MotionScale,
|
|
26
|
+
MotionStagger,
|
|
27
|
+
} from '@devalok/shilp-sutra/motion/primitives'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
| Primitive | Use |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `MotionFade` | Mount/unmount with opacity fade. |
|
|
33
|
+
| `MotionCollapse` | Height-based expand/collapse. |
|
|
34
|
+
| `MotionSlide` | Slide in from a direction (`from="top"|"bottom"|"left"|"right"`). |
|
|
35
|
+
| `MotionPop` | Scale + fade pop. Good for tooltips, badges entering. |
|
|
36
|
+
| `MotionScale` | Scale only. |
|
|
37
|
+
| `MotionStagger` | Stagger children with a configurable delay. |
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
<MotionFade>
|
|
41
|
+
<Card>This card fades in on mount.</Card>
|
|
42
|
+
</MotionFade>
|
|
43
|
+
|
|
44
|
+
<MotionStagger gap={0.05}>
|
|
45
|
+
{items.map((item) => (
|
|
46
|
+
<ListItem key={item.id}>{item.name}</ListItem>
|
|
47
|
+
))}
|
|
48
|
+
</MotionStagger>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Springs & tweens
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import { springs, tweens } from '@devalok/shilp-sutra/motion'
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
| Spring | Feel |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `springs.snappy` | Decisive, quick. Default for controls. |
|
|
60
|
+
| `springs.smooth` | Smooth, no overshoot. Default for layout. |
|
|
61
|
+
| `springs.bouncy` | Playful overshoot. Use sparingly — only for delight moments. |
|
|
62
|
+
| `springs.gentle` | Soft, slow. For ambient motion. |
|
|
63
|
+
|
|
64
|
+
| Tween | Duration |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `tweens.fade` | 0.11s — color/opacity. |
|
|
67
|
+
| `tweens.colorShift` | 0.07s — hover color changes. |
|
|
68
|
+
|
|
69
|
+
Apply via framer's `transition` prop:
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { motion } from 'framer-motion'
|
|
73
|
+
import { springs } from '@devalok/shilp-sutra/motion'
|
|
74
|
+
|
|
75
|
+
<motion.div animate={{ y: open ? 0 : -8 }} transition={springs.snappy}>
|
|
76
|
+
…
|
|
77
|
+
</motion.div>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Duration tokens (CSS, when motion primitives aren't right)
|
|
81
|
+
|
|
82
|
+
| Token | Duration |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `--duration-fast-01` | 70 ms |
|
|
85
|
+
| `--duration-fast-02` | 110 ms |
|
|
86
|
+
| `--duration-moderate-01` | 150 ms |
|
|
87
|
+
| `--duration-moderate-02` | 240 ms |
|
|
88
|
+
| `--duration-slow-01` | 400 ms |
|
|
89
|
+
| `--duration-slow-02` | 700 ms |
|
|
90
|
+
|
|
91
|
+
Tailwind utilities: `duration-fast-01`, `duration-moderate-01`, etc. CSS variables: `var(--duration-moderate-01)`.
|
|
92
|
+
|
|
93
|
+
The system easing is `ease-productive-standard` — apply via `transition` shorthand:
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
<div className="transition-colors duration-fast-02">…</div>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Patterns
|
|
100
|
+
|
|
101
|
+
**Drawer / Sheet slide-in:** already built into `<Sheet>`. Don't reimplement.
|
|
102
|
+
|
|
103
|
+
**Dialog backdrop fade + content scale:** already built into `<Dialog>`. Don't reimplement.
|
|
104
|
+
|
|
105
|
+
**List item enter (e.g. new card appears):**
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
<MotionFade>
|
|
109
|
+
<Card>New item</Card>
|
|
110
|
+
</MotionFade>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Staggered table rows:**
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
<MotionStagger gap={0.03}>
|
|
117
|
+
{rows.map((row) => <TableRow key={row.id}>{row.cells}</TableRow>)}
|
|
118
|
+
</MotionStagger>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Async button feedback:** use `<Button onClickAsync>` — it auto-animates idle → loading → success/error → idle. Don't hand-roll spinner toggling.
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
<Button onClickAsync={async () => { await save() }}>Save</Button>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Rules
|
|
128
|
+
|
|
129
|
+
- **Wrap the app in `<MotionProvider reducedMotion="user">` once.** Without it, OS reduce-motion preference is ignored.
|
|
130
|
+
- **Never** write CSS `@keyframes` for app animations — use framer primitives.
|
|
131
|
+
- **Use motion primitives** before reaching for raw `motion.*` — they bundle the right spring + reduced-motion respect.
|
|
132
|
+
- **Don't animate layout** (height, width, top, left) — animate `transform` and `opacity`. The primitives do this for you.
|
|
133
|
+
- **Don't overuse `bouncy`** — bouncy works for one delight moment per session, not for every hover.
|
|
134
|
+
- For async button states, use `onClickAsync` + `asyncFeedbackDuration`, not manual `loading` toggling.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Radius
|
|
2
|
+
|
|
3
|
+
Two layers: a primitive scale and semantic role tokens. **Components reference roles. Override roles, not individual components.**
|
|
4
|
+
|
|
5
|
+
## Two-layer model
|
|
6
|
+
|
|
7
|
+
**Primitive scale** (size buckets):
|
|
8
|
+
|
|
9
|
+
| Token | Pixels |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `radius-ds-none` | 0 |
|
|
12
|
+
| `radius-ds-sm` | 2 |
|
|
13
|
+
| `radius-ds-md` | 6 |
|
|
14
|
+
| `radius-ds-lg` | 10 |
|
|
15
|
+
| `radius-ds-xl` | 16 |
|
|
16
|
+
| `radius-ds-2xl` | 24 |
|
|
17
|
+
| `radius-ds-full` | 9999 (pill) |
|
|
18
|
+
|
|
19
|
+
**Semantic roles** (mapped to scale at the default preset):
|
|
20
|
+
|
|
21
|
+
| Role | Default | Where it applies |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `radius-control` | 6 | Button, Input, Select, IconButton, segmented item, Tab trigger. |
|
|
24
|
+
| `radius-control-inner` | 2 | Inner elements inside a control (e.g. selected segment indicator). |
|
|
25
|
+
| `radius-surface` | 10 | Cards, widgets, panels. |
|
|
26
|
+
| `radius-overlay-sm` | 6 | Tooltip, Toast. |
|
|
27
|
+
| `radius-overlay` | 10 | Popover, Dropdown, Menubar. |
|
|
28
|
+
| `radius-overlay-lg` | 16 | Dialog, Sheet. |
|
|
29
|
+
| `radius-pill` | 9999 | Badge, Switch, Radio dot, Avatar circle. Pill always wins. |
|
|
30
|
+
| `radius-bubble` | 24 | Chat bubbles. |
|
|
31
|
+
|
|
32
|
+
## How to apply
|
|
33
|
+
|
|
34
|
+
In components: never — they apply the right role automatically.
|
|
35
|
+
|
|
36
|
+
In your own elements (rare):
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
<div className="bg-surface-raised rounded-(--radius-surface) p-ds-05">…</div>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
TW4 shorthand `rounded-(--radius-surface)` is the right way. **Do not** use `rounded-ds-lg` directly — it's not a semantic role, and the pre-publish audit rejects it in `src/ui/`.
|
|
43
|
+
|
|
44
|
+
## Shape presets
|
|
45
|
+
|
|
46
|
+
The kit ships three shape presets, switchable via `[data-shape]` on `<html>`:
|
|
47
|
+
|
|
48
|
+
| Preset | Roles |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `sharp` | control 2 / control-inner 0 / surface 4 / overlay 4–6 |
|
|
51
|
+
| `slightly-rounded` (default) | control 6 / control-inner 2 / surface 10 / overlay 6–16 |
|
|
52
|
+
| `rounded` | control 10 / control-inner 4 / surface 16 / overlay 10–24 |
|
|
53
|
+
|
|
54
|
+
Toggle by setting `data-shape="rounded"` on `<html>` (or any subtree) — every component re-skins instantly. Pill shapes (Badge, Switch, Radio dot, Avatar circle) stay pill regardless of preset.
|
|
55
|
+
|
|
56
|
+
```html
|
|
57
|
+
<html data-shape="rounded">
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Custom override
|
|
61
|
+
|
|
62
|
+
Consumers can swap any role in their global CSS:
|
|
63
|
+
|
|
64
|
+
```css
|
|
65
|
+
:root {
|
|
66
|
+
--radius-control: 4px; /* sharper buttons + inputs */
|
|
67
|
+
--radius-surface: 12px; /* gentler cards */
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Override the role, not individual components. That way one declaration re-skins the whole system.
|
|
72
|
+
|
|
73
|
+
## Rules
|
|
74
|
+
|
|
75
|
+
- **Never** hand-pick a pixel radius (`rounded-[12px]`). Use a role.
|
|
76
|
+
- **Never** use `rounded-ds-*` directly in component-level code — that's the primitive scale, not the role.
|
|
77
|
+
- **Never** override `<Card>`, `<Button>` etc. radius via className. Override the role instead.
|
|
78
|
+
- **Pill shapes** are uncontested — Badge, Switch, Radio dot, Avatar circle stay pill no matter the preset.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Spacing
|
|
2
|
+
|
|
3
|
+
The kit uses a `ds-*` spacing scale (2 → 160 px) so utilities never collide with consumer values. **Default to a three-tier cadence — `ds-03` / `ds-05` / `ds-07` — not every adjacent token.**
|
|
4
|
+
|
|
5
|
+
## Three-tier cadence (the rule)
|
|
6
|
+
|
|
7
|
+
| Tier | Token | Pixels | Use |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| Related | `ds-03` | 8 | Items that read as one chunk — icon + label, label + input, badges in a row. |
|
|
10
|
+
| Grouped | `ds-05` | 16 | Sections within a card — heading group, controls group, body. |
|
|
11
|
+
| Section | `ds-07` | 32 | Distinct page sections, big breaks. |
|
|
12
|
+
|
|
13
|
+
**Do not** reach for `ds-04` (12 px) or `ds-06` (24 px) just because they exist. Three tiers create rhythm; five create noise.
|
|
14
|
+
|
|
15
|
+
If you need a *fourth* tier (e.g. between heading and body inside one group), use `ds-02` (4 px) — that's the "tight pair" tier (caption to value, hint to input).
|
|
16
|
+
|
|
17
|
+
## Full scale
|
|
18
|
+
|
|
19
|
+
| Token | Pixels | Typical use |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| `ds-01` | 2 | Hairline gap. Rare. |
|
|
22
|
+
| `ds-02` | 4 | Tight pair (caption under value, icon to text inside dense control). |
|
|
23
|
+
| `ds-02b` | 6 | Off-cadence; use sparingly. |
|
|
24
|
+
| `ds-03` | 8 | **Related** (default). |
|
|
25
|
+
| `ds-04` | 12 | Use only when 8 is too tight and 16 is too loose. |
|
|
26
|
+
| `ds-05` | 16 | **Grouped** (default). |
|
|
27
|
+
| `ds-05b` | 20 | Off-cadence; rare. |
|
|
28
|
+
| `ds-06` | 24 | Comfortable section gap; prefer `ds-07` unless 32 is too much. |
|
|
29
|
+
| `ds-06b` | 28 | Off-cadence. |
|
|
30
|
+
| `ds-07` | 32 | **Section** (default). |
|
|
31
|
+
| `ds-08` | 40 | Big section. |
|
|
32
|
+
| `ds-09` | 48 | Hero spacing. |
|
|
33
|
+
| `ds-10` | 64 | Marketing. |
|
|
34
|
+
| `ds-11+` | 80 / 96 / 160 | Marketing only. |
|
|
35
|
+
|
|
36
|
+
## How to apply
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
// Padding
|
|
40
|
+
<Card className="p-ds-05">…</Card> // inside-card spacing
|
|
41
|
+
<div className="px-ds-07 py-ds-09">…</div> // page region
|
|
42
|
+
|
|
43
|
+
// Gaps
|
|
44
|
+
<Stack gap="ds-03">…</Stack> // related items
|
|
45
|
+
<Stack gap="ds-05">…</Stack> // grouped items
|
|
46
|
+
<Stack gap="ds-07">…</Stack> // sections
|
|
47
|
+
|
|
48
|
+
// Margins (rare — prefer gap)
|
|
49
|
+
<Text className="mt-ds-02">helper</Text> // tight pair
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## `<Stack>` over flex divs
|
|
53
|
+
|
|
54
|
+
Don't write `<div className="flex flex-col gap-ds-05">`. Use `<Stack>`:
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
<Stack gap="ds-05">
|
|
58
|
+
<Text variant="heading-md">Heading</Text>
|
|
59
|
+
<Text variant="body-md">Body</Text>
|
|
60
|
+
<Button>CTA</Button>
|
|
61
|
+
</Stack>
|
|
62
|
+
|
|
63
|
+
<Stack direction="row" gap="ds-03" align="center">
|
|
64
|
+
<Icon icon={IconUser} />
|
|
65
|
+
<Text>Username</Text>
|
|
66
|
+
</Stack>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`<Stack>` ships with `direction` (row | col, default col), `gap` (ds-* token), `align`, `justify`, `wrap`, `as` (polymorphic).
|
|
70
|
+
|
|
71
|
+
## Component sizing (separate from spacing)
|
|
72
|
+
|
|
73
|
+
Heights for controls use `ds-{size}` aliases:
|
|
74
|
+
|
|
75
|
+
| Token | Pixels | Component sizes |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `ds-xs` | 24 | n/a (rare) |
|
|
78
|
+
| `ds-xs-plus` | 28 | Button/Input/Select **xs** |
|
|
79
|
+
| `ds-sm` | 32 | Button/Input/Select **sm** |
|
|
80
|
+
| `ds-md` | 40 | Button/Input/Select **md** (default) |
|
|
81
|
+
| `ds-lg` | 48 | Button/Input/Select **lg** |
|
|
82
|
+
| `ds-xl` | 56 | Button **xl** |
|
|
83
|
+
|
|
84
|
+
Apply via component `size` prop, not className.
|
|
85
|
+
|
|
86
|
+
## Page layout tokens
|
|
87
|
+
|
|
88
|
+
| Token | Use |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `--spacing-page-x` | Horizontal page padding. Responsive: 16 → 24 → 40 px. |
|
|
91
|
+
| `--spacing-section-gap` | Vertical gap between major page sections. |
|
|
92
|
+
| `--spacing-card-gap` | Vertical gap between adjacent cards. |
|
|
93
|
+
|
|
94
|
+
Apply via:
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
<main className="px-(--spacing-page-x) py-ds-09">
|
|
98
|
+
<Stack gap="(--spacing-section-gap)">
|
|
99
|
+
…
|
|
100
|
+
</Stack>
|
|
101
|
+
</main>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Rules
|
|
105
|
+
|
|
106
|
+
- **Default** to `ds-03 / ds-05 / ds-07`. Justify any deviation.
|
|
107
|
+
- **Never** use raw Tailwind spacing (`p-4`, `gap-6`). The kit's spacing is `ds-*` namespaced.
|
|
108
|
+
- **Never** mix arbitrary pixel values (`p-[14px]`). Pick a scale token.
|
|
109
|
+
- **Prefer `<Stack gap>` over `flex … gap-*`**. Cleaner semantics, polymorphic via `as`.
|
|
110
|
+
- Component sizes (xs / sm / md / lg / xl) come from the size prop, not from manual height utilities.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Surfaces & Shadows
|
|
2
|
+
|
|
3
|
+
The kit ships five semantic surface tiers and a paired shadow system. **Picking the right surface is mandatory** — the wrong one breaks elevation hierarchy and dark mode adaptation. There is a hard rule about never combining surfaces with borders.
|
|
4
|
+
|
|
5
|
+
## The five surface tiers
|
|
6
|
+
|
|
7
|
+
| Tier | Token | What sits here |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| **Base** | `surface-base` | Page background. The "back wall." |
|
|
10
|
+
| **Raised** | `surface-raised` | Cards, widgets, panels — anything that floats **on** the page. |
|
|
11
|
+
| **Sunken** | `surface-sunken` | Shell chrome (Sidebar, TopBar), board columns, segmented-control tracks. |
|
|
12
|
+
| **Overlay** | `surface-overlay` | Floating layers — Dialog, Sheet, Popover, Dropdown, Toast, Tooltip-when-light, Input field. |
|
|
13
|
+
| **Inverted** | `surface-inverted` | Tooltips, inverted badges. Pair with `text-surface-inverted-fg`. |
|
|
14
|
+
|
|
15
|
+
Each tier has hover / active variants:
|
|
16
|
+
|
|
17
|
+
- `surface-raised-hover` — hover on a raised element.
|
|
18
|
+
- `surface-raised-active` — pressed / selected raised element.
|
|
19
|
+
|
|
20
|
+
And a disabled state:
|
|
21
|
+
|
|
22
|
+
- `surface-disabled` + `text-surface-fg-disabled`.
|
|
23
|
+
|
|
24
|
+
## Decision matrix
|
|
25
|
+
|
|
26
|
+
| Building… | Surface | Shadow |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| Page / layout shell | `surface-base` | none |
|
|
29
|
+
| Sidebar / TopBar (shell) | `surface-sunken` | `shadow-raised` |
|
|
30
|
+
| Card / widget / panel | `surface-raised` | `shadow-raised` |
|
|
31
|
+
| Card on hover | `surface-raised` | `shadow-raised-hover` |
|
|
32
|
+
| Board column / well / track | `surface-sunken` | none |
|
|
33
|
+
| Popover / dropdown / menu | `surface-overlay` | `shadow-floating` |
|
|
34
|
+
| Dialog / modal / sheet | `surface-overlay` | `shadow-overlay` |
|
|
35
|
+
| Tooltip | `surface-inverted` | `shadow-floating` |
|
|
36
|
+
| Toast | `surface-overlay` | `shadow-floating` |
|
|
37
|
+
| Input (rest) | `surface-overlay` | none |
|
|
38
|
+
| Input (focus) | `surface-overlay` | `shadow-ring` |
|
|
39
|
+
| Button (solid) | accent colors | `shadow-raised` |
|
|
40
|
+
| Button (disabled) | `surface-disabled` | none |
|
|
41
|
+
| Segmented track | `surface-sunken` | `shadow-inset` |
|
|
42
|
+
| Selected item | current surface | `shadow-glow` |
|
|
43
|
+
| Backdrop (under modal) | `bg-backdrop` | none |
|
|
44
|
+
|
|
45
|
+
## Shadow tokens
|
|
46
|
+
|
|
47
|
+
| Token | When |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `shadow-raised` | Cards, panels, shell chrome at rest. |
|
|
50
|
+
| `shadow-raised-hover` | Cards on hover. Subtle lift. |
|
|
51
|
+
| `shadow-floating` | Popovers, dropdowns, menus, tooltips. |
|
|
52
|
+
| `shadow-overlay` | Dialogs, sheets. Deepest. |
|
|
53
|
+
| `shadow-brand` | Optional brand-tinted glow. Use on hero CTA. |
|
|
54
|
+
| `shadow-glow` | Selection / focus accent. |
|
|
55
|
+
| `shadow-inset` | Toggle / segmented track deboss. |
|
|
56
|
+
| `shadow-ring` | Focus ring (2px accent). |
|
|
57
|
+
| `shadow-ring-sm` | Subtle separator (1px border-color). |
|
|
58
|
+
| `shadow-kbd` | Inset for keyboard shortcut badges. |
|
|
59
|
+
| `shadow-success` / `shadow-error` / `shadow-warning` | Status glow. Use on status notifications. |
|
|
60
|
+
|
|
61
|
+
## The hard rule: never combine `border-*` with `shadow-*`
|
|
62
|
+
|
|
63
|
+
Shadow tokens include a 1-px ring layer (inset hairline + drop shadow). Adding an explicit `border-surface-border` on top creates a 2-px edge that looks broken.
|
|
64
|
+
|
|
65
|
+
❌ **Wrong:**
|
|
66
|
+
```tsx
|
|
67
|
+
<div className="bg-surface-raised border border-surface-border shadow-raised rounded-(--radius-surface)">
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
✅ **Right (shadow only):**
|
|
71
|
+
```tsx
|
|
72
|
+
<div className="bg-surface-raised shadow-raised rounded-(--radius-surface)">
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
✅ **Right (border only — when shadow is unwanted, e.g. dense lists):**
|
|
76
|
+
```tsx
|
|
77
|
+
<div className="bg-surface-raised border border-surface-border rounded-(--radius-surface)">
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`<Card>` already follows this rule. Don't override with extra border classes.
|
|
81
|
+
|
|
82
|
+
## Elevation reasoning
|
|
83
|
+
|
|
84
|
+
Surfaces describe **what kind of layer** something is, not **how high** it is. Two cards next to each other are both `raised`. A dialog over them isn't `raised-more` — it's `overlay`. A sidebar isn't `raised-but-lower` — it's `sunken`.
|
|
85
|
+
|
|
86
|
+
This means: **don't stack `raised` on `raised`**. If you'd reach for that, the inner element is probably `overlay` or wants a `surface-sunken` recess instead.
|
|
87
|
+
|
|
88
|
+
## Dark mode
|
|
89
|
+
|
|
90
|
+
In dark mode the kit lightens surfaces with elevation (so `surface-overlay` is *brighter* than `surface-base`). This is the inverse of light mode and is intentional — it's how Stripe / Linear / Material 3 model elevation in dark. Don't fight it.
|
|
91
|
+
|
|
92
|
+
## Page composition example
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<div className="min-h-screen bg-surface-base">
|
|
96
|
+
<aside className="bg-surface-sunken"> {/* Sidebar */}
|
|
97
|
+
…
|
|
98
|
+
</aside>
|
|
99
|
+
<main className="bg-surface-base p-ds-07">
|
|
100
|
+
<Card className="bg-surface-raised shadow-raised p-ds-05"> {/* Card */}
|
|
101
|
+
…
|
|
102
|
+
<Dialog>
|
|
103
|
+
<DialogContent className="bg-surface-overlay shadow-overlay">
|
|
104
|
+
… {/* Modal */}
|
|
105
|
+
</DialogContent>
|
|
106
|
+
</Dialog>
|
|
107
|
+
</Card>
|
|
108
|
+
</main>
|
|
109
|
+
</div>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
(In practice you wouldn't write the `bg-*` / `shadow-*` directly — `<Card>`, `<DialogContent>`, `<AppSidebar>` apply them. This is just the layer model.)
|
|
113
|
+
|
|
114
|
+
## Rules
|
|
115
|
+
|
|
116
|
+
- **Card-like elements** (anything that reads as a panel on the page) → `surface-raised`, never `surface-base`.
|
|
117
|
+
- **Overlay-like elements** → `surface-overlay`, always.
|
|
118
|
+
- **Never** combine an explicit border with a shadow token.
|
|
119
|
+
- **Never** invent a sixth tier. Five is the system.
|
|
120
|
+
- **Don't** override `<Card>`, `<DialogContent>`, `<PopoverContent>` surface — they pick the right one for you.
|
|
121
|
+
- For Storybook authors and pre-publish audit: components in `src/ui/` that render as cards must NOT use `bg-surface-base`. The audit script will reject it.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Typography
|
|
2
|
+
|
|
3
|
+
The kit ships an 11-step type scale (`text-ds-xs` through `text-ds-6xl`) with paired line-heights. **Use the scale, not arbitrary pixel values.**
|
|
4
|
+
|
|
5
|
+
## Philosophy
|
|
6
|
+
|
|
7
|
+
- One scale, applied with intent. A page should land on 3–4 text sizes max — heading + body + caption.
|
|
8
|
+
- Larger headings (3xl and up) use `clamp()` to scale fluidly between mobile and desktop. Smaller sizes are fixed.
|
|
9
|
+
- Default font is Inter Variable (sans). `Ranade` Variable is the accent display face for marketing-heavy treatments. SF Mono is the monospace.
|
|
10
|
+
- Letter spacing on body sizes is tuned per-size (`body-lg: -0.01em`, `body-md: 0`, `body-sm: +0.01em`, `body-xs: +0.02em`).
|
|
11
|
+
|
|
12
|
+
## Size scale
|
|
13
|
+
|
|
14
|
+
| Token | Size | Use |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| `text-ds-xs` | 10px | Footnotes, fine print, only when space is at premium. Never for primary content. |
|
|
17
|
+
| `text-ds-sm` | 12px | Captions, table labels, helper text, badges. |
|
|
18
|
+
| `text-ds-md` | 14px | Default UI body. All input fields. Default Button label. |
|
|
19
|
+
| `text-ds-base` | 16px | Body copy in marketing / docs. Mobile input minimum. |
|
|
20
|
+
| `text-ds-lg` | 18px | Lead paragraphs, large body. |
|
|
21
|
+
| `text-ds-xl` | 20px | Small heading (h4). |
|
|
22
|
+
| `text-ds-2xl` | 24px | Section heading (h3). |
|
|
23
|
+
| `text-ds-3xl` | clamp 24→32 | Page heading (h2). Fluid. |
|
|
24
|
+
| `text-ds-4xl` | clamp 28→36 | Large page heading (h1 in product). |
|
|
25
|
+
| `text-ds-5xl` | clamp 32→48 | Marketing hero. |
|
|
26
|
+
| `text-ds-6xl` | clamp 36→60 | Marketing display. |
|
|
27
|
+
|
|
28
|
+
Pair each size with a line-height: `leading-ds-{none|tight|snug|normal|relaxed|loose}`. Default for body: `leading-ds-normal` (1.4). For headings: `leading-ds-tight` (1.15) or `leading-ds-snug` (1.25).
|
|
29
|
+
|
|
30
|
+
## Composite typography utilities
|
|
31
|
+
|
|
32
|
+
Prefer these — they bundle size + leading + weight + tracking:
|
|
33
|
+
|
|
34
|
+
| Utility | Use |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `text-heading-xl` | Page hero heading. |
|
|
37
|
+
| `text-heading-lg` | Section heading. |
|
|
38
|
+
| `text-heading-md` | Card / panel heading. |
|
|
39
|
+
| `text-heading-sm` | Sub-heading. |
|
|
40
|
+
| `text-body-lg` | Lead body. |
|
|
41
|
+
| `text-body-md` | Default body. |
|
|
42
|
+
| `text-body-sm` | Secondary body. |
|
|
43
|
+
| `text-body-xs` | Caption, footnote. |
|
|
44
|
+
| `text-code` | Inline / block code. |
|
|
45
|
+
| `text-label-plain-lg` / `-md` / `-sm` | Form labels (non-tab). |
|
|
46
|
+
|
|
47
|
+
These are what `<Text>` applies internally. When generating raw text, prefer using `<Text variant="heading-md">` (or similar) over manually composing utilities.
|
|
48
|
+
|
|
49
|
+
## Font families
|
|
50
|
+
|
|
51
|
+
| Token | Family | Use |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| `font-sans` | Inter | Default. UI body + headings. |
|
|
54
|
+
| `font-display` | Inter | Marketing headings (same family by default, separate token for theming). |
|
|
55
|
+
| `font-accent` | Ranade | Optional display accent. Only for marketing hero / brand moments. |
|
|
56
|
+
| `font-mono` | SF Mono / Fira Code | Code blocks, keyboard shortcuts, tabular data. |
|
|
57
|
+
|
|
58
|
+
## Weights
|
|
59
|
+
|
|
60
|
+
`font-weight-{light|regular|medium|semibold|bold}` = 300 / 400 / 500 / 600 / 700.
|
|
61
|
+
|
|
62
|
+
Apply via Tailwind's `font-light` / `font-medium` / `font-semibold` / `font-bold`. Default is regular (400). Headings default to `font-medium` (500). **Avoid bold (700)** for headings — the kit uses medium for refined feel. Use semibold for emphasized labels.
|
|
63
|
+
|
|
64
|
+
## Hierarchy patterns
|
|
65
|
+
|
|
66
|
+
**Card heading + body:**
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
<Card className="p-ds-05">
|
|
70
|
+
<Text variant="heading-md">Project</Text>
|
|
71
|
+
<Text variant="body-md" className="text-fg-muted mt-ds-02">
|
|
72
|
+
Sub-description with metadata.
|
|
73
|
+
</Text>
|
|
74
|
+
</Card>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Page header:**
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
<Stack gap="ds-03" className="mb-ds-07">
|
|
81
|
+
<Text variant="heading-xl">Dashboard</Text>
|
|
82
|
+
<Text variant="body-lg" className="text-fg-muted">
|
|
83
|
+
Overview of every active project.
|
|
84
|
+
</Text>
|
|
85
|
+
</Stack>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Form label + input:**
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
<FormField>
|
|
92
|
+
<Label>Email</Label>
|
|
93
|
+
<Input type="email" placeholder="you@company.com" />
|
|
94
|
+
<FormHelperText>We'll never share this.</FormHelperText>
|
|
95
|
+
</FormField>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`<Label>` already applies `text-label-plain-md`. Don't override with size utilities.
|
|
99
|
+
|
|
100
|
+
## Polymorphic `<Text>`
|
|
101
|
+
|
|
102
|
+
`<Text>` is polymorphic via `as` prop:
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
<Text as="h1" variant="heading-xl">Page title</Text>
|
|
106
|
+
<Text as="p" variant="body-md">Body copy.</Text>
|
|
107
|
+
<Text as="label" htmlFor="email">Email</Text>
|
|
108
|
+
<Text as="a" href="/x">A link</Text>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The `as` prop widens accepted HTML attributes to the rendered element — `htmlFor` / `href` typecheck correctly.
|
|
112
|
+
|
|
113
|
+
## Rules
|
|
114
|
+
|
|
115
|
+
- **Never** hand-pick a pixel font-size (`text-[15px]`, `text-[18px]`). Use the scale.
|
|
116
|
+
- **Never** use `font-bold` on headings. Use `font-medium` or `font-semibold`.
|
|
117
|
+
- **Never** apply two size utilities at once (`text-ds-md text-ds-lg`). Use composite variants.
|
|
118
|
+
- **Never** use small text (`text-ds-xs`) for anything except captions and footnotes.
|
|
119
|
+
- **Prefer `<Text variant="...">` over raw `<p>` / `<span>`** when typographic semantics matter.
|
|
120
|
+
- Inputs must be at least `text-ds-md` (14px) on desktop and `16px` on mobile (the kit enforces the iOS-zoom-safe minimum automatically).
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Setup
|
|
2
|
+
|
|
3
|
+
Required to use this kit. Every generated app needs all of these.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i @devalok/shilp-sutra @tabler/icons-react framer-motion tailwindcss @tailwindcss/vite
|
|
9
|
+
# optional, only if you render toasts:
|
|
10
|
+
npm i sonner
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`framer-motion` and `tailwindcss` are **required peer dependencies.** Without them, motion contexts split and tokens don't compile.
|
|
14
|
+
|
|
15
|
+
## Vite config
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { defineConfig } from 'vite'
|
|
19
|
+
import react from '@vitejs/plugin-react'
|
|
20
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
21
|
+
|
|
22
|
+
export default defineConfig({
|
|
23
|
+
plugins: [react(), tailwindcss()],
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Single CSS entry
|
|
28
|
+
|
|
29
|
+
In `src/index.css` (or your global stylesheet):
|
|
30
|
+
|
|
31
|
+
```css
|
|
32
|
+
@import "tailwindcss";
|
|
33
|
+
@import "@devalok/shilp-sutra/css";
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Import that CSS file once at the app root (`main.tsx`):
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
import './index.css'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Do not add `tailwind.config.ts`.** This kit is Tailwind 4 CSS-first. There is no JS preset. All tokens come from the CSS import via `@theme` blocks.
|
|
43
|
+
|
|
44
|
+
## Provider tree
|
|
45
|
+
|
|
46
|
+
Wrap the app once at the root:
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { MotionProvider } from '@devalok/shilp-sutra/motion'
|
|
50
|
+
import { Toaster } from '@devalok/shilp-sutra/ui/toaster'
|
|
51
|
+
import { IconProvider } from '@devalok/shilp-sutra/ui/icon-context'
|
|
52
|
+
|
|
53
|
+
export default function App({ children }) {
|
|
54
|
+
return (
|
|
55
|
+
<MotionProvider reducedMotion="user">
|
|
56
|
+
<IconProvider size={16}>
|
|
57
|
+
{children}
|
|
58
|
+
<Toaster />
|
|
59
|
+
</IconProvider>
|
|
60
|
+
</MotionProvider>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- `MotionProvider` — required. `reducedMotion="user"` respects OS preference. Without this provider, motion primitives still work but reduced-motion is ignored.
|
|
66
|
+
- `IconProvider` — optional but recommended. Sets default icon size for all `<Icon>` children. Override per-call with `<Icon size={20} />`.
|
|
67
|
+
- `Toaster` — only needed if the app calls `toast(...)`. Mount exactly once.
|
|
68
|
+
|
|
69
|
+
## Dark mode toggle
|
|
70
|
+
|
|
71
|
+
Add the `.dark` class on `<html>` or `<body>` to flip the entire token system to dark. The kit ships `useColorMode()`:
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { useColorMode } from '@devalok/shilp-sutra/hooks/use-color-mode'
|
|
75
|
+
|
|
76
|
+
function ThemeToggle() {
|
|
77
|
+
const { mode, setMode } = useColorMode() // 'light' | 'dark' | 'system'
|
|
78
|
+
return (
|
|
79
|
+
<Button onClick={() => setMode(mode === 'dark' ? 'light' : 'dark')}>
|
|
80
|
+
Toggle theme
|
|
81
|
+
</Button>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The hook persists choice to `localStorage`, syncs across tabs, and respects `prefers-color-scheme` when set to `'system'`.
|
|
87
|
+
|
|
88
|
+
## Import paths
|
|
89
|
+
|
|
90
|
+
Two valid styles:
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
// Per-component (preferred — smaller bundle, server-component safe):
|
|
94
|
+
import { Button } from '@devalok/shilp-sutra/ui/button'
|
|
95
|
+
import { Card } from '@devalok/shilp-sutra/ui/card'
|
|
96
|
+
|
|
97
|
+
// Barrel (fine for client-only apps):
|
|
98
|
+
import { Button, Card, Dialog } from '@devalok/shilp-sutra'
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
A few components are **per-component only** (their barrel was removed in v0.40 because they statically import optional peer deps). These must use the subpath:
|
|
102
|
+
|
|
103
|
+
- `Toaster`, `toast` → `@devalok/shilp-sutra/ui/toaster`, `/ui/toast`
|
|
104
|
+
- `DatePicker*` → `@devalok/shilp-sutra/composed/date-picker`
|
|
105
|
+
- `EmojiPicker*` → `@devalok/shilp-sutra/composed/emoji-picker`
|
|
106
|
+
- `FilePreview` → `@devalok/shilp-sutra/composed/file-preview`
|
|
107
|
+
- `MarkdownViewer` → `@devalok/shilp-sutra/composed/markdown-viewer`
|
|
108
|
+
- `RichTextEditor*` → `@devalok/shilp-sutra/composed/rich-text-editor`
|
|
109
|
+
- `InputOTP*` → `@devalok/shilp-sutra/ui/input-otp`
|
|
110
|
+
|
|
111
|
+
## Verify
|
|
112
|
+
|
|
113
|
+
After install, this should render without errors:
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
import { Button } from '@devalok/shilp-sutra/ui/button'
|
|
117
|
+
import { Card } from '@devalok/shilp-sutra/ui/card'
|
|
118
|
+
|
|
119
|
+
export default function Page() {
|
|
120
|
+
return (
|
|
121
|
+
<div className="min-h-screen bg-surface-base p-ds-07">
|
|
122
|
+
<Card className="max-w-md mx-auto p-ds-05">
|
|
123
|
+
<Button>Hello</Button>
|
|
124
|
+
</Card>
|
|
125
|
+
</div>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
If `bg-surface-base` doesn't apply, the CSS import is missing or out of order. The kit's CSS must come **after** `@import "tailwindcss"`.
|