@softium/ui 0.1.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/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@softium/ui",
3
+ "version": "0.1.0",
4
+ "description": "General softium-ui React primitives and app-shell layout: Button, AppShell, Sidebar, Header, ThemeToggle.",
5
+ "license": "MIT",
6
+ "author": "Sunghoon Lee",
7
+ "homepage": "https://github.com/HOONY-LEE/softium-ui/tree/main/packages/ui#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/HOONY-LEE/softium-ui.git",
11
+ "directory": "packages/ui"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/HOONY-LEE/softium-ui/issues"
15
+ },
16
+ "keywords": [
17
+ "react",
18
+ "ui",
19
+ "components",
20
+ "app-shell",
21
+ "erp",
22
+ "softium-ui"
23
+ ],
24
+ "type": "module",
25
+ "sideEffects": [
26
+ "*.css"
27
+ ],
28
+ "files": [
29
+ "dist",
30
+ "src/styles",
31
+ "LICENSE"
32
+ ],
33
+ "main": "./dist/index.cjs",
34
+ "module": "./dist/index.js",
35
+ "types": "./dist/index.d.ts",
36
+ "exports": {
37
+ ".": {
38
+ "types": "./dist/index.d.ts",
39
+ "import": "./dist/index.js",
40
+ "require": "./dist/index.cjs"
41
+ },
42
+ "./styles.css": "./dist/styles.css"
43
+ },
44
+ "peerDependencies": {
45
+ "react": "^18.0.0 || ^19.0.0",
46
+ "react-dom": "^18.0.0 || ^19.0.0"
47
+ },
48
+ "dependencies": {
49
+ "lucide-react": "^0.460.0",
50
+ "@softium/styles": "0.1.0"
51
+ },
52
+ "devDependencies": {
53
+ "@types/react": "^18.3.18",
54
+ "@types/react-dom": "^18.3.5",
55
+ "react": "^18.3.1",
56
+ "react-dom": "^18.3.1",
57
+ "tsup": "^8.3.5",
58
+ "typescript": "^5.7.3",
59
+ "vitest": "^2.1.8"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public"
63
+ },
64
+ "scripts": {
65
+ "build": "tsup && node ../../scripts/add-use-client.mjs dist/index.js dist/index.cjs && node ../../scripts/bundle-css.mjs styles.css dist/styles.css",
66
+ "dev": "tsup --watch",
67
+ "typecheck": "tsc --noEmit",
68
+ "test": "vitest run --passWithNoTests"
69
+ }
70
+ }
@@ -0,0 +1,558 @@
1
+ /**
2
+ * @softium/ui — component styles. Token-driven (see @softium/styles), so the whole
3
+ * shell re-skins on theme change with zero component churn.
4
+ */
5
+
6
+ :root {
7
+ --sft-sidebar-width: 224px;
8
+ --sft-sidebar-collapsed-width: 64px;
9
+ --sft-header-height: 56px;
10
+ }
11
+
12
+ /* ── Button ─────────────────────────────────────────────── */
13
+ .sft-button {
14
+ display: inline-flex;
15
+ align-items: center;
16
+ justify-content: center;
17
+ gap: var(--sft-space-2);
18
+ font: inherit;
19
+ font-weight: var(--sft-font-weight-medium);
20
+ line-height: 1;
21
+ border: 1px solid transparent;
22
+ border-radius: var(--sft-radius-md);
23
+ cursor: pointer;
24
+ white-space: nowrap;
25
+ transition: background var(--sft-duration-fast) var(--sft-ease), border-color
26
+ var(--sft-duration-fast) var(--sft-ease), color var(--sft-duration-fast) var(--sft-ease);
27
+ }
28
+
29
+ .sft-button:disabled {
30
+ opacity: 0.45;
31
+ cursor: default;
32
+ }
33
+
34
+ .sft-button--sm {
35
+ height: 30px;
36
+ padding: 0 var(--sft-space-3);
37
+ font-size: var(--sft-font-size-sm);
38
+ }
39
+
40
+ .sft-button--md {
41
+ height: 38px;
42
+ padding: 0 var(--sft-space-4);
43
+ font-size: var(--sft-font-size-md);
44
+ }
45
+
46
+ .sft-button--block {
47
+ width: 100%;
48
+ }
49
+
50
+ .sft-button--primary {
51
+ background: var(--sft-color-accent);
52
+ color: var(--sft-color-text-on-accent);
53
+ }
54
+ .sft-button--primary:not(:disabled):hover {
55
+ background: var(--sft-color-accent-hover);
56
+ }
57
+
58
+ .sft-button--secondary {
59
+ background: var(--sft-color-surface);
60
+ border-color: var(--sft-color-border);
61
+ color: var(--sft-color-text);
62
+ }
63
+ .sft-button--secondary:not(:disabled):hover {
64
+ background: var(--sft-color-bg-hover);
65
+ }
66
+
67
+ .sft-button--ghost {
68
+ background: transparent;
69
+ color: var(--sft-color-text);
70
+ }
71
+ .sft-button--ghost:not(:disabled):hover {
72
+ background: var(--sft-color-bg-hover);
73
+ }
74
+
75
+ .sft-button--danger {
76
+ background: var(--sft-color-danger);
77
+ color: #fff;
78
+ }
79
+ .sft-button--danger:not(:disabled):hover {
80
+ filter: brightness(0.94);
81
+ }
82
+
83
+ .sft-button__icon {
84
+ display: inline-flex;
85
+ align-items: center;
86
+ }
87
+
88
+ /* ── Switch ─────────────────────────────────────────────── */
89
+ .sft-switch {
90
+ display: inline-flex;
91
+ align-items: center;
92
+ gap: var(--sft-space-2);
93
+ cursor: pointer;
94
+ user-select: none;
95
+ }
96
+
97
+ .sft-switch--disabled {
98
+ opacity: 0.5;
99
+ cursor: default;
100
+ }
101
+
102
+ .sft-switch__input {
103
+ position: absolute;
104
+ width: 1px;
105
+ height: 1px;
106
+ opacity: 0;
107
+ pointer-events: none;
108
+ }
109
+
110
+ .sft-switch__track {
111
+ position: relative;
112
+ flex: none;
113
+ width: 34px;
114
+ height: 20px;
115
+ border-radius: var(--sft-radius-full);
116
+ background: var(--sft-color-border-strong);
117
+ transition: background var(--sft-duration-fast) var(--sft-ease);
118
+ }
119
+
120
+ .sft-switch__thumb {
121
+ position: absolute;
122
+ top: 2px;
123
+ left: 2px;
124
+ width: 16px;
125
+ height: 16px;
126
+ border-radius: 50%;
127
+ background: #fff;
128
+ box-shadow: var(--sft-shadow-sm);
129
+ transition: transform var(--sft-duration-fast) var(--sft-ease);
130
+ }
131
+
132
+ .sft-switch__input:checked + .sft-switch__track {
133
+ background: var(--sft-color-accent);
134
+ }
135
+
136
+ .sft-switch__input:checked + .sft-switch__track .sft-switch__thumb {
137
+ transform: translateX(14px);
138
+ }
139
+
140
+ .sft-switch__input:focus-visible + .sft-switch__track {
141
+ outline: 2px solid var(--sft-color-accent);
142
+ outline-offset: 2px;
143
+ }
144
+
145
+ .sft-switch__label {
146
+ font-size: var(--sft-font-size-sm);
147
+ color: var(--sft-color-text);
148
+ }
149
+
150
+ /* ── AppShell (fixed sidebar + scrolling body) ──────────── */
151
+ .sft-shell {
152
+ min-height: 100vh;
153
+ background: var(--sft-color-bg);
154
+ }
155
+
156
+ .sft-shell__sidebar {
157
+ position: fixed;
158
+ top: 0;
159
+ left: 0;
160
+ height: 100vh;
161
+ width: var(--sft-sidebar-width);
162
+ overflow-x: hidden;
163
+ overflow-y: auto;
164
+ border-right: 1px solid var(--sft-color-border);
165
+ background: transparent;
166
+ z-index: var(--sft-z-header);
167
+ transition: width var(--sft-duration-base) var(--sft-ease), transform var(--sft-duration-base)
168
+ var(--sft-ease);
169
+ }
170
+
171
+ .sft-shell[data-collapsed] .sft-shell__sidebar {
172
+ width: var(--sft-sidebar-collapsed-width);
173
+ }
174
+
175
+ .sft-shell__main {
176
+ display: flex;
177
+ flex-direction: column;
178
+ min-width: 0;
179
+ height: 100vh;
180
+ overflow-y: auto;
181
+ overscroll-behavior-y: none;
182
+ margin-left: var(--sft-sidebar-width);
183
+ transition: margin-left var(--sft-duration-base) var(--sft-ease);
184
+ }
185
+
186
+ .sft-shell[data-collapsed] .sft-shell__main {
187
+ margin-left: var(--sft-sidebar-collapsed-width);
188
+ }
189
+
190
+ .sft-shell__header {
191
+ position: sticky;
192
+ top: 0;
193
+ z-index: var(--sft-z-header);
194
+ flex: none;
195
+ }
196
+
197
+ .sft-shell__content {
198
+ flex: 1;
199
+ min-height: 0;
200
+ padding: var(--sft-space-6);
201
+ }
202
+
203
+ .sft-shell__overlay {
204
+ display: none;
205
+ }
206
+
207
+ /* ── Header ─────────────────────────────────────────────── */
208
+ .sft-header {
209
+ display: flex;
210
+ align-items: center;
211
+ gap: var(--sft-space-4);
212
+ height: var(--sft-header-height);
213
+ padding: 0 var(--sft-space-5);
214
+ border-bottom: 1px solid var(--sft-color-border);
215
+ background: var(--sft-color-surface);
216
+ }
217
+
218
+ .sft-header__menu {
219
+ display: none;
220
+ align-items: center;
221
+ }
222
+
223
+ .sft-header__left {
224
+ min-width: 0;
225
+ display: flex;
226
+ align-items: center;
227
+ gap: var(--sft-space-2);
228
+ margin-right: auto;
229
+ }
230
+
231
+ .sft-header__title {
232
+ margin: 0;
233
+ font-size: var(--sft-font-size-lg);
234
+ font-weight: var(--sft-font-weight-bold);
235
+ color: var(--sft-color-text);
236
+ white-space: nowrap;
237
+ overflow: hidden;
238
+ text-overflow: ellipsis;
239
+ }
240
+
241
+ .sft-header__nav {
242
+ display: flex;
243
+ align-items: center;
244
+ gap: var(--sft-space-2);
245
+ height: 100%;
246
+ }
247
+
248
+ .sft-header__actions {
249
+ display: flex;
250
+ align-items: center;
251
+ gap: var(--sft-space-2);
252
+ margin-left: auto;
253
+ }
254
+
255
+ /* header breadcrumb (root / current) */
256
+ .sft-breadcrumb {
257
+ display: flex;
258
+ align-items: center;
259
+ gap: var(--sft-space-2);
260
+ min-width: 0;
261
+ }
262
+ .sft-breadcrumb__root {
263
+ font-size: var(--sft-font-size-md);
264
+ color: var(--sft-color-text-muted);
265
+ white-space: nowrap;
266
+ }
267
+ .sft-breadcrumb__sep {
268
+ color: var(--sft-color-border-strong);
269
+ }
270
+ .sft-breadcrumb__current {
271
+ font-size: var(--sft-font-size-md);
272
+ font-weight: var(--sft-font-weight-bold);
273
+ color: var(--sft-color-text);
274
+ white-space: nowrap;
275
+ overflow: hidden;
276
+ text-overflow: ellipsis;
277
+ }
278
+
279
+ /* ── Sidebar ────────────────────────────────────────────── */
280
+ .sft-sidebar {
281
+ display: flex;
282
+ flex-direction: column;
283
+ height: 100%;
284
+ }
285
+
286
+ .sft-sidebar__header {
287
+ display: flex;
288
+ align-items: center;
289
+ gap: var(--sft-space-2);
290
+ height: var(--sft-header-height);
291
+ padding: 0 var(--sft-space-4);
292
+ border-bottom: 1px solid var(--sft-color-border);
293
+ box-sizing: border-box;
294
+ flex: none;
295
+ }
296
+
297
+ .sft-sidebar__header-content {
298
+ flex: 1;
299
+ min-width: 0;
300
+ }
301
+
302
+ .sft-sidebar__collapse {
303
+ display: flex;
304
+ align-items: center;
305
+ justify-content: center;
306
+ width: 32px;
307
+ height: 32px;
308
+ border: 1px solid var(--sft-color-border);
309
+ border-radius: var(--sft-radius-md);
310
+ background: var(--sft-color-surface);
311
+ color: var(--sft-color-text-secondary);
312
+ cursor: pointer;
313
+ flex: none;
314
+ transition: background var(--sft-duration-fast) var(--sft-ease), color var(--sft-duration-fast)
315
+ var(--sft-ease);
316
+ }
317
+ .sft-sidebar__collapse:hover {
318
+ background: var(--sft-color-bg-hover);
319
+ color: var(--sft-color-text);
320
+ }
321
+
322
+ .sft-sidebar__collapse-icon {
323
+ transition: transform var(--sft-duration-base) var(--sft-ease);
324
+ }
325
+ .sft-sidebar__collapse-icon--flipped {
326
+ transform: rotate(180deg);
327
+ }
328
+
329
+ .sft-sidebar__content {
330
+ flex: 1;
331
+ overflow-y: auto;
332
+ overflow-x: hidden;
333
+ padding: var(--sft-space-4) var(--sft-space-3);
334
+ display: flex;
335
+ flex-direction: column;
336
+ gap: var(--sft-space-4);
337
+ }
338
+
339
+ .sft-sidebar__footer {
340
+ padding: var(--sft-space-4) var(--sft-space-4);
341
+ border-top: 1px solid var(--sft-color-border);
342
+ flex: none;
343
+ }
344
+
345
+ .sft-sidebar__brand {
346
+ display: flex;
347
+ align-items: center;
348
+ gap: var(--sft-space-2);
349
+ min-width: 0;
350
+ }
351
+
352
+ .sft-sidebar__logo {
353
+ display: inline-flex;
354
+ align-items: center;
355
+ justify-content: center;
356
+ width: 28px;
357
+ height: 28px;
358
+ border-radius: var(--sft-radius-md);
359
+ background: var(--sft-color-accent);
360
+ color: var(--sft-color-text-on-accent);
361
+ font-weight: var(--sft-font-weight-bold);
362
+ flex: none;
363
+ }
364
+
365
+ .sft-sidebar__brand-text {
366
+ display: flex;
367
+ flex-direction: column;
368
+ line-height: 1.2;
369
+ min-width: 0;
370
+ }
371
+
372
+ .sft-sidebar__title {
373
+ font-weight: var(--sft-font-weight-bold);
374
+ color: var(--sft-color-text);
375
+ white-space: nowrap;
376
+ overflow: hidden;
377
+ text-overflow: ellipsis;
378
+ }
379
+
380
+ .sft-sidebar__subtitle {
381
+ font-size: var(--sft-font-size-xs);
382
+ color: var(--sft-color-text-muted);
383
+ }
384
+
385
+ .sft-sidebar__section {
386
+ display: flex;
387
+ flex-direction: column;
388
+ gap: 2px;
389
+ }
390
+
391
+ .sft-sidebar__section-label,
392
+ .sft-sidebar__section-toggle {
393
+ padding: var(--sft-space-1) var(--sft-space-2);
394
+ font-size: var(--sft-font-size-xs);
395
+ font-weight: var(--sft-font-weight-bold);
396
+ letter-spacing: 0.04em;
397
+ text-transform: uppercase;
398
+ color: var(--sft-color-text-muted);
399
+ }
400
+
401
+ .sft-sidebar__section-toggle {
402
+ display: flex;
403
+ align-items: center;
404
+ justify-content: space-between;
405
+ width: 100%;
406
+ border: none;
407
+ background: none;
408
+ border-radius: var(--sft-radius-sm);
409
+ cursor: pointer;
410
+ font-family: inherit;
411
+ transition: background var(--sft-duration-fast) var(--sft-ease), color var(--sft-duration-fast)
412
+ var(--sft-ease);
413
+ }
414
+ .sft-sidebar__section-toggle:hover {
415
+ background: var(--sft-color-bg-hover);
416
+ color: var(--sft-color-text-secondary);
417
+ }
418
+
419
+ .sft-sidebar__section-chevron {
420
+ flex: none;
421
+ transition: transform var(--sft-duration-fast) var(--sft-ease);
422
+ transform: rotate(-90deg);
423
+ }
424
+ .sft-sidebar__section-chevron--open {
425
+ transform: rotate(0deg);
426
+ }
427
+
428
+ .sft-sidebar__items {
429
+ display: flex;
430
+ flex-direction: column;
431
+ gap: 1px;
432
+ }
433
+
434
+ .sft-sidebar__item {
435
+ display: flex;
436
+ align-items: center;
437
+ gap: var(--sft-space-2);
438
+ width: 100%;
439
+ padding: 9px var(--sft-space-2);
440
+ border: none;
441
+ border-radius: var(--sft-radius-sm);
442
+ background: transparent;
443
+ color: var(--sft-color-text-secondary);
444
+ font: inherit;
445
+ font-size: var(--sft-font-size-lg);
446
+ text-align: left;
447
+ cursor: pointer;
448
+ transition: background var(--sft-duration-fast) var(--sft-ease);
449
+ }
450
+
451
+ .sft-sidebar__item:hover {
452
+ background: var(--sft-color-bg-hover);
453
+ color: var(--sft-color-text);
454
+ }
455
+
456
+ .sft-sidebar__item--active {
457
+ background: var(--sft-color-accent-subtle);
458
+ color: var(--sft-color-accent);
459
+ font-weight: var(--sft-font-weight-medium);
460
+ }
461
+
462
+ .sft-sidebar__item-icon {
463
+ display: inline-flex;
464
+ width: 20px;
465
+ justify-content: center;
466
+ flex: none;
467
+ }
468
+
469
+ .sft-sidebar__item-label {
470
+ flex: 1;
471
+ min-width: 0;
472
+ overflow: hidden;
473
+ text-overflow: ellipsis;
474
+ white-space: nowrap;
475
+ }
476
+
477
+ .sft-sidebar__tooltip {
478
+ position: fixed;
479
+ transform: translateY(-50%);
480
+ padding: var(--sft-space-1) var(--sft-space-2);
481
+ font-size: var(--sft-font-size-xs);
482
+ font-weight: var(--sft-font-weight-medium);
483
+ color: var(--sft-color-bg);
484
+ background: var(--sft-color-text);
485
+ border-radius: var(--sft-radius-sm);
486
+ white-space: nowrap;
487
+ z-index: var(--sft-z-modal);
488
+ pointer-events: none;
489
+ }
490
+
491
+ /* ── Sidebar: collapsed (icon-only rail) ────────────────── */
492
+ .sft-sidebar--collapsed .sft-sidebar__header {
493
+ justify-content: center;
494
+ padding: 0;
495
+ }
496
+ .sft-sidebar--collapsed .sft-sidebar__header-content,
497
+ .sft-sidebar--collapsed .sft-sidebar__footer,
498
+ .sft-sidebar--collapsed .sft-sidebar__section-label,
499
+ .sft-sidebar--collapsed .sft-sidebar__section-toggle,
500
+ .sft-sidebar--collapsed .sft-sidebar__item-label {
501
+ display: none;
502
+ }
503
+ .sft-sidebar--collapsed .sft-sidebar__content {
504
+ padding: var(--sft-space-4) 0;
505
+ align-items: center;
506
+ }
507
+ .sft-sidebar--collapsed .sft-sidebar__section {
508
+ width: 100%;
509
+ align-items: center;
510
+ }
511
+ .sft-sidebar--collapsed .sft-sidebar__items {
512
+ width: 100%;
513
+ align-items: center;
514
+ }
515
+ .sft-sidebar--collapsed .sft-sidebar__item {
516
+ width: 42px;
517
+ height: 42px;
518
+ padding: 0;
519
+ justify-content: center;
520
+ }
521
+ .sft-sidebar--collapsed .sft-sidebar__item-icon {
522
+ width: auto;
523
+ }
524
+
525
+ /* ── responsive: tablet auto-rail, mobile drawer ────────── */
526
+ @media (max-width: 1024px) and (min-width: 769px) {
527
+ .sft-shell__sidebar {
528
+ width: var(--sft-sidebar-collapsed-width);
529
+ }
530
+ .sft-shell__main {
531
+ margin-left: var(--sft-sidebar-collapsed-width);
532
+ }
533
+ }
534
+
535
+ @media (max-width: 768px) {
536
+ .sft-shell__sidebar {
537
+ transform: translateX(-100%);
538
+ width: var(--sft-sidebar-width);
539
+ background: var(--sft-color-bg);
540
+ z-index: calc(var(--sft-z-overlay) + 1);
541
+ }
542
+ .sft-shell[data-drawer-open] .sft-shell__sidebar {
543
+ transform: translateX(0);
544
+ }
545
+ .sft-shell__main {
546
+ margin-left: 0;
547
+ }
548
+ .sft-shell__overlay {
549
+ display: block;
550
+ position: fixed;
551
+ inset: 0;
552
+ background: rgba(16, 24, 40, 0.45);
553
+ z-index: var(--sft-z-overlay);
554
+ }
555
+ .sft-header__menu {
556
+ display: flex;
557
+ }
558
+ }