@f0rbit/ui 0.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.
@@ -0,0 +1,121 @@
1
+ @layer tokens {
2
+ :root {
3
+ --bg: oklch(97% 0.02 290);
4
+ --bg-alt: oklch(96% 0.01 290);
5
+ --border: oklch(91% 0.01 290);
6
+
7
+ --fg: oklch(1% 0.02 290);
8
+ --fg-muted: oklch(25% 0.02 290);
9
+ --fg-subtle: oklch(35% 0.02 290);
10
+ --fg-faint: oklch(50% 0.03 290);
11
+
12
+ --accent: oklch(46% 0.015 300);
13
+ --accent-fg: var(--bg);
14
+
15
+ --success: oklch(0.95 0.05 150);
16
+ --success-fg: oklch(0.35 0.15 150);
17
+ --error: oklch(0.95 0.05 20);
18
+ --error-fg: oklch(0.45 0.15 20);
19
+ --warning: oklch(0.95 0.08 85);
20
+ --warning-fg: oklch(0.45 0.15 85);
21
+ --info: oklch(0.95 0.05 240);
22
+ --info-fg: oklch(0.4 0.15 240);
23
+
24
+ --font: "neue-haas-grotesk-text", "Inter", Helvetica, sans-serif;
25
+ --font-mono: ui-monospace, "SF Mono", Menlo, monospace;
26
+ --font-semibold: 600;
27
+
28
+ --text-xs: 0.75rem;
29
+ --text-xs-lh: 1rem;
30
+ --text-sm: 0.875rem;
31
+ --text-sm-lh: 1.25rem;
32
+ --text-base: 1rem;
33
+ --text-base-lh: 1.5rem;
34
+ --text-lg: 1.125rem;
35
+ --text-lg-lh: 1.75rem;
36
+ --text-xl: 1.25rem;
37
+ --text-xl-lh: 1.75rem;
38
+ --text-2xl: 1.5rem;
39
+ --text-2xl-lh: 2rem;
40
+ --text-3xl: 1.875rem;
41
+ --text-3xl-lh: 2.25rem;
42
+ --text-4xl: 2.25rem;
43
+ --text-4xl-lh: 2.5rem;
44
+
45
+ --leading-normal: 1.5;
46
+
47
+ --space-xs: 0.25rem;
48
+ --space-sm: 0.35rem;
49
+ --space-md: 0.75rem;
50
+ --space-lg: 1.5rem;
51
+ --space-xl: 2.5rem;
52
+
53
+ --radius: 0.25rem;
54
+ --radius-lg: 0.5rem;
55
+
56
+ --shadow: 0 1px 3px rgb(0 0 0 / 0.1);
57
+ --shadow-lg: 0 10px 25px rgb(0 0 0 / 0.15);
58
+
59
+ --transition: 150ms ease;
60
+
61
+ --hover-filter: brightness(0.8);
62
+ }
63
+
64
+ /* Dark mode: system preference or explicit data-theme */
65
+ @media (prefers-color-scheme: dark) {
66
+ :root:not([data-theme="light"]) {
67
+ --bg: oklch(21% 0.015 280);
68
+ --bg-alt: oklch(27% 0.02 290);
69
+ --border: oklch(32% 0.02 290);
70
+
71
+ --fg: oklch(100% 0 0);
72
+ --fg-muted: oklch(85% 0.02 290);
73
+ --fg-subtle: oklch(75% 0.02 290);
74
+ --fg-faint: oklch(60% 0.03 290);
75
+
76
+ --accent: oklch(70% 0.035 280);
77
+
78
+ --success: oklch(0.35 0.05 150);
79
+ --success-fg: oklch(0.85 0.1 150);
80
+ --error: oklch(0.35 0.05 20);
81
+ --error-fg: oklch(0.85 0.1 20);
82
+ --warning: oklch(0.35 0.08 85);
83
+ --warning-fg: oklch(0.85 0.12 85);
84
+ --info: oklch(0.35 0.05 240);
85
+ --info-fg: oklch(0.85 0.1 240);
86
+
87
+ --shadow: 0 1px 3px rgb(0 0 0 / 0.3);
88
+ --shadow-lg: 0 10px 25px rgb(0 0 0 / 0.4);
89
+
90
+ --hover-filter: brightness(1.2);
91
+ }
92
+ }
93
+
94
+ /* Explicit dark theme override */
95
+ :root[data-theme="dark"] {
96
+ --bg: oklch(21% 0.015 280);
97
+ --bg-alt: oklch(27% 0.02 290);
98
+ --border: oklch(32% 0.02 290);
99
+
100
+ --fg: oklch(100% 0 0);
101
+ --fg-muted: oklch(85% 0.02 290);
102
+ --fg-subtle: oklch(75% 0.02 290);
103
+ --fg-faint: oklch(60% 0.03 290);
104
+
105
+ --accent: oklch(70% 0.035 280);
106
+
107
+ --success: oklch(0.35 0.05 150);
108
+ --success-fg: oklch(0.85 0.1 150);
109
+ --error: oklch(0.35 0.05 20);
110
+ --error-fg: oklch(0.85 0.1 20);
111
+ --warning: oklch(0.35 0.08 85);
112
+ --warning-fg: oklch(0.85 0.12 85);
113
+ --info: oklch(0.35 0.05 240);
114
+ --info-fg: oklch(0.85 0.1 240);
115
+
116
+ --shadow: 0 1px 3px rgb(0 0 0 / 0.3);
117
+ --shadow-lg: 0 10px 25px rgb(0 0 0 / 0.4);
118
+
119
+ --hover-filter: brightness(1.2);
120
+ }
121
+ }
@@ -0,0 +1,298 @@
1
+ @layer utilities {
2
+ .stack {
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: var(--stack-gap, var(--space-md));
6
+ }
7
+
8
+ .stack-sm {
9
+ --stack-gap: var(--space-sm);
10
+ }
11
+
12
+ .stack-lg {
13
+ --stack-gap: var(--space-lg);
14
+ }
15
+
16
+ .row {
17
+ display: flex;
18
+ flex-direction: row;
19
+ align-items: center;
20
+ gap: var(--row-gap, var(--space-sm));
21
+ }
22
+
23
+ .row-sm {
24
+ --row-gap: var(--space-xs);
25
+ }
26
+
27
+ .row-lg {
28
+ --row-gap: var(--space-md);
29
+ }
30
+
31
+ .row-between {
32
+ justify-content: space-between;
33
+ }
34
+
35
+ .row-end {
36
+ justify-content: flex-end;
37
+ }
38
+
39
+ .row-start {
40
+ align-items: flex-start;
41
+ }
42
+
43
+ .cluster {
44
+ display: flex;
45
+ flex-wrap: wrap;
46
+ gap: var(--space-sm);
47
+ align-items: center;
48
+ }
49
+
50
+ .center {
51
+ max-width: var(--center-width, 65ch);
52
+ margin-inline: auto;
53
+ padding-inline: var(--space-md);
54
+ }
55
+
56
+ .center-narrow {
57
+ --center-width: 45ch;
58
+ }
59
+
60
+ .center-wide {
61
+ --center-width: 90ch;
62
+ }
63
+
64
+ .grid {
65
+ display: grid;
66
+ grid-template-columns: repeat(auto-fit, minmax(var(--grid-min, 250px), 1fr));
67
+ gap: var(--space-md);
68
+ }
69
+
70
+ .grid-2 {
71
+ grid-template-columns: repeat(2, 1fr);
72
+ }
73
+
74
+ .grid-3 {
75
+ grid-template-columns: repeat(3, 1fr);
76
+ }
77
+
78
+ .grid-4 {
79
+ grid-template-columns: repeat(4, 1fr);
80
+ }
81
+
82
+ .text-primary {
83
+ color: var(--fg);
84
+ }
85
+
86
+ .text-muted {
87
+ color: var(--fg-muted);
88
+ }
89
+
90
+ .text-subtle {
91
+ color: var(--fg-subtle);
92
+ }
93
+
94
+ .text-faint {
95
+ color: var(--fg-faint);
96
+ }
97
+
98
+ .text-accent {
99
+ color: var(--accent);
100
+ }
101
+
102
+ .text-xs {
103
+ font-size: var(--text-xs);
104
+ line-height: var(--text-xs-lh);
105
+ }
106
+
107
+ .text-sm {
108
+ font-size: var(--text-sm);
109
+ line-height: var(--text-sm-lh);
110
+ }
111
+
112
+ .text-base {
113
+ font-size: var(--text-base);
114
+ line-height: var(--text-base-lh);
115
+ }
116
+
117
+ .text-lg {
118
+ font-size: var(--text-lg);
119
+ line-height: var(--text-lg-lh);
120
+ }
121
+
122
+ .text-xl {
123
+ font-size: var(--text-xl);
124
+ line-height: var(--text-xl-lh);
125
+ }
126
+
127
+ .text-2xl {
128
+ font-size: var(--text-2xl);
129
+ line-height: var(--text-2xl-lh);
130
+ }
131
+
132
+ .text-3xl {
133
+ font-size: var(--text-3xl);
134
+ line-height: var(--text-3xl-lh);
135
+ }
136
+
137
+ .text-4xl {
138
+ font-size: var(--text-4xl);
139
+ line-height: var(--text-4xl-lh);
140
+ }
141
+
142
+ .font-medium {
143
+ font-weight: 500;
144
+ }
145
+
146
+ .font-bold {
147
+ font-weight: 700;
148
+ }
149
+
150
+ .font-mono {
151
+ font-family: var(--font-mono);
152
+ }
153
+
154
+ .truncate {
155
+ overflow: hidden;
156
+ text-overflow: ellipsis;
157
+ white-space: nowrap;
158
+ }
159
+
160
+ .list {
161
+ padding-left: var(--space-lg);
162
+ }
163
+
164
+ .list li {
165
+ position: relative;
166
+ padding-left: var(--space-sm);
167
+ margin-bottom: var(--space-xs);
168
+ }
169
+
170
+ .list li::marker {
171
+ color: var(--fg-faint);
172
+ }
173
+
174
+ .list li:last-child {
175
+ margin-bottom: 0;
176
+ }
177
+
178
+ ul.list {
179
+ list-style-type: disc;
180
+ }
181
+
182
+ ul.list ul {
183
+ list-style-type: circle;
184
+ margin-top: var(--space-xs);
185
+ padding-left: var(--space-lg);
186
+ }
187
+
188
+ ul.list ul ul {
189
+ list-style-type: square;
190
+ }
191
+
192
+ ol.list {
193
+ list-style-type: decimal;
194
+ }
195
+
196
+ ol.list ol {
197
+ list-style-type: lower-alpha;
198
+ margin-top: var(--space-xs);
199
+ padding-left: var(--space-lg);
200
+ }
201
+
202
+ ol.list ol ol {
203
+ list-style-type: lower-roman;
204
+ }
205
+
206
+ .list li > ul,
207
+ .list li > ol {
208
+ margin-top: var(--space-xs);
209
+ }
210
+
211
+ .sr-only {
212
+ position: absolute;
213
+ width: 1px;
214
+ height: 1px;
215
+ padding: 0;
216
+ margin: -1px;
217
+ overflow: hidden;
218
+ clip: rect(0, 0, 0, 0);
219
+ border: 0;
220
+ }
221
+
222
+ .hidden {
223
+ display: none;
224
+ }
225
+
226
+ .hover-reveal {
227
+ opacity: 0;
228
+ transition: opacity var(--transition);
229
+ }
230
+
231
+ *:hover > .hover-reveal,
232
+ *:focus-within > .hover-reveal {
233
+ opacity: 1;
234
+ }
235
+
236
+ .interactive {
237
+ --_hover-border: var(--hover-border, var(--fg-faint));
238
+ transition: border-color var(--transition);
239
+ }
240
+
241
+ .interactive:hover {
242
+ border-color: var(--_hover-border);
243
+ }
244
+
245
+ .interactive-color {
246
+ --_hover-color: var(--hover-color, var(--fg));
247
+ transition: color var(--transition);
248
+ }
249
+
250
+ .interactive-color:hover {
251
+ color: var(--_hover-color);
252
+ }
253
+
254
+ @keyframes spin {
255
+ to {
256
+ transform: rotate(360deg);
257
+ }
258
+ }
259
+
260
+ @keyframes pulse {
261
+ 50% {
262
+ opacity: 0.5;
263
+ }
264
+ }
265
+
266
+ @keyframes fade-in {
267
+ from {
268
+ opacity: 0;
269
+ }
270
+ }
271
+
272
+ .animate-spin {
273
+ animation: spin 1s linear infinite;
274
+ }
275
+
276
+ .animate-pulse {
277
+ animation: pulse 2s ease-in-out infinite;
278
+ }
279
+
280
+ @media (prefers-reduced-motion: reduce) {
281
+ .animate-spin,
282
+ .animate-pulse {
283
+ animation: none;
284
+ }
285
+ }
286
+
287
+ @media (max-width: 768px) {
288
+ .hide-mobile {
289
+ display: none;
290
+ }
291
+
292
+ .grid-2,
293
+ .grid-3,
294
+ .grid-4 {
295
+ grid-template-columns: 1fr;
296
+ }
297
+ }
298
+ }
package/package.json ADDED
@@ -0,0 +1,84 @@
1
+ {
2
+ "name": "@f0rbit/ui",
3
+ "version": "0.1.2",
4
+ "description": "A minimal, composable UI component library for SolidJS",
5
+ "type": "module",
6
+ "main": "./dist/server.js",
7
+ "module": "./dist/server.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "worker": {
12
+ "solid": "./dist/server.jsx",
13
+ "import": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/server.js"
16
+ }
17
+ },
18
+ "browser": {
19
+ "solid": "./dist/index.jsx",
20
+ "import": {
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.js"
23
+ }
24
+ },
25
+ "deno": {
26
+ "solid": "./dist/server.jsx",
27
+ "import": {
28
+ "types": "./dist/index.d.ts",
29
+ "default": "./dist/server.js"
30
+ }
31
+ },
32
+ "node": {
33
+ "solid": "./dist/server.jsx",
34
+ "import": {
35
+ "types": "./dist/index.d.ts",
36
+ "default": "./dist/server.js"
37
+ }
38
+ },
39
+ "solid": "./dist/index.jsx",
40
+ "import": {
41
+ "types": "./dist/index.d.ts",
42
+ "default": "./dist/index.js"
43
+ }
44
+ },
45
+ "./styles": "./dist/styles.css",
46
+ "./styles/tokens": "./dist/tokens.css",
47
+ "./styles/reset": "./dist/reset.css",
48
+ "./styles/utilities": "./dist/utilities.css",
49
+ "./styles/components": "./dist/components.css"
50
+ },
51
+ "files": ["dist", "src"],
52
+ "scripts": {
53
+ "build": "tsup && npm run build:css",
54
+ "build:css": "node scripts/build-css.js",
55
+ "build:llm": "node scripts/generate-llm-docs.js",
56
+ "dev": "tsup --watch",
57
+ "typecheck": "tsc --noEmit",
58
+ "prepublishOnly": "npm run build",
59
+ "docs": "bun run build && bun run build:llm && cd docs && bun run dev",
60
+ "docs:dev": "cd docs && bun run dev",
61
+ "docs:build": "bun run build:llm && cd docs && bun run build"
62
+ },
63
+ "peerDependencies": {
64
+ "solid-js": "^1.8.0"
65
+ },
66
+ "devDependencies": {
67
+ "esbuild-plugin-solid": "^0.6.0",
68
+ "solid-js": "^1.9.3",
69
+ "tsup": "^8.3.5",
70
+ "tsup-preset-solid": "^2.2.0",
71
+ "typescript": "^5.7.2"
72
+ },
73
+ "keywords": ["solid", "solidjs", "ui", "components", "design-system"],
74
+ "author": "f0rbit",
75
+ "license": "MIT",
76
+ "repository": {
77
+ "type": "git",
78
+ "url": "https://github.com/f0rbit/ui.git"
79
+ },
80
+ "browser": {
81
+ "./dist/server.js": "./dist/index.js"
82
+ },
83
+ "typesVersions": {}
84
+ }
@@ -0,0 +1,38 @@
1
+ import { JSX, splitProps } from "solid-js";
2
+
3
+ export type BadgeVariant = "default" | "success" | "error" | "warning" | "info" | "accent";
4
+
5
+ export interface BadgeProps extends JSX.HTMLAttributes<HTMLSpanElement> {
6
+ variant?: BadgeVariant;
7
+ }
8
+
9
+ const variantClasses: Record<BadgeVariant, string> = {
10
+ default: "",
11
+ success: "badge-success",
12
+ error: "badge-error",
13
+ warning: "badge-warning",
14
+ info: "badge-info",
15
+ accent: "badge-accent",
16
+ };
17
+
18
+ export function Badge(props: BadgeProps) {
19
+ const [local, rest] = splitProps(props, ["variant", "class", "children"]);
20
+
21
+ const classes = () => {
22
+ const parts = ["badge"];
23
+ const variant = local.variant ?? "default";
24
+ if (variant !== "default") {
25
+ parts.push(variantClasses[variant]);
26
+ }
27
+ if (local.class) {
28
+ parts.push(local.class);
29
+ }
30
+ return parts.join(" ");
31
+ };
32
+
33
+ return (
34
+ <span class={classes()} {...rest}>
35
+ {local.children}
36
+ </span>
37
+ );
38
+ }
@@ -0,0 +1,55 @@
1
+ import { JSX, splitProps } from "solid-js";
2
+
3
+ export type ButtonVariant = "primary" | "secondary" | "ghost" | "danger";
4
+ export type ButtonSize = "sm" | "md" | "lg";
5
+
6
+ export interface ButtonProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
7
+ variant?: ButtonVariant;
8
+ size?: ButtonSize;
9
+ icon?: boolean;
10
+ label?: string;
11
+ loading?: boolean;
12
+ }
13
+
14
+ const variantClasses: Record<ButtonVariant, string> = {
15
+ primary: "",
16
+ secondary: "btn-secondary",
17
+ ghost: "btn-ghost",
18
+ danger: "btn-danger",
19
+ };
20
+
21
+ const sizeClasses: Record<ButtonSize, string> = {
22
+ sm: "btn-sm",
23
+ md: "",
24
+ lg: "btn-lg",
25
+ };
26
+
27
+ export function Button(props: ButtonProps) {
28
+ const [local, rest] = splitProps(props, ["variant", "size", "icon", "label", "loading", "class", "disabled", "children"]);
29
+
30
+ const classes = () => {
31
+ const parts = ["btn"];
32
+ parts.push(variantClasses[local.variant ?? "primary"]);
33
+ if (local.size && local.size !== "md") {
34
+ parts.push(sizeClasses[local.size]);
35
+ }
36
+ if (local.icon) {
37
+ parts.push("btn-icon");
38
+ }
39
+ if (local.class) {
40
+ parts.push(local.class);
41
+ }
42
+ return parts.join(" ");
43
+ };
44
+
45
+ return (
46
+ <button
47
+ class={classes()}
48
+ aria-label={local.icon ? local.label : undefined}
49
+ disabled={local.disabled || local.loading}
50
+ {...rest}
51
+ >
52
+ {local.loading ? <span class="spinner spinner-sm" /> : local.children}
53
+ </button>
54
+ );
55
+ }
@@ -0,0 +1,82 @@
1
+ import { JSX, splitProps } from "solid-js";
2
+
3
+ export interface CardProps extends JSX.HTMLAttributes<HTMLDivElement> {
4
+ interactive?: boolean;
5
+ }
6
+
7
+ export interface CardHeaderProps extends JSX.HTMLAttributes<HTMLDivElement> {}
8
+ export interface CardTitleProps extends JSX.HTMLAttributes<HTMLHeadingElement> {}
9
+ export interface CardDescriptionProps extends JSX.HTMLAttributes<HTMLParagraphElement> {}
10
+ export interface CardContentProps extends JSX.HTMLAttributes<HTMLDivElement> {}
11
+ export interface CardFooterProps extends JSX.HTMLAttributes<HTMLDivElement> {}
12
+
13
+ export function Card(props: CardProps) {
14
+ const [local, rest] = splitProps(props, ["interactive", "class", "children"]);
15
+
16
+ const classes = () => {
17
+ const parts = ["card"];
18
+ if (local.interactive) {
19
+ parts.push("card-interactive");
20
+ }
21
+ if (local.class) {
22
+ parts.push(local.class);
23
+ }
24
+ return parts.join(" ");
25
+ };
26
+
27
+ return (
28
+ <div class={classes()} {...rest}>
29
+ {local.children}
30
+ </div>
31
+ );
32
+ }
33
+
34
+ export function CardHeader(props: CardHeaderProps) {
35
+ const [local, rest] = splitProps(props, ["class", "children"]);
36
+
37
+ return (
38
+ <div class={`card-header ${local.class ?? ""}`.trim()} {...rest}>
39
+ {local.children}
40
+ </div>
41
+ );
42
+ }
43
+
44
+ export function CardTitle(props: CardTitleProps) {
45
+ const [local, rest] = splitProps(props, ["class", "children"]);
46
+
47
+ return (
48
+ <h3 class={`card-title ${local.class ?? ""}`.trim()} {...rest}>
49
+ {local.children}
50
+ </h3>
51
+ );
52
+ }
53
+
54
+ export function CardDescription(props: CardDescriptionProps) {
55
+ const [local, rest] = splitProps(props, ["class", "children"]);
56
+
57
+ return (
58
+ <p class={`card-description ${local.class ?? ""}`.trim()} {...rest}>
59
+ {local.children}
60
+ </p>
61
+ );
62
+ }
63
+
64
+ export function CardContent(props: CardContentProps) {
65
+ const [local, rest] = splitProps(props, ["class", "children"]);
66
+
67
+ return (
68
+ <div class={`card-content ${local.class ?? ""}`.trim()} {...rest}>
69
+ {local.children}
70
+ </div>
71
+ );
72
+ }
73
+
74
+ export function CardFooter(props: CardFooterProps) {
75
+ const [local, rest] = splitProps(props, ["class", "children"]);
76
+
77
+ return (
78
+ <div class={`card-footer ${local.class ?? ""}`.trim()} {...rest}>
79
+ {local.children}
80
+ </div>
81
+ );
82
+ }