@l.x/mycelium 1.0.2 → 1.0.3
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/.depcheckrc +9 -0
- package/.eslintrc.js +10 -0
- package/README.md +53 -0
- package/css/animations.css +280 -0
- package/css/base.css +13 -0
- package/css/fonts.css +25 -0
- package/css/theme.css +322 -0
- package/css/variables.css +202 -0
- package/fonts/Basel-Grotesk-Book.woff +0 -0
- package/fonts/Basel-Grotesk-Book.woff2 +0 -0
- package/fonts/Basel-Grotesk-Medium.woff +0 -0
- package/fonts/Basel-Grotesk-Medium.woff2 +0 -0
- package/fonts.css +4 -0
- package/package.json +50 -1
- package/project.json +18 -0
- package/src/cn.ts +75 -0
- package/src/index.ts +8 -0
- package/src/types.ts +49 -0
- package/src/unicon/Unicon.tsx +84 -0
- package/src/unicon/colors.ts +33 -0
- package/src/unicon/hash.ts +20 -0
- package/src/unicon/icons.ts +276 -0
- package/tailwind.css +8 -0
- package/tsconfig.json +26 -0
- package/tsconfig.lint.json +8 -0
- package/index.d.ts +0 -1
- package/index.js +0 -1
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/* CSS Custom Properties */
|
|
2
|
+
|
|
3
|
+
/* Light mode is the default */
|
|
4
|
+
:root {
|
|
5
|
+
color-scheme: light;
|
|
6
|
+
--font-basel: "Basel Grotesk";
|
|
7
|
+
|
|
8
|
+
/* Light mode shadows */
|
|
9
|
+
--shadow-short: 0px 1px 6px 2px rgba(0, 0, 0, 0.03), 0px 1px 2px 0px rgba(0, 0, 0, 0.02);
|
|
10
|
+
--shadow-medium: 0px 6px 12px -3px rgba(19, 19, 19, 0.04), 0px 2px 5px -2px rgba(19, 19, 19, 0.03);
|
|
11
|
+
--shadow-large: 0px 10px 20px -5px rgba(19, 19, 19, 0.05), 0px 4px 12px -3px rgba(19, 19, 19, 0.04);
|
|
12
|
+
|
|
13
|
+
/* shadcn compatibility */
|
|
14
|
+
--radius: 0.625rem;
|
|
15
|
+
--background: oklch(1 0 0);
|
|
16
|
+
--foreground: oklch(0.145 0 0);
|
|
17
|
+
--card: oklch(1 0 0);
|
|
18
|
+
--card-foreground: oklch(0.145 0 0);
|
|
19
|
+
--popover: oklch(1 0 0);
|
|
20
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
21
|
+
--primary: oklch(0.205 0 0);
|
|
22
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
23
|
+
--secondary: oklch(0.97 0 0);
|
|
24
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
25
|
+
--muted: oklch(0.97 0 0);
|
|
26
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
27
|
+
--accent: oklch(0.97 0 0);
|
|
28
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
29
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
30
|
+
--border: oklch(0.922 0 0);
|
|
31
|
+
--input: oklch(0.922 0 0);
|
|
32
|
+
--ring: oklch(0.708 0 0);
|
|
33
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
34
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
35
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
36
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
37
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
38
|
+
--sidebar: oklch(0.985 0 0);
|
|
39
|
+
--sidebar-foreground: oklch(0.145 0 0);
|
|
40
|
+
--sidebar-primary: oklch(0.205 0 0);
|
|
41
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
42
|
+
--sidebar-accent: oklch(0.97 0 0);
|
|
43
|
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
44
|
+
--sidebar-border: oklch(0.922 0 0);
|
|
45
|
+
--sidebar-ring: oklch(0.708 0 0);
|
|
46
|
+
|
|
47
|
+
/* Mycelium semantic colors - light mode (auto-switching) */
|
|
48
|
+
--surface1: var(--color-surface1-light);
|
|
49
|
+
--surface2: var(--color-surface2-light);
|
|
50
|
+
--surface3: var(--color-surface3-light);
|
|
51
|
+
--surface4: var(--color-surface4-light);
|
|
52
|
+
--surface5: var(--color-surface5-light);
|
|
53
|
+
--neutral1: var(--color-neutral1-light);
|
|
54
|
+
--neutral2: var(--color-neutral2-light);
|
|
55
|
+
--neutral3: var(--color-neutral3-light);
|
|
56
|
+
--accent1: var(--color-accent1-light);
|
|
57
|
+
--accent2: var(--color-accent2-light);
|
|
58
|
+
|
|
59
|
+
/* Semantic status colors - auto-switching light/dark */
|
|
60
|
+
--success: var(--color-success-light);
|
|
61
|
+
--warning: var(--color-gold-200);
|
|
62
|
+
--critical: var(--color-critical-light);
|
|
63
|
+
|
|
64
|
+
/* Unicon avatar colors - auto-switching */
|
|
65
|
+
--unicon-0: var(--color-unicon-0-light);
|
|
66
|
+
--unicon-1: var(--color-unicon-1-light);
|
|
67
|
+
--unicon-2: var(--color-unicon-2-light);
|
|
68
|
+
--unicon-3: var(--color-unicon-3-light);
|
|
69
|
+
--unicon-4: var(--color-unicon-4-light);
|
|
70
|
+
--unicon-5: var(--color-unicon-5-light);
|
|
71
|
+
--unicon-6: var(--color-unicon-6-light);
|
|
72
|
+
--unicon-7: var(--color-unicon-7-light);
|
|
73
|
+
--unicon-8: var(--color-unicon-8-light);
|
|
74
|
+
--unicon-9: var(--color-unicon-9-light);
|
|
75
|
+
--unicon-bg-opacity: 0.12;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Dark mode applies when .dark class is present */
|
|
79
|
+
.dark {
|
|
80
|
+
color-scheme: dark;
|
|
81
|
+
|
|
82
|
+
/* Dark mode shadows */
|
|
83
|
+
--shadow-short: 0px 1px 3px 0px rgba(0, 0, 0, 0.12), 0px 1px 2px 0px rgba(0, 0, 0, 0.24);
|
|
84
|
+
--shadow-medium: 0px 10px 15px -3px rgba(19, 19, 19, 0.54), 0px 4px 6px -2px rgba(19, 19, 19, 0.4);
|
|
85
|
+
--shadow-large: 0px 16px 24px -6px rgba(0, 0, 0, 0.6), 0px 8px 12px -4px rgba(0, 0, 0, 0.48);
|
|
86
|
+
|
|
87
|
+
/* shadcn compatibility */
|
|
88
|
+
--background: oklch(0.145 0 0);
|
|
89
|
+
--foreground: oklch(0.985 0 0);
|
|
90
|
+
--card: oklch(0.205 0 0);
|
|
91
|
+
--card-foreground: oklch(0.985 0 0);
|
|
92
|
+
--popover: oklch(0.205 0 0);
|
|
93
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
94
|
+
--primary: oklch(0.922 0 0);
|
|
95
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
96
|
+
--secondary: oklch(0.269 0 0);
|
|
97
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
98
|
+
--muted: oklch(0.269 0 0);
|
|
99
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
100
|
+
--accent: oklch(0.269 0 0);
|
|
101
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
102
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
103
|
+
--border: oklch(1 0 0 / 10%);
|
|
104
|
+
--input: oklch(1 0 0 / 15%);
|
|
105
|
+
--ring: oklch(0.556 0 0);
|
|
106
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
107
|
+
--chart-2: oklch(0.696 0.17 162.48);
|
|
108
|
+
--chart-3: oklch(0.769 0.188 70.08);
|
|
109
|
+
--chart-4: oklch(0.627 0.265 303.9);
|
|
110
|
+
--chart-5: oklch(0.645 0.246 16.439);
|
|
111
|
+
--sidebar: oklch(0.205 0 0);
|
|
112
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
113
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
114
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
115
|
+
--sidebar-accent: oklch(0.269 0 0);
|
|
116
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
117
|
+
--sidebar-border: oklch(1 0 0 / 10%);
|
|
118
|
+
--sidebar-ring: oklch(0.556 0 0);
|
|
119
|
+
|
|
120
|
+
/* Mycelium semantic colors - dark mode (auto-switching) */
|
|
121
|
+
--surface1: var(--color-surface1-dark);
|
|
122
|
+
--surface2: var(--color-surface2-dark);
|
|
123
|
+
--surface3: var(--color-surface3-dark);
|
|
124
|
+
--surface4: var(--color-surface4-dark);
|
|
125
|
+
--surface5: var(--color-surface5-dark);
|
|
126
|
+
--neutral1: var(--color-neutral1-dark);
|
|
127
|
+
--neutral2: var(--color-neutral2-dark);
|
|
128
|
+
--neutral3: var(--color-neutral3-dark);
|
|
129
|
+
--accent1: var(--color-accent1-dark);
|
|
130
|
+
--accent2: var(--color-accent2-dark);
|
|
131
|
+
/* Status colors - dark mode */
|
|
132
|
+
--success: var(--color-success-dark);
|
|
133
|
+
--critical: var(--color-critical-dark);
|
|
134
|
+
|
|
135
|
+
/* Unicon avatar colors - dark mode */
|
|
136
|
+
--unicon-0: var(--color-unicon-0-dark);
|
|
137
|
+
--unicon-1: var(--color-unicon-1-dark);
|
|
138
|
+
--unicon-2: var(--color-unicon-2-dark);
|
|
139
|
+
--unicon-3: var(--color-unicon-3-dark);
|
|
140
|
+
--unicon-4: var(--color-unicon-4-dark);
|
|
141
|
+
--unicon-5: var(--color-unicon-5-dark);
|
|
142
|
+
--unicon-6: var(--color-unicon-6-dark);
|
|
143
|
+
--unicon-7: var(--color-unicon-7-dark);
|
|
144
|
+
--unicon-8: var(--color-unicon-8-dark);
|
|
145
|
+
--unicon-9: var(--color-unicon-9-dark);
|
|
146
|
+
--unicon-bg-opacity: 0.16;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* shadcn theme inline block for Tailwind v4 */
|
|
150
|
+
@theme inline {
|
|
151
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
152
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
153
|
+
--radius-lg: var(--radius);
|
|
154
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
155
|
+
--color-background: var(--background);
|
|
156
|
+
--color-foreground: var(--foreground);
|
|
157
|
+
--color-card: var(--card);
|
|
158
|
+
--color-card-foreground: var(--card-foreground);
|
|
159
|
+
--color-popover: var(--popover);
|
|
160
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
161
|
+
--color-primary: var(--primary);
|
|
162
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
163
|
+
--color-secondary: var(--secondary);
|
|
164
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
165
|
+
--color-muted: var(--muted);
|
|
166
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
167
|
+
--color-accent: var(--accent);
|
|
168
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
169
|
+
--color-destructive: var(--destructive);
|
|
170
|
+
--color-border: var(--border);
|
|
171
|
+
--color-input: var(--input);
|
|
172
|
+
--color-ring: var(--ring);
|
|
173
|
+
--color-chart-1: var(--chart-1);
|
|
174
|
+
--color-chart-2: var(--chart-2);
|
|
175
|
+
--color-chart-3: var(--chart-3);
|
|
176
|
+
--color-chart-4: var(--chart-4);
|
|
177
|
+
--color-chart-5: var(--chart-5);
|
|
178
|
+
--color-sidebar: var(--sidebar);
|
|
179
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
180
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
181
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
182
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
183
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
184
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
185
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
186
|
+
|
|
187
|
+
/* Mycelium semantic colors - auto-switching Tailwind utilities */
|
|
188
|
+
--color-surface1: var(--surface1);
|
|
189
|
+
--color-surface2: var(--surface2);
|
|
190
|
+
--color-surface3: var(--surface3);
|
|
191
|
+
--color-surface4: var(--surface4);
|
|
192
|
+
--color-surface5: var(--surface5);
|
|
193
|
+
--color-neutral1: var(--neutral1);
|
|
194
|
+
--color-neutral2: var(--neutral2);
|
|
195
|
+
--color-neutral3: var(--neutral3);
|
|
196
|
+
--color-accent1: var(--accent1);
|
|
197
|
+
--color-accent2: var(--accent2);
|
|
198
|
+
/* Status colors: semantic tokens reference raw palette from theme.css */
|
|
199
|
+
--color-success: var(--success);
|
|
200
|
+
--color-warning: var(--warning);
|
|
201
|
+
--color-critical: var(--critical);
|
|
202
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/fonts.css
ADDED
package/package.json
CHANGED
|
@@ -1 +1,50 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"name": "@l.x/mycelium",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"private": false,
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"sideEffects": [
|
|
7
|
+
"*.css"
|
|
8
|
+
],
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./src/index.ts",
|
|
11
|
+
"./tailwind": "./tailwind.css",
|
|
12
|
+
"./fonts": "./fonts.css",
|
|
13
|
+
"./cn": {
|
|
14
|
+
"types": "./src/cn.d.ts",
|
|
15
|
+
"default": "./src/index.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"clsx": "2.1.1",
|
|
20
|
+
"tailwind-merge": "3.3.1"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"react": ">=18.0.0",
|
|
24
|
+
"tailwindcss": ">=4.0.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "22.13.1",
|
|
28
|
+
"@typescript/native-preview": "7.0.0-dev.20260311.1",
|
|
29
|
+
"@luxfi/eslint-config": "workspace:^",
|
|
30
|
+
"depcheck": "1.4.7",
|
|
31
|
+
"eslint": "8.57.1",
|
|
32
|
+
"react": "19.0.3",
|
|
33
|
+
"tailwindcss": "4.1.16",
|
|
34
|
+
"typescript": "5.8.3"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"typecheck": "nx typecheck @l.x/mycelium",
|
|
38
|
+
"typecheck:tsgo": "nx typecheck:tsgo @l.x/mycelium",
|
|
39
|
+
"lint": "nx lint @l.x/mycelium",
|
|
40
|
+
"lint:fix": "nx lint:fix @l.x/mycelium",
|
|
41
|
+
"lint:biome": "nx lint:biome @l.x/mycelium",
|
|
42
|
+
"lint:biome:fix": "nx lint:biome:fix @l.x/mycelium",
|
|
43
|
+
"lint:eslint": "nx lint:eslint @l.x/mycelium",
|
|
44
|
+
"lint:eslint:fix": "nx lint:eslint:fix @l.x/mycelium",
|
|
45
|
+
"check:deps:usage": "nx check:deps:usage @l.x/mycelium"
|
|
46
|
+
},
|
|
47
|
+
"nx": {
|
|
48
|
+
"includedScripts": []
|
|
49
|
+
}
|
|
50
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@l.x/mycelium",
|
|
3
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "pkgs/mycelium/src",
|
|
5
|
+
"projectType": "library",
|
|
6
|
+
"tags": ["scope:mycelium", "type:lib"],
|
|
7
|
+
"targets": {
|
|
8
|
+
"typecheck": {},
|
|
9
|
+
"typecheck:tsgo": {},
|
|
10
|
+
"lint:biome": {},
|
|
11
|
+
"lint:biome:fix": {},
|
|
12
|
+
"lint:eslint": {},
|
|
13
|
+
"lint:eslint:fix": {},
|
|
14
|
+
"lint": {},
|
|
15
|
+
"lint:fix": {},
|
|
16
|
+
"check:deps:usage": {}
|
|
17
|
+
}
|
|
18
|
+
}
|
package/src/cn.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type ClassValue, clsx } from 'clsx'
|
|
2
|
+
import { extendTailwindMerge } from 'tailwind-merge'
|
|
3
|
+
import { typographyClasses } from './types'
|
|
4
|
+
|
|
5
|
+
/** Custom color classes for tailwind-merge conflict resolution */
|
|
6
|
+
const colorClasses = [
|
|
7
|
+
'foreground',
|
|
8
|
+
'background',
|
|
9
|
+
'neutral1',
|
|
10
|
+
'neutral1-light',
|
|
11
|
+
'neutral1-dark',
|
|
12
|
+
'neutral2',
|
|
13
|
+
'neutral2-light',
|
|
14
|
+
'neutral2-dark',
|
|
15
|
+
'neutral3',
|
|
16
|
+
'neutral3-light',
|
|
17
|
+
'neutral3-dark',
|
|
18
|
+
'surface1',
|
|
19
|
+
'surface1-light',
|
|
20
|
+
'surface1-dark',
|
|
21
|
+
'surface2',
|
|
22
|
+
'surface2-light',
|
|
23
|
+
'surface2-dark',
|
|
24
|
+
'surface3',
|
|
25
|
+
'surface3-light',
|
|
26
|
+
'surface3-dark',
|
|
27
|
+
'surface4',
|
|
28
|
+
'surface4-light',
|
|
29
|
+
'surface4-dark',
|
|
30
|
+
'surface5',
|
|
31
|
+
'surface5-light',
|
|
32
|
+
'surface5-dark',
|
|
33
|
+
'accent1',
|
|
34
|
+
'accent1-light',
|
|
35
|
+
'accent1-dark',
|
|
36
|
+
'accent2',
|
|
37
|
+
'accent2-light',
|
|
38
|
+
'accent2-dark',
|
|
39
|
+
'success',
|
|
40
|
+
'warning',
|
|
41
|
+
'critical',
|
|
42
|
+
'destructive',
|
|
43
|
+
'muted-foreground',
|
|
44
|
+
'card-foreground',
|
|
45
|
+
'popover-foreground',
|
|
46
|
+
'primary-foreground',
|
|
47
|
+
'secondary-foreground',
|
|
48
|
+
'destructive-foreground',
|
|
49
|
+
] as const
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Extended tailwind-merge configuration for Mycelium classes.
|
|
53
|
+
* - Typography: ensures cn('text-sm', 'text-body-1') correctly resolves to 'text-body-1'
|
|
54
|
+
* - Colors: ensures cn('text-foreground', 'text-critical') correctly resolves to 'text-critical'
|
|
55
|
+
*/
|
|
56
|
+
const customTwMerge = extendTailwindMerge({
|
|
57
|
+
extend: {
|
|
58
|
+
classGroups: {
|
|
59
|
+
'font-size': [...typographyClasses],
|
|
60
|
+
'text-color': colorClasses.map((c) => `text-${c}`),
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Merge class names with Tailwind CSS conflict resolution.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* cn('text-sm', 'text-body-1') // => 'text-body-1'
|
|
70
|
+
* cn('bg-red-500', isActive && 'bg-blue-500') // => 'bg-blue-500' if isActive
|
|
71
|
+
* cn('p-4', className) // Merge with external className prop
|
|
72
|
+
*/
|
|
73
|
+
export function cn(...inputs: ClassValue[]): string {
|
|
74
|
+
return customTwMerge(clsx(inputs))
|
|
75
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Mycelium - Lx's Tailwind Design System
|
|
2
|
+
// Main JS exports
|
|
3
|
+
|
|
4
|
+
export { cn } from './cn'
|
|
5
|
+
export * from './types'
|
|
6
|
+
export { COLOR_COUNT, UNICON_COLORS } from './unicon/colors'
|
|
7
|
+
export { hashString } from './unicon/hash'
|
|
8
|
+
export { Unicon, type UniconProps } from './unicon/Unicon'
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** Typography class names for tailwind-merge conflict resolution */
|
|
2
|
+
export const typographyClasses = [
|
|
3
|
+
'text-body-1',
|
|
4
|
+
'text-body-2',
|
|
5
|
+
'text-body-3',
|
|
6
|
+
'text-body-4',
|
|
7
|
+
'text-heading-1',
|
|
8
|
+
'text-heading-2',
|
|
9
|
+
'text-heading-3',
|
|
10
|
+
'text-subheading-1',
|
|
11
|
+
'text-subheading-2',
|
|
12
|
+
'text-button-1',
|
|
13
|
+
'text-button-2',
|
|
14
|
+
'text-button-3',
|
|
15
|
+
'text-button-4',
|
|
16
|
+
] as const
|
|
17
|
+
|
|
18
|
+
/** Mycelium typography class type */
|
|
19
|
+
export type TypographyClass = (typeof typographyClasses)[number]
|
|
20
|
+
|
|
21
|
+
/** Mycelium color tokens */
|
|
22
|
+
export type ColorToken =
|
|
23
|
+
| 'white'
|
|
24
|
+
| 'black'
|
|
25
|
+
| 'background'
|
|
26
|
+
| 'neutral1'
|
|
27
|
+
| 'neutral2'
|
|
28
|
+
| 'neutral3'
|
|
29
|
+
| 'surface1'
|
|
30
|
+
| 'surface2'
|
|
31
|
+
| 'surface3'
|
|
32
|
+
| 'surface4'
|
|
33
|
+
| 'surface5'
|
|
34
|
+
| 'accent1'
|
|
35
|
+
| 'accent2'
|
|
36
|
+
| 'accent3'
|
|
37
|
+
| 'accent4'
|
|
38
|
+
| 'success'
|
|
39
|
+
| 'critical'
|
|
40
|
+
| 'warning'
|
|
41
|
+
|
|
42
|
+
/** Mycelium screen breakpoints */
|
|
43
|
+
export type Breakpoint = 'xxs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | 'xxxl' | 'h-short' | 'h-mid'
|
|
44
|
+
|
|
45
|
+
/** Mycelium border radius tokens */
|
|
46
|
+
export type BorderRadius = 'none' | '4' | '6' | '8' | '12' | '16' | '20' | '24' | '28' | '32' | 'full'
|
|
47
|
+
|
|
48
|
+
/** Mycelium box shadow tokens */
|
|
49
|
+
export type BoxShadow = 'short' | 'medium' | 'large'
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
|
+
|
|
3
|
+
import { cn } from '../cn'
|
|
4
|
+
import { COLOR_COUNT } from './colors'
|
|
5
|
+
import { hashString } from './hash'
|
|
6
|
+
import { type IconPaths, Icons } from './icons'
|
|
7
|
+
|
|
8
|
+
export interface UniconProps {
|
|
9
|
+
/** Any string for deterministic avatar generation */
|
|
10
|
+
input: string
|
|
11
|
+
/** Size in pixels (default: 32) */
|
|
12
|
+
size?: number
|
|
13
|
+
/** Additional CSS classes */
|
|
14
|
+
className?: string
|
|
15
|
+
/** Custom icon to render instead of the default generated icon. Will be colored with the computed unicon color. */
|
|
16
|
+
icon?: React.ReactNode
|
|
17
|
+
/** When true, removes the background circle and scales the shape to fill the full container. */
|
|
18
|
+
bare?: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Deterministic avatar component that generates a unique visual identity
|
|
23
|
+
* based on any input string (e.g., wallet address, username, email).
|
|
24
|
+
*
|
|
25
|
+
* Colors automatically adapt to light/dark mode via CSS variables.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* <Unicon input="0x1234..." size={48} />
|
|
30
|
+
* <Unicon input="user@example.com" size={32} className="border border-neutral1" />
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function Unicon({ input, size = 32, className, icon, bare }: UniconProps): React.ReactElement {
|
|
34
|
+
const { colorVar, paths } = useMemo(() => {
|
|
35
|
+
const hash = hashString(input)
|
|
36
|
+
const iconKeys = Object.keys(Icons)
|
|
37
|
+
|
|
38
|
+
// Use BigInt modulo for both to avoid precision loss when converting to Number
|
|
39
|
+
const colorIndex = Number(hash % BigInt(COLOR_COUNT))
|
|
40
|
+
const iconIndex = Number(hash % BigInt(iconKeys.length))
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
colorVar: `var(--unicon-${colorIndex})`,
|
|
44
|
+
paths: Icons[iconKeys[iconIndex] as keyof typeof Icons] as IconPaths,
|
|
45
|
+
}
|
|
46
|
+
}, [input])
|
|
47
|
+
|
|
48
|
+
const scale = bare ? size / 48 : (size / 48 / 1.5) * 0.9
|
|
49
|
+
const scaledSize = 48 * scale
|
|
50
|
+
const translate = (size - scaledSize) / 2
|
|
51
|
+
|
|
52
|
+
// For custom icons, center them at ~40% of the container size
|
|
53
|
+
const iconSize = Math.round(size * 0.4)
|
|
54
|
+
const iconOffset = (size - iconSize) / 2
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} className={cn('shrink-0', className)}>
|
|
58
|
+
{!bare && <circle cx={size / 2} cy={size / 2} r={size / 2} fill={colorVar} opacity="var(--unicon-bg-opacity)" />}
|
|
59
|
+
{icon ? (
|
|
60
|
+
<foreignObject x={iconOffset} y={iconOffset} width={iconSize} height={iconSize}>
|
|
61
|
+
{/* biome-ignore lint/correctness/noRestrictedElements: div required inside SVG foreignObject */}
|
|
62
|
+
<div
|
|
63
|
+
style={{
|
|
64
|
+
width: '100%',
|
|
65
|
+
height: '100%',
|
|
66
|
+
display: 'flex',
|
|
67
|
+
alignItems: 'center',
|
|
68
|
+
justifyContent: 'center',
|
|
69
|
+
color: colorVar,
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
{icon}
|
|
73
|
+
</div>
|
|
74
|
+
</foreignObject>
|
|
75
|
+
) : (
|
|
76
|
+
<g transform={`translate(${translate}, ${translate}) scale(${scale})`}>
|
|
77
|
+
{paths.map((d, i) => (
|
|
78
|
+
<path key={i} d={d} fill={colorVar} clipRule="evenodd" fillRule="evenodd" />
|
|
79
|
+
))}
|
|
80
|
+
</g>
|
|
81
|
+
)}
|
|
82
|
+
</svg>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unicon color definitions
|
|
3
|
+
* Light and dark mode color pairs for avatar generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const UNICON_COLORS = {
|
|
7
|
+
light: [
|
|
8
|
+
'#F50DB4', // 0 - pink
|
|
9
|
+
'#FFBF17', // 1 - yellow
|
|
10
|
+
'#FF8934', // 2 - orange
|
|
11
|
+
'#85754A', // 3 - brown
|
|
12
|
+
'#0C8911', // 4 - green
|
|
13
|
+
'#78E744', // 5 - lime
|
|
14
|
+
'#00C3A0', // 6 - teal
|
|
15
|
+
'#23A3FF', // 7 - blue
|
|
16
|
+
'#4981FF', // 8 - indigo
|
|
17
|
+
'#4300B0', // 9 - purple
|
|
18
|
+
],
|
|
19
|
+
dark: [
|
|
20
|
+
'#FC74FE', // 0 - pink
|
|
21
|
+
'#FFF612', // 1 - yellow
|
|
22
|
+
'#FF4D00', // 2 - orange
|
|
23
|
+
'#996F01', // 3 - brown
|
|
24
|
+
'#21C95E', // 4 - green
|
|
25
|
+
'#B1F13C', // 5 - lime
|
|
26
|
+
'#5CFE9D', // 6 - teal
|
|
27
|
+
'#3ADCFF', // 7 - blue
|
|
28
|
+
'#0047FF', // 8 - indigo
|
|
29
|
+
'#9E62FF', // 9 - purple
|
|
30
|
+
],
|
|
31
|
+
} as const
|
|
32
|
+
|
|
33
|
+
export const COLOR_COUNT = 10
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// biome-ignore-all lint/suspicious/noBitwiseOperators: Hash function requires bitwise ops
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* cyrb53 - fast, well-distributed 53-bit hash
|
|
5
|
+
* No dependencies, excellent distribution for avatar generation
|
|
6
|
+
*/
|
|
7
|
+
export function hashString(str: string, seed = 0): bigint {
|
|
8
|
+
let h1 = 0xdeadbeef ^ seed
|
|
9
|
+
let h2 = 0x41c6ce57 ^ seed
|
|
10
|
+
for (let i = 0; i < str.length; i++) {
|
|
11
|
+
const ch = str.charCodeAt(i)
|
|
12
|
+
h1 = Math.imul(h1 ^ ch, 2654435761)
|
|
13
|
+
h2 = Math.imul(h2 ^ ch, 1597334677)
|
|
14
|
+
}
|
|
15
|
+
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507)
|
|
16
|
+
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909)
|
|
17
|
+
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507)
|
|
18
|
+
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909)
|
|
19
|
+
return BigInt(h2 >>> 0) * BigInt(0x100000000) + BigInt(h1 >>> 0)
|
|
20
|
+
}
|