@human-synthesis/norns-ui 0.0.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/README.md +150 -0
- package/package.json +52 -0
- package/src/auto-import.js +47 -0
- package/src/components/Btn.n +34 -0
- package/src/index.js +13 -0
- package/src/lib/cn.js +19 -0
- package/src/lib/variants.js +22 -0
- package/src/styles/atoms.css +62 -0
- package/src/styles/index.css +8 -0
- package/src/styles/tokens.css +58 -0
- package/src/types/Btn.d.ts +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# `@human-synthesis/norns-ui`
|
|
2
|
+
|
|
3
|
+
UI library for the [Norns](https://github.com/human-synthesis/norns) ecosystem — Pug + Civet components on Tailwind v4.
|
|
4
|
+
|
|
5
|
+
**Status: Phase 1 (foundation).** Currently ships `Btn` and the atom CSS layer. Forms tier (Input, Field, Form, …) lands in Phase 2; Bits-UI-backed behavior tier (Dialog, Popover, Tabs, Toast, …) in Phase 3.
|
|
6
|
+
|
|
7
|
+
## Stack
|
|
8
|
+
|
|
9
|
+
- [Svelte 5](https://svelte.dev) — components and runes
|
|
10
|
+
- [Pug](https://pugjs.org) — templates (in `.n` files)
|
|
11
|
+
- [Civet](https://civet.dev) — `<script>` language
|
|
12
|
+
- [Tailwind CSS v4](https://tailwindcss.com) — styling, **hard peer dep**
|
|
13
|
+
- [tailwind-merge](https://github.com/dcastil/tailwind-merge) — class deduplication
|
|
14
|
+
- `@human-synthesis/norns` `^0.0.7` — peer (`nornsAutoImport` for the registration flow)
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
bun add -D @human-synthesis/norns-ui
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Setup
|
|
23
|
+
|
|
24
|
+
### 1. Wire `presetUI()` into `nornsAutoImport`
|
|
25
|
+
|
|
26
|
+
So `<Btn>`, `<Card>`, `<Field>` resolve in markup without explicit imports.
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
// vite.config.js
|
|
30
|
+
import { defineConfig } from 'vite';
|
|
31
|
+
import { sveltekit } from '@sveltejs/kit/vite';
|
|
32
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
33
|
+
import { nornsAutoImport } from '@human-synthesis/norns/auto-import';
|
|
34
|
+
import { nornsCivetPlugin } from '@human-synthesis/norns/vite';
|
|
35
|
+
import { presetUI } from '@human-synthesis/norns-ui/auto-import';
|
|
36
|
+
|
|
37
|
+
const ui = presetUI();
|
|
38
|
+
|
|
39
|
+
export default defineConfig({
|
|
40
|
+
plugins: [
|
|
41
|
+
nornsCivetPlugin(),
|
|
42
|
+
nornsAutoImport({
|
|
43
|
+
componentDirs: ['src/lib/components'],
|
|
44
|
+
components: ui.components
|
|
45
|
+
}),
|
|
46
|
+
tailwindcss(),
|
|
47
|
+
sveltekit()
|
|
48
|
+
]
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Same shape goes in `svelte.config.js`'s `preprocess` array.
|
|
53
|
+
|
|
54
|
+
`componentDirs` resolves first — your `src/lib/components/Btn.n` silently shadows the library's `Btn` whenever you want to override.
|
|
55
|
+
|
|
56
|
+
### 2. Import the styles
|
|
57
|
+
|
|
58
|
+
```css
|
|
59
|
+
/* app.css */
|
|
60
|
+
@import 'tailwindcss';
|
|
61
|
+
@import '@human-synthesis/norns-ui/styles';
|
|
62
|
+
|
|
63
|
+
/* override tokens here if you want a different brand color */
|
|
64
|
+
@theme {
|
|
65
|
+
--color-primary-500: oklch(60% 0.2 200);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. Use components in Pug
|
|
70
|
+
|
|
71
|
+
```pug
|
|
72
|
+
section.space-y-3
|
|
73
|
+
h1.text-3xl Notes
|
|
74
|
+
form(method="POST" action="?/create")
|
|
75
|
+
input.input(name="title" required)
|
|
76
|
+
Btn(type="submit" variant="primary") Create note
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## What ships
|
|
80
|
+
|
|
81
|
+
### Atoms (CSS-only — Tailwind `@layer components`)
|
|
82
|
+
|
|
83
|
+
Use directly via Pug class shorthand:
|
|
84
|
+
|
|
85
|
+
- `.btn` + variants: `.btn-primary`, `.btn-secondary`, `.btn-ghost`, `.btn-danger`, `.btn-link`
|
|
86
|
+
- `.btn` + sizes: `.btn-sm`, `.btn-lg` (default md is built into `.btn`)
|
|
87
|
+
- `.ui-spinner` — small inline spinner
|
|
88
|
+
|
|
89
|
+
More atoms (`.input`, `.card`, `.badge`, `.field-*`) land in Phase 2.
|
|
90
|
+
|
|
91
|
+
### Components
|
|
92
|
+
|
|
93
|
+
#### `<Btn>` ([source](src/components/Btn.n))
|
|
94
|
+
|
|
95
|
+
Wrapped `<button>` with class merging, variant/size props, loading state, and snippet-prop API for icons.
|
|
96
|
+
|
|
97
|
+
```pug
|
|
98
|
+
Btn(variant="primary") Save
|
|
99
|
+
Btn(variant="secondary" size="sm") Cancel
|
|
100
|
+
Btn(loading!="{saving}" type="submit") Save
|
|
101
|
+
Btn(variant="danger" onclick!="{remove}")
|
|
102
|
+
+snippet('leading')
|
|
103
|
+
// icon goes here
|
|
104
|
+
| Delete
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Props (see [`src/types/Btn.d.ts`](src/types/Btn.d.ts)):
|
|
108
|
+
- `variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'link'` (default `'primary'`)
|
|
109
|
+
- `size?: 'sm' | 'md' | 'lg'` (default `'md'`)
|
|
110
|
+
- `loading?: boolean` — replaces leading icon with `.ui-spinner`, sets `disabled` and `aria-busy`
|
|
111
|
+
- `disabled?: boolean`
|
|
112
|
+
- `type?: 'button' | 'submit' | 'reset'` (default `'button'`)
|
|
113
|
+
- `onclick?: (event) => void`
|
|
114
|
+
- `class?: string` — merged via [`tailwind-merge`](https://github.com/dcastil/tailwind-merge)
|
|
115
|
+
- `children` — default snippet (the button label)
|
|
116
|
+
- `leading`, `trailing` — snippet slots for icons
|
|
117
|
+
|
|
118
|
+
## Theming
|
|
119
|
+
|
|
120
|
+
Tokens live in [`src/styles/tokens.css`](src/styles/tokens.css) as a Tailwind v4 `@theme` block — colors, radii, sizes. Override by re-declaring `@theme { ... }` in your own `app.css` after the library import. Tailwind v4 merges layered theme blocks.
|
|
121
|
+
|
|
122
|
+
Dark mode: toggle via `<html data-theme="dark">`. The library's tokens.css ships dark-theme overrides; Tailwind's existing dark-aware utilities continue to work.
|
|
123
|
+
|
|
124
|
+
## Class merging
|
|
125
|
+
|
|
126
|
+
Every component takes a `class` prop merged via `tailwind-merge` (the `cn` helper):
|
|
127
|
+
|
|
128
|
+
```svelte
|
|
129
|
+
<Btn variant="primary" class="w-full" /> <!-- both classes survive; later wins on conflicts -->
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`cn` is exported from `@human-synthesis/norns-ui/cn` if you want to swap in plain `clsx` for smaller bundle:
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
import { cn } from '@human-synthesis/norns-ui/cn';
|
|
136
|
+
cn('btn', isActive && 'btn-active', extra)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Override a component
|
|
140
|
+
|
|
141
|
+
Drop a same-name file in your `src/lib/components/` and `nornsAutoImport`'s first-match-wins resolves to your version silently. No fork needed.
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
src/lib/components/Btn.n ← your override wins
|
|
145
|
+
node_modules/@human-synthesis/norns-ui/src/components/Btn.n ← fallback
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## License
|
|
149
|
+
|
|
150
|
+
MIT © Daniel Teodoroiu / [Human Synthesis](https://humansynthesis.ai). Built on top of [Svelte](https://github.com/sveltejs/svelte) © Svelte Contributors, MIT licensed.
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@human-synthesis/norns-ui",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "UI library for the Norns ecosystem — Pug + Civet components on Tailwind v4.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Daniel Teodoroiu (https://humansynthesis.ai)",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/human-synthesis/norns-ui.git"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"src",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "bun test"
|
|
18
|
+
},
|
|
19
|
+
"exports": {
|
|
20
|
+
".": "./src/index.js",
|
|
21
|
+
"./auto-import": "./src/auto-import.js",
|
|
22
|
+
"./cn": "./src/lib/cn.js",
|
|
23
|
+
"./styles": "./src/styles/index.css",
|
|
24
|
+
"./styles/tokens": "./src/styles/tokens.css",
|
|
25
|
+
"./styles/atoms": "./src/styles/atoms.css",
|
|
26
|
+
"./components/*": "./src/components/*",
|
|
27
|
+
"./package.json": "./package.json"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@human-synthesis/norns": "^0.0.7",
|
|
31
|
+
"@human-synthesis/norns-core": "^0.0.7",
|
|
32
|
+
"svelte": "^5.0.0",
|
|
33
|
+
"tailwindcss": "^4.0.0"
|
|
34
|
+
},
|
|
35
|
+
"peerDependenciesMeta": {
|
|
36
|
+
"@human-synthesis/norns": {
|
|
37
|
+
"optional": false
|
|
38
|
+
},
|
|
39
|
+
"@human-synthesis/norns-core": {
|
|
40
|
+
"optional": false
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"tailwind-merge": "^2.5.0"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Norns UI auto-import preset.
|
|
3
|
+
*
|
|
4
|
+
* Returns `{ components, helpers }` for `nornsAutoImport`'s options. Wire it
|
|
5
|
+
* into your app's vite.config (or svelte.config) like:
|
|
6
|
+
*
|
|
7
|
+
* import { nornsAutoImport } from '@human-synthesis/norns/auto-import';
|
|
8
|
+
* import { presetUI } from '@human-synthesis/norns-ui/auto-import';
|
|
9
|
+
*
|
|
10
|
+
* const ui = presetUI();
|
|
11
|
+
*
|
|
12
|
+
* // vite.config.js
|
|
13
|
+
* plugins: [
|
|
14
|
+
* nornsCivetPlugin(),
|
|
15
|
+
* nornsAutoImport({
|
|
16
|
+
* componentDirs: ['src/lib/components'], // user folder wins
|
|
17
|
+
* components: ui.components, // norns-ui as fallback
|
|
18
|
+
* helpers: ui.helpers
|
|
19
|
+
* }),
|
|
20
|
+
* sveltekit()
|
|
21
|
+
* ]
|
|
22
|
+
*
|
|
23
|
+
* Components are imported via bare specifiers so the consumer's
|
|
24
|
+
* `node_modules/@human-synthesis/norns-ui/...` is the resolution target.
|
|
25
|
+
* Helpers (functional APIs like `toast()`) are added incrementally as the
|
|
26
|
+
* library grows.
|
|
27
|
+
*
|
|
28
|
+
* @typedef {Object} UIPreset
|
|
29
|
+
* @property {Record<string, string>} components name → bare-specifier import path
|
|
30
|
+
* @property {Array<{ from: string, imports: string[] }>} helpers name groups for nornsAutoImport
|
|
31
|
+
*
|
|
32
|
+
* @returns {UIPreset}
|
|
33
|
+
*/
|
|
34
|
+
export function presetUI() {
|
|
35
|
+
return {
|
|
36
|
+
components: {
|
|
37
|
+
Btn: '@human-synthesis/norns-ui/components/Btn.n'
|
|
38
|
+
// Phase 2+ adds: Input, Textarea, Select, Checkbox, Radio, Switch,
|
|
39
|
+
// Field, Form, FieldGroup, Card, Dialog, Sheet, Popover, Dropdown,
|
|
40
|
+
// Tooltip, Tabs, Accordion, Toast, Listbox, Combobox, Pagination,
|
|
41
|
+
// Avatar, Badge, Spinner, Progress, Skeleton, Icon
|
|
42
|
+
},
|
|
43
|
+
helpers: [
|
|
44
|
+
// Phase 3+: { from: '@human-synthesis/norns-ui', imports: ['toast'] }
|
|
45
|
+
]
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
button(
|
|
2
|
+
type!="{type}"
|
|
3
|
+
class!="{classes}"
|
|
4
|
+
disabled!="{disabled || loading}"
|
|
5
|
+
onclick!="{onclick}"
|
|
6
|
+
aria-busy!="{loading ? 'true' : undefined}"
|
|
7
|
+
)
|
|
8
|
+
+if('loading')
|
|
9
|
+
span.ui-spinner(aria-hidden="true")
|
|
10
|
+
+if('!loading && leading')
|
|
11
|
+
| {@render leading()}
|
|
12
|
+
+if('children')
|
|
13
|
+
| {@render children()}
|
|
14
|
+
+if('!loading && trailing')
|
|
15
|
+
| {@render trailing()}
|
|
16
|
+
|
|
17
|
+
<script>
|
|
18
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
19
|
+
|
|
20
|
+
{
|
|
21
|
+
variant = 'primary'
|
|
22
|
+
size = 'md'
|
|
23
|
+
loading = false
|
|
24
|
+
disabled = false
|
|
25
|
+
type = 'button'
|
|
26
|
+
onclick = undefined
|
|
27
|
+
children
|
|
28
|
+
leading
|
|
29
|
+
trailing
|
|
30
|
+
class: extra = ''
|
|
31
|
+
} := $props()
|
|
32
|
+
|
|
33
|
+
classes := cn 'btn', `btn-${variant}`, size !== 'md' && `btn-${size}`, extra
|
|
34
|
+
</script>
|
package/src/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main barrel — re-exports user-facing components and helpers.
|
|
3
|
+
*
|
|
4
|
+
* Most consumption goes through auto-import (via the `presetUI()` factory in
|
|
5
|
+
* `@human-synthesis/norns-ui/auto-import`). This barrel exists for users
|
|
6
|
+
* who prefer explicit imports or who need to reach a component from JS
|
|
7
|
+
* code that auto-import doesn't process (e.g. dynamic mount).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export { default as Btn } from './components/Btn.n';
|
|
11
|
+
export { cn } from './lib/cn.js';
|
|
12
|
+
export { variantClasses } from './lib/variants.js';
|
|
13
|
+
export { presetUI } from './auto-import.js';
|
package/src/lib/cn.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { twMerge } from 'tailwind-merge';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Merge a list of class strings, deduplicating Tailwind utilities so the
|
|
5
|
+
* later one wins (e.g. `cn('p-4', 'p-2')` → `'p-2'`).
|
|
6
|
+
*
|
|
7
|
+
* Falsy inputs (null / undefined / '' / false) are filtered, so consumers
|
|
8
|
+
* can pass conditionals inline:
|
|
9
|
+
* cn('btn', variant === 'primary' && 'btn-primary', extra)
|
|
10
|
+
*
|
|
11
|
+
* Exported from a subpath so users can swap to plain `clsx` if they don't
|
|
12
|
+
* want tailwind-merge's bundle cost.
|
|
13
|
+
*
|
|
14
|
+
* @param {...(string | false | null | undefined)} parts
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
export function cn(...parts) {
|
|
18
|
+
return twMerge(parts.filter(Boolean).join(' '));
|
|
19
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map a `{ variant, size, ... }` prop bag to a class string following the
|
|
3
|
+
* `<atom>-<variant>` naming convention. Skips falsy values (so an undefined
|
|
4
|
+
* variant doesn't emit `btn-undefined`).
|
|
5
|
+
*
|
|
6
|
+
* variantClasses('btn', { variant: 'primary', size: 'sm' })
|
|
7
|
+
* → 'btn-primary btn-sm'
|
|
8
|
+
*
|
|
9
|
+
* Used by component wrappers (Btn, Badge, etc.) so the variant convention
|
|
10
|
+
* stays uniform across the library.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} atom
|
|
13
|
+
* @param {Record<string, string | undefined | false>} props
|
|
14
|
+
* @returns {string}
|
|
15
|
+
*/
|
|
16
|
+
export function variantClasses(atom, props) {
|
|
17
|
+
const out = [];
|
|
18
|
+
for (const value of Object.values(props)) {
|
|
19
|
+
if (value) out.push(`${atom}-${value}`);
|
|
20
|
+
}
|
|
21
|
+
return out.join(' ');
|
|
22
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Norns UI — component atom classes.
|
|
3
|
+
*
|
|
4
|
+
* Pure Tailwind utility-composition via `@layer components`. No JS, no Svelte
|
|
5
|
+
* — just classes consumers can use directly via Pug shorthand (`button.btn.btn-primary`)
|
|
6
|
+
* or via the wrapper components (`<Btn variant="primary">`).
|
|
7
|
+
*
|
|
8
|
+
* Naming convention: `<atom>-<variant>` and `<atom>-<size>`. AI-predictable.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
@reference 'tailwindcss';
|
|
12
|
+
|
|
13
|
+
@layer components {
|
|
14
|
+
/* --- Button --- */
|
|
15
|
+
.btn {
|
|
16
|
+
@apply inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium leading-none whitespace-nowrap;
|
|
17
|
+
@apply transition-[background-color,color,box-shadow,opacity] duration-150 ease-out;
|
|
18
|
+
@apply select-none cursor-pointer;
|
|
19
|
+
@apply disabled:cursor-not-allowed disabled:opacity-60;
|
|
20
|
+
@apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-primary-500;
|
|
21
|
+
height: var(--ui-h-md);
|
|
22
|
+
padding-inline: 0.875rem;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.btn-sm {
|
|
26
|
+
@apply text-xs;
|
|
27
|
+
height: var(--ui-h-sm);
|
|
28
|
+
padding-inline: 0.625rem;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.btn-lg {
|
|
32
|
+
@apply text-base;
|
|
33
|
+
height: var(--ui-h-lg);
|
|
34
|
+
padding-inline: 1.125rem;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.btn-primary {
|
|
38
|
+
@apply bg-primary-600 text-white shadow-sm hover:bg-primary-700 active:bg-primary-800;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.btn-secondary {
|
|
42
|
+
@apply bg-neutral-100 text-neutral-900 shadow-sm hover:bg-neutral-200 active:bg-neutral-300;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.btn-ghost {
|
|
46
|
+
@apply bg-transparent text-neutral-700 hover:bg-neutral-100 active:bg-neutral-200;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.btn-danger {
|
|
50
|
+
@apply bg-red-600 text-white shadow-sm hover:bg-red-700 active:bg-red-800 focus-visible:ring-red-500;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.btn-link {
|
|
54
|
+
@apply bg-transparent text-primary-600 underline-offset-4 hover:underline px-0;
|
|
55
|
+
height: auto;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* --- Spinner (used inside Btn loading state) --- */
|
|
59
|
+
.ui-spinner {
|
|
60
|
+
@apply inline-block size-4 animate-spin rounded-full border-2 border-current border-t-transparent;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Norns UI — design tokens.
|
|
3
|
+
*
|
|
4
|
+
* Tailwind v4 `@theme` block. Users override by re-declaring `@theme { ... }`
|
|
5
|
+
* after `@import '@human-synthesis/norns-ui/styles'` in their own app.css —
|
|
6
|
+
* Tailwind merges layered theme blocks. Same goes for dark mode via the
|
|
7
|
+
* `[data-theme="dark"]` selector at the bottom.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
@theme {
|
|
11
|
+
/* Brand color (violet) — keeps Norns's existing violet-500 accent. */
|
|
12
|
+
--color-primary-50: oklch(97.7% 0.014 308.299);
|
|
13
|
+
--color-primary-100: oklch(94.6% 0.033 307.174);
|
|
14
|
+
--color-primary-200: oklch(90.2% 0.063 306.703);
|
|
15
|
+
--color-primary-300: oklch(81.1% 0.111 293.571);
|
|
16
|
+
--color-primary-400: oklch(70.2% 0.183 293.541);
|
|
17
|
+
--color-primary-500: oklch(60.6% 0.25 292.717);
|
|
18
|
+
--color-primary-600: oklch(54.1% 0.281 293.009);
|
|
19
|
+
--color-primary-700: oklch(49.1% 0.27 292.581);
|
|
20
|
+
--color-primary-800: oklch(43.2% 0.232 292.759);
|
|
21
|
+
--color-primary-900: oklch(38% 0.189 293.745);
|
|
22
|
+
--color-primary-950: oklch(28.3% 0.141 291.089);
|
|
23
|
+
|
|
24
|
+
/* Semantic surface colors derived from neutral + primary. Components
|
|
25
|
+
* reference `--color-fg`, `--color-muted`, etc. so consumer themes can
|
|
26
|
+
* remap surfaces without overriding every utility. */
|
|
27
|
+
--color-fg: var(--color-neutral-900);
|
|
28
|
+
--color-fg-muted: var(--color-neutral-600);
|
|
29
|
+
--color-fg-subtle: var(--color-neutral-400);
|
|
30
|
+
--color-bg: white;
|
|
31
|
+
--color-bg-muted: var(--color-neutral-100);
|
|
32
|
+
--color-bg-subtle: var(--color-neutral-50);
|
|
33
|
+
--color-border: var(--color-neutral-200);
|
|
34
|
+
--color-border-strong: var(--color-neutral-300);
|
|
35
|
+
|
|
36
|
+
/* Component sizing — kept minimal; most sizing is via Tailwind utilities. */
|
|
37
|
+
--ui-radius-sm: 0.25rem;
|
|
38
|
+
--ui-radius: 0.375rem;
|
|
39
|
+
--ui-radius-lg: 0.5rem;
|
|
40
|
+
--ui-radius-full: 9999px;
|
|
41
|
+
|
|
42
|
+
--ui-h-sm: 1.75rem; /* 28px */
|
|
43
|
+
--ui-h-md: 2.25rem; /* 36px */
|
|
44
|
+
--ui-h-lg: 2.75rem; /* 44px */
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Dark theme — user toggles via <html data-theme="dark">. Override surfaces;
|
|
48
|
+
* primary scale stays the same (Tailwind primaries already work on dark). */
|
|
49
|
+
[data-theme='dark'] {
|
|
50
|
+
--color-fg: var(--color-neutral-100);
|
|
51
|
+
--color-fg-muted: var(--color-neutral-400);
|
|
52
|
+
--color-fg-subtle: var(--color-neutral-500);
|
|
53
|
+
--color-bg: var(--color-neutral-900);
|
|
54
|
+
--color-bg-muted: var(--color-neutral-800);
|
|
55
|
+
--color-bg-subtle: var(--color-neutral-850, oklch(25% 0 0));
|
|
56
|
+
--color-border: var(--color-neutral-800);
|
|
57
|
+
--color-border-strong: var(--color-neutral-700);
|
|
58
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type shim for `Btn.n`. Hand-rolled until we have a proper
|
|
3
|
+
* `svelte-package` build over the Civet+Pug source.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Component, Snippet } from 'svelte';
|
|
7
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
8
|
+
|
|
9
|
+
export type BtnVariant = 'primary' | 'secondary' | 'ghost' | 'danger' | 'link';
|
|
10
|
+
export type BtnSize = 'sm' | 'md' | 'lg';
|
|
11
|
+
|
|
12
|
+
export type BtnProps = Omit<HTMLButtonAttributes, 'class' | 'children'> & {
|
|
13
|
+
variant?: BtnVariant;
|
|
14
|
+
size?: BtnSize;
|
|
15
|
+
loading?: boolean;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
type?: 'button' | 'submit' | 'reset';
|
|
18
|
+
class?: string;
|
|
19
|
+
children?: Snippet;
|
|
20
|
+
leading?: Snippet;
|
|
21
|
+
trailing?: Snippet;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
declare const Btn: Component<BtnProps>;
|
|
25
|
+
export default Btn;
|