@aortl/admin-css 0.0.1 → 0.2.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/README.md +0 -22
- package/dist/admin.css +2918 -248
- package/dist/admin.min.css +1 -1
- package/dist/admin.scoped.css +3383 -0
- package/dist/admin.scoped.min.css +46 -0
- package/package.json +15 -3
- package/src/base.css +13 -7
- package/src/components/accordion.css +79 -0
- package/src/components/alert.css +83 -0
- package/src/components/app-shell.css +59 -0
- package/src/components/badge.css +44 -0
- package/src/components/brand-tile.css +9 -0
- package/src/components/breadcrumbs.css +38 -0
- package/src/components/button-group.css +73 -0
- package/src/components/button.css +50 -1
- package/src/components/card.css +1 -1
- package/src/components/checkbox.css +38 -0
- package/src/components/dialog.css +91 -0
- package/src/components/field.css +29 -2
- package/src/components/file-input.css +36 -0
- package/src/components/footer.css +26 -0
- package/src/components/index.css +24 -0
- package/src/components/input-group.css +38 -0
- package/src/components/input.css +7 -0
- package/src/components/menu.css +88 -0
- package/src/components/navbar.css +66 -0
- package/src/components/pagination.css +43 -0
- package/src/components/progress.css +97 -0
- package/src/components/radio.css +45 -0
- package/src/components/select.css +114 -0
- package/src/components/sidebar.css +225 -0
- package/src/components/spinner.css +40 -0
- package/src/components/switch.css +62 -0
- package/src/components/table.css +124 -0
- package/src/components/tabs.css +172 -0
- package/src/components/textarea.css +33 -0
- package/src/fonts.css +88 -0
- package/src/index.css +1 -0
- package/src/theme.css +122 -29
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* Native <progress> styled across engines. `appearance: none` strips the
|
|
3
|
+
platform default; the track is the element itself, the fill comes from
|
|
4
|
+
::-webkit-progress-value / ::-moz-progress-bar. */
|
|
5
|
+
.progress {
|
|
6
|
+
appearance: none;
|
|
7
|
+
-webkit-appearance: none;
|
|
8
|
+
display: block;
|
|
9
|
+
width: 100%;
|
|
10
|
+
height: 0.375rem;
|
|
11
|
+
border: 0;
|
|
12
|
+
border-radius: 9999px;
|
|
13
|
+
overflow: hidden;
|
|
14
|
+
background-color: var(--color-surface-strong);
|
|
15
|
+
color: var(--color-primary);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.progress::-webkit-progress-bar {
|
|
19
|
+
background-color: var(--color-surface-strong);
|
|
20
|
+
border-radius: 9999px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.progress::-webkit-progress-value {
|
|
24
|
+
background-color: currentColor;
|
|
25
|
+
border-radius: 9999px;
|
|
26
|
+
transition: inline-size 200ms ease;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.progress::-moz-progress-bar {
|
|
30
|
+
background-color: currentColor;
|
|
31
|
+
border-radius: 9999px;
|
|
32
|
+
transition: inline-size 200ms ease;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Sizes */
|
|
36
|
+
.progress-sm {
|
|
37
|
+
height: 0.25rem;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.progress-lg {
|
|
41
|
+
height: 0.5rem;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Variants — recolour the fill via currentColor */
|
|
45
|
+
.progress-success {
|
|
46
|
+
color: var(--color-success);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.progress-warning {
|
|
50
|
+
color: var(--color-warning);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.progress-danger {
|
|
54
|
+
color: var(--color-danger);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Indeterminate — no value attribute. WebKit hides the value pseudo, Firefox
|
|
58
|
+
draws it full-width; in both cases we paint an animated gradient on the
|
|
59
|
+
bar itself. */
|
|
60
|
+
.progress:indeterminate {
|
|
61
|
+
background-image: linear-gradient(
|
|
62
|
+
90deg,
|
|
63
|
+
var(--color-surface-strong) 0%,
|
|
64
|
+
var(--color-surface-strong) 40%,
|
|
65
|
+
currentColor 50%,
|
|
66
|
+
var(--color-surface-strong) 60%,
|
|
67
|
+
var(--color-surface-strong) 100%
|
|
68
|
+
);
|
|
69
|
+
background-size: 250% 100%;
|
|
70
|
+
background-repeat: no-repeat;
|
|
71
|
+
animation: progress-indeterminate 1.2s linear infinite;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.progress:indeterminate::-webkit-progress-bar {
|
|
75
|
+
background-color: transparent;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.progress:indeterminate::-webkit-progress-value,
|
|
79
|
+
.progress:indeterminate::-moz-progress-bar {
|
|
80
|
+
background-color: transparent;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@media (prefers-reduced-motion: reduce) {
|
|
84
|
+
.progress:indeterminate {
|
|
85
|
+
animation-duration: 3s;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@keyframes progress-indeterminate {
|
|
90
|
+
from {
|
|
91
|
+
background-position: 100% 0;
|
|
92
|
+
}
|
|
93
|
+
to {
|
|
94
|
+
background-position: 0 0;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.radio {
|
|
3
|
+
@apply inline-flex items-center justify-center size-4 shrink-0
|
|
4
|
+
rounded-full border bg-surface
|
|
5
|
+
cursor-pointer
|
|
6
|
+
transition-colors duration-150
|
|
7
|
+
focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary
|
|
8
|
+
disabled:opacity-50 disabled:cursor-not-allowed;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.radio[data-unchecked] {
|
|
12
|
+
@apply border-border-strong hover:border-text-muted;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.radio[data-checked] {
|
|
16
|
+
@apply bg-primary border-primary hover:bg-primary-hover;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.radio[data-disabled] {
|
|
20
|
+
@apply opacity-50 cursor-not-allowed;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.radio-indicator {
|
|
24
|
+
@apply inline-flex size-1.5 rounded-full bg-primary-content;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.radio-group {
|
|
28
|
+
@apply inline-flex flex-wrap gap-4;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.radio-group-vertical {
|
|
32
|
+
@apply flex-col gap-2 items-start;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* A <label> wrapping a radio + text lays out inline with a small gap.
|
|
36
|
+
Covers both the vanilla input.radio and Base UI's span.radio. */
|
|
37
|
+
label:has(> .radio) {
|
|
38
|
+
@apply inline-flex items-center gap-2 cursor-pointer;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
label:has(> .radio:disabled),
|
|
42
|
+
label:has(> .radio[data-disabled]) {
|
|
43
|
+
@apply opacity-60 cursor-not-allowed;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.select {
|
|
3
|
+
@apply inline-flex items-center justify-between gap-2 w-full px-3 py-2
|
|
4
|
+
rounded-lg text-sm leading-none text-left
|
|
5
|
+
bg-surface text-text
|
|
6
|
+
border border-transparent
|
|
7
|
+
cursor-pointer select-none
|
|
8
|
+
transition-colors duration-150
|
|
9
|
+
focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary
|
|
10
|
+
disabled:opacity-50 disabled:cursor-not-allowed;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.select[data-popup-open] {
|
|
14
|
+
@apply outline-2 outline-offset-2 outline-primary;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.select[data-placeholder] {
|
|
18
|
+
@apply text-text-muted;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.select-bordered {
|
|
22
|
+
@apply border-border hover:border-border-strong;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.select-ghost {
|
|
26
|
+
@apply bg-transparent hover:bg-surface-muted;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.select-danger {
|
|
30
|
+
@apply border-danger focus-visible:outline-danger;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Sizes */
|
|
34
|
+
.select-sm {
|
|
35
|
+
@apply text-xs px-2.5 py-1.5;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.select-lg {
|
|
39
|
+
@apply text-base px-4 py-2.5;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Native <select> usage: same class, but suppress the native chevron and
|
|
43
|
+
supply our own via background-image so the look matches the Base UI
|
|
44
|
+
trigger. Chevron stroke uses Flexoki base-500, which reads as neutral
|
|
45
|
+
in both light and dark mode. */
|
|
46
|
+
select.select {
|
|
47
|
+
appearance: none;
|
|
48
|
+
padding-right: 2rem;
|
|
49
|
+
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23878580' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
|
|
50
|
+
background-repeat: no-repeat;
|
|
51
|
+
background-position: right 0.5rem center;
|
|
52
|
+
background-size: 1rem;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
select.select-sm {
|
|
56
|
+
padding-right: 1.75rem;
|
|
57
|
+
background-size: 0.875rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
select.select-lg {
|
|
61
|
+
padding-right: 2.25rem;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.select-icon {
|
|
65
|
+
@apply inline-flex items-center justify-center size-4 shrink-0
|
|
66
|
+
text-text-muted transition-transform duration-150;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.select[data-popup-open] .select-icon {
|
|
70
|
+
@apply rotate-180;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.select-popup {
|
|
74
|
+
@apply min-w-[var(--anchor-width)] max-h-72 overflow-auto
|
|
75
|
+
py-1 outline-none
|
|
76
|
+
bg-surface text-text
|
|
77
|
+
border border-border rounded-lg shadow-md
|
|
78
|
+
transition-[opacity,transform] duration-100;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.select-popup[data-starting-style],
|
|
82
|
+
.select-popup[data-ending-style] {
|
|
83
|
+
@apply opacity-0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.select-popup[data-starting-style] {
|
|
87
|
+
@apply -translate-y-1;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.select-item {
|
|
91
|
+
@apply flex items-center gap-2 px-3 py-1.5 text-sm
|
|
92
|
+
cursor-pointer select-none outline-none;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.select-item[data-highlighted] {
|
|
96
|
+
@apply bg-surface-muted;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.select-item[data-selected] {
|
|
100
|
+
@apply font-medium;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.select-item[data-disabled] {
|
|
104
|
+
@apply opacity-50 cursor-not-allowed;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.select-item-indicator {
|
|
108
|
+
@apply inline-flex items-center justify-center size-4 ml-auto text-primary;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.select-group-label {
|
|
112
|
+
@apply px-3 pt-2 pb-1 text-xs uppercase tracking-wide text-text-muted;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.sidebar {
|
|
3
|
+
@apply flex flex-col shrink-0
|
|
4
|
+
bg-surface-muted text-text
|
|
5
|
+
border-r border-border;
|
|
6
|
+
width: var(--app-shell-sidebar-w, 240px);
|
|
7
|
+
transition: width 150ms ease;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/* Hidden checkbox that drives the collapsed state via :has(). Lives inside
|
|
11
|
+
<Sidebar.CollapseToggle>, but :has() finds it from anywhere in .sidebar. */
|
|
12
|
+
.sidebar-toggle {
|
|
13
|
+
position: absolute;
|
|
14
|
+
width: 1px;
|
|
15
|
+
height: 1px;
|
|
16
|
+
margin: 0;
|
|
17
|
+
padding: 0;
|
|
18
|
+
border: 0;
|
|
19
|
+
opacity: 0;
|
|
20
|
+
pointer-events: none;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.sidebar:has(.sidebar-toggle:checked) {
|
|
24
|
+
width: var(--app-shell-sidebar-w-collapsed, 56px);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* Hide labels, group headings, badges, and tree panels in collapsed mode. */
|
|
28
|
+
.sidebar:has(.sidebar-toggle:checked) .sidebar-label,
|
|
29
|
+
.sidebar:has(.sidebar-toggle:checked) .sidebar-group-label,
|
|
30
|
+
.sidebar:has(.sidebar-toggle:checked) .sidebar-badge,
|
|
31
|
+
.sidebar:has(.sidebar-toggle:checked) .sidebar-collapsible-panel,
|
|
32
|
+
.sidebar:has(.sidebar-toggle:checked) .sidebar-collapsible-trigger::after {
|
|
33
|
+
display: none;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Center the icon in collapsed mode. */
|
|
37
|
+
.sidebar:has(.sidebar-toggle:checked) .sidebar-item,
|
|
38
|
+
.sidebar:has(.sidebar-toggle:checked) .sidebar-subitem,
|
|
39
|
+
.sidebar:has(.sidebar-toggle:checked) .sidebar-collapsible-trigger {
|
|
40
|
+
@apply justify-center px-1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.sidebar-header {
|
|
44
|
+
@apply flex items-center gap-2 px-3 h-12 shrink-0 border-b border-border;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.sidebar-nav {
|
|
48
|
+
@apply flex flex-col gap-0.5 px-2 py-2 flex-1 overflow-auto;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.sidebar-group {
|
|
52
|
+
@apply flex flex-col gap-0.5;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.sidebar-group + .sidebar-group {
|
|
56
|
+
@apply mt-2;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.sidebar-group-label {
|
|
60
|
+
@apply px-2 pt-2 pb-1 text-xs uppercase tracking-wide text-text-muted;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.sidebar-item {
|
|
64
|
+
@apply flex items-center gap-2 px-2 py-1.5
|
|
65
|
+
rounded-md text-sm leading-none text-text
|
|
66
|
+
bg-transparent hover:bg-surface-strong
|
|
67
|
+
cursor-pointer select-none no-underline
|
|
68
|
+
transition-colors duration-150
|
|
69
|
+
focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.sidebar-item[aria-current="page"],
|
|
73
|
+
.sidebar-item[data-active] {
|
|
74
|
+
@apply bg-primary-muted text-primary font-medium;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.sidebar-icon {
|
|
78
|
+
@apply inline-flex items-center justify-center size-4 shrink-0 text-text-muted;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.sidebar-item[aria-current="page"] .sidebar-icon,
|
|
82
|
+
.sidebar-item[data-active] .sidebar-icon {
|
|
83
|
+
@apply text-primary;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.sidebar-label {
|
|
87
|
+
@apply truncate min-w-0 flex-1;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.sidebar-badge {
|
|
91
|
+
@apply ml-auto inline-flex items-center justify-center min-w-5 px-1.5 h-5
|
|
92
|
+
rounded-full text-xs font-medium
|
|
93
|
+
bg-surface-strong text-text-muted;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Native <details> for tree groups. */
|
|
97
|
+
.sidebar-collapsible {
|
|
98
|
+
@apply flex flex-col;
|
|
99
|
+
interpolate-size: allow-keywords;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.sidebar-collapsible-trigger {
|
|
103
|
+
@apply flex items-center gap-2 w-full px-2 py-1.5
|
|
104
|
+
rounded-md text-sm leading-none text-text text-left
|
|
105
|
+
bg-transparent hover:bg-surface-strong
|
|
106
|
+
cursor-pointer select-none
|
|
107
|
+
transition-colors duration-150
|
|
108
|
+
focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary;
|
|
109
|
+
list-style: none;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.sidebar-collapsible-trigger::-webkit-details-marker {
|
|
113
|
+
display: none;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* Chevron — points right when closed, down when open. */
|
|
117
|
+
.sidebar-collapsible-trigger::after {
|
|
118
|
+
content: "";
|
|
119
|
+
margin-left: auto;
|
|
120
|
+
width: 0.375rem;
|
|
121
|
+
height: 0.375rem;
|
|
122
|
+
border-right: 2px solid currentColor;
|
|
123
|
+
border-bottom: 2px solid currentColor;
|
|
124
|
+
color: var(--color-text-muted);
|
|
125
|
+
transform: rotate(-45deg);
|
|
126
|
+
transition: transform 150ms ease;
|
|
127
|
+
flex-shrink: 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.sidebar-collapsible[open] > .sidebar-collapsible-trigger::after {
|
|
131
|
+
transform: rotate(45deg);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.sidebar-collapsible-panel {
|
|
135
|
+
@apply flex flex-col gap-0.5 pl-4 mt-0.5;
|
|
136
|
+
overflow: hidden;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* Smooth panel expansion via ::details-content (modern browsers).
|
|
140
|
+
Older browsers degrade to the native instant toggle. */
|
|
141
|
+
.sidebar-collapsible::details-content {
|
|
142
|
+
opacity: 0;
|
|
143
|
+
height: 0;
|
|
144
|
+
overflow: clip;
|
|
145
|
+
transition:
|
|
146
|
+
opacity 150ms ease,
|
|
147
|
+
height 150ms ease,
|
|
148
|
+
content-visibility 150ms;
|
|
149
|
+
transition-behavior: allow-discrete;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.sidebar-collapsible[open]::details-content {
|
|
153
|
+
opacity: 1;
|
|
154
|
+
height: auto;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.sidebar-subitem {
|
|
158
|
+
@apply flex items-center gap-2 px-2 py-1
|
|
159
|
+
rounded-md text-sm leading-none text-text
|
|
160
|
+
bg-transparent hover:bg-surface-strong
|
|
161
|
+
cursor-pointer select-none no-underline
|
|
162
|
+
transition-colors duration-150
|
|
163
|
+
focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.sidebar-subitem[aria-current="page"],
|
|
167
|
+
.sidebar-subitem[data-active] {
|
|
168
|
+
@apply bg-primary-muted text-primary font-medium;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.sidebar-footer {
|
|
172
|
+
@apply flex flex-col gap-1 px-2 py-2 border-t border-border;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* Label that toggles the hidden checkbox inside it. */
|
|
176
|
+
.sidebar-collapse-toggle {
|
|
177
|
+
@apply relative inline-flex items-center justify-center size-7
|
|
178
|
+
rounded-md text-text-muted bg-transparent
|
|
179
|
+
hover:bg-surface-strong hover:text-text
|
|
180
|
+
cursor-pointer select-none
|
|
181
|
+
transition-colors duration-150;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.sidebar-collapse-toggle:has(.sidebar-toggle:focus-visible) {
|
|
185
|
+
@apply outline-2 outline-offset-2 outline-primary;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* Chevron-left when expanded; flips to chevron-right when collapsed. */
|
|
189
|
+
.sidebar-collapse-toggle::before {
|
|
190
|
+
content: "";
|
|
191
|
+
width: 0.5rem;
|
|
192
|
+
height: 0.5rem;
|
|
193
|
+
border-left: 2px solid currentColor;
|
|
194
|
+
border-bottom: 2px solid currentColor;
|
|
195
|
+
transform: rotate(45deg);
|
|
196
|
+
transition: transform 150ms ease;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.sidebar:has(.sidebar-toggle:checked) .sidebar-collapse-toggle::before {
|
|
200
|
+
transform: rotate(-135deg);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* Mobile drawer surface (rendered inside a Base UI Dialog) */
|
|
204
|
+
.sidebar-drawer-backdrop {
|
|
205
|
+
@apply fixed inset-0 z-40 bg-black/40
|
|
206
|
+
transition-opacity duration-150;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.sidebar-drawer-backdrop[data-starting-style],
|
|
210
|
+
.sidebar-drawer-backdrop[data-ending-style] {
|
|
211
|
+
@apply opacity-0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.sidebar-drawer {
|
|
215
|
+
@apply fixed inset-y-0 left-0 z-50 flex flex-col
|
|
216
|
+
bg-surface-muted text-text border-r border-border
|
|
217
|
+
transition-transform duration-150;
|
|
218
|
+
width: min(var(--app-shell-sidebar-w, 240px), 80vw);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.sidebar-drawer[data-starting-style],
|
|
222
|
+
.sidebar-drawer[data-ending-style] {
|
|
223
|
+
@apply -translate-x-full;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* Border-driven spinner — the top edge takes the host's `currentColor` while
|
|
3
|
+
the rest stays muted, producing a visible rotating arc. Inline-block so it
|
|
4
|
+
drops into `.btn`, `.field-label`, etc., without disturbing flow layout. */
|
|
5
|
+
.spinner {
|
|
6
|
+
display: inline-block;
|
|
7
|
+
width: 1rem;
|
|
8
|
+
height: 1rem;
|
|
9
|
+
flex-shrink: 0;
|
|
10
|
+
border-radius: 9999px;
|
|
11
|
+
border: 2px solid color-mix(in oklab, currentColor 25%, transparent);
|
|
12
|
+
border-top-color: currentColor;
|
|
13
|
+
animation: spinner-spin 0.6s linear infinite;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.spinner-sm {
|
|
17
|
+
width: 0.75rem;
|
|
18
|
+
height: 0.75rem;
|
|
19
|
+
border-width: 1.5px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.spinner-lg {
|
|
23
|
+
width: 1.5rem;
|
|
24
|
+
height: 1.5rem;
|
|
25
|
+
border-width: 3px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Honour reduced-motion — pause rather than spin frantically. */
|
|
29
|
+
@media (prefers-reduced-motion: reduce) {
|
|
30
|
+
.spinner {
|
|
31
|
+
animation-duration: 2s;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@keyframes spinner-spin {
|
|
36
|
+
to {
|
|
37
|
+
transform: rotate(360deg);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.switch {
|
|
3
|
+
@apply relative inline-flex items-center w-9 h-5 shrink-0 p-0.5
|
|
4
|
+
rounded-full border border-transparent
|
|
5
|
+
cursor-pointer
|
|
6
|
+
transition-colors duration-150
|
|
7
|
+
focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary
|
|
8
|
+
disabled:opacity-50 disabled:cursor-not-allowed;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/* Base UI button variant: state is driven by data-* attributes. */
|
|
12
|
+
.switch[data-unchecked] {
|
|
13
|
+
@apply bg-border-strong;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.switch[data-checked] {
|
|
17
|
+
@apply bg-primary;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.switch[data-disabled] {
|
|
21
|
+
@apply opacity-50 cursor-not-allowed;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.switch-thumb {
|
|
25
|
+
@apply size-4 rounded-full bg-paper shadow-sm
|
|
26
|
+
transition-transform duration-150;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.switch[data-checked] .switch-thumb {
|
|
30
|
+
@apply translate-x-4;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Native input variant: state is driven by :checked, thumb is ::before. */
|
|
34
|
+
input.switch {
|
|
35
|
+
@apply appearance-none bg-border-strong m-0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
input.switch:checked {
|
|
39
|
+
@apply bg-primary;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
input.switch::before {
|
|
43
|
+
content: "";
|
|
44
|
+
@apply size-4 rounded-full bg-paper shadow-sm
|
|
45
|
+
transition-transform duration-150;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
input.switch:checked::before {
|
|
49
|
+
@apply translate-x-4;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* A <label> wrapping a switch + text lays out inline.
|
|
53
|
+
Switches are wider than checkboxes/radios so the gap is a touch larger. */
|
|
54
|
+
label:has(> .switch) {
|
|
55
|
+
@apply inline-flex items-center gap-3 cursor-pointer;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
label:has(> .switch:disabled),
|
|
59
|
+
label:has(> .switch[data-disabled]) {
|
|
60
|
+
@apply opacity-60 cursor-not-allowed;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.table {
|
|
3
|
+
@apply w-full text-sm text-text border-collapse;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/* Default cell padding/alignment/divider — applied via descendant selectors
|
|
7
|
+
for hand-written markup AND via explicit classes for cases where the
|
|
8
|
+
consumer renders a non-<td>/<th> element. :where() keeps specificity at 0
|
|
9
|
+
so authored utilities and modifiers can win without !important. Covers
|
|
10
|
+
thead/tbody/tfoot uniformly so a hand-written `<tfoot> <td>` lands on
|
|
11
|
+
the same padding as the React `.table-cell`. */
|
|
12
|
+
.table :where(th, td),
|
|
13
|
+
.table-cell,
|
|
14
|
+
.table-header-cell {
|
|
15
|
+
@apply px-3 py-1.5 text-left align-middle border-b border-border;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.table :where(thead th),
|
|
19
|
+
.table-header-cell {
|
|
20
|
+
@apply font-medium text-text-muted bg-surface-muted border-b-border-strong whitespace-nowrap;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Drop the divider on the very last row, whether it lives in tbody (no
|
|
24
|
+
tfoot present) or in tfoot — so the table doesn't double-border against
|
|
25
|
+
a surrounding container. `.table > :last-child` resolves to the final
|
|
26
|
+
section element (browsers wrap a loose `<tr>` in an implicit <tbody>). */
|
|
27
|
+
.table > :last-child > tr:last-child :where(td),
|
|
28
|
+
.table > :last-child > tr:last-child .table-cell {
|
|
29
|
+
@apply border-b-0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Alignment — `data-align` works equally for hand-written
|
|
33
|
+
`<td data-align="right">` and for React's `<Table.Cell align="right">`,
|
|
34
|
+
so consumers never need a Tailwind utility in their markup. */
|
|
35
|
+
.table :where(th, td)[data-align="right"] {
|
|
36
|
+
@apply text-right;
|
|
37
|
+
}
|
|
38
|
+
.table :where(th, td)[data-align="center"] {
|
|
39
|
+
@apply text-center;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Right-aligned with tabular-nums so currency/totals columns don't shimmy. */
|
|
43
|
+
.table-cell-numeric {
|
|
44
|
+
@apply text-right tabular-nums;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Narrow first-column gutter for row-level status icons. The inline icon
|
|
48
|
+
rule normalizes Tabler webfont (`<i class="ti …">`) against React's
|
|
49
|
+
`<IconCircleCheck size={16}>` SVG so both deliveries land at the same
|
|
50
|
+
16px box and the same vertical baseline. */
|
|
51
|
+
.table-cell-gutter {
|
|
52
|
+
@apply w-6 px-0 text-center text-text-muted;
|
|
53
|
+
}
|
|
54
|
+
.table-cell-gutter > :is(i, svg) {
|
|
55
|
+
font-size: 1rem;
|
|
56
|
+
line-height: 1;
|
|
57
|
+
vertical-align: middle;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Modifiers compose independently — striped + sticky + relaxed is valid. */
|
|
61
|
+
.table-striped tbody tr:nth-child(even) :where(td) {
|
|
62
|
+
@apply bg-surface-muted;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.table-bordered {
|
|
66
|
+
@apply border border-border;
|
|
67
|
+
}
|
|
68
|
+
.table-bordered :where(th, td) {
|
|
69
|
+
@apply border border-border;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.table-relaxed :where(th, td) {
|
|
73
|
+
@apply px-4 py-3;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Sticky header — requires a scrolling ancestor (e.g.
|
|
77
|
+
`<div class="overflow-auto" style="max-height: …">`). The wrapper isn't
|
|
78
|
+
added by the component; it would have no vanilla equivalent and would
|
|
79
|
+
break compositions like `<details><table>…`. */
|
|
80
|
+
.table-sticky thead :where(th) {
|
|
81
|
+
@apply sticky top-0 z-10 bg-surface-muted;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Hover — always on. Admin tables expect a row-scan affordance. */
|
|
85
|
+
.table tbody tr {
|
|
86
|
+
@apply transition-colors duration-75;
|
|
87
|
+
}
|
|
88
|
+
.table tbody tr:hover :where(td) {
|
|
89
|
+
@apply bg-surface-muted;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Selection — pure CSS via :has(). Fires for native checkboxes,
|
|
93
|
+
Base UI's checkbox via [data-checked], and the explicit
|
|
94
|
+
[data-selected] hook for programmatic selection without a checkbox. */
|
|
95
|
+
.table tbody tr:has(input[type="checkbox"]:checked),
|
|
96
|
+
.table tbody tr:has(.checkbox[data-checked]),
|
|
97
|
+
.table tbody tr[data-selected] {
|
|
98
|
+
@apply bg-primary-muted;
|
|
99
|
+
}
|
|
100
|
+
.table tbody tr:has(input[type="checkbox"]:checked):hover :where(td),
|
|
101
|
+
.table tbody tr:has(.checkbox[data-checked]):hover :where(td),
|
|
102
|
+
.table tbody tr[data-selected]:hover :where(td) {
|
|
103
|
+
@apply bg-primary-muted;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Whole-row link — the first <a> in the row gets its hit-area expanded to
|
|
107
|
+
fill the row via ::after. Consumer still writes the actual anchor
|
|
108
|
+
(preserves <Link>, plain <a href>, etc.). Other in-row anchors/buttons
|
|
109
|
+
should sit above the spanning pseudo-element with `relative` + non-auto
|
|
110
|
+
z-index; `.btn` already qualifies. */
|
|
111
|
+
.table-row-link {
|
|
112
|
+
@apply relative cursor-pointer;
|
|
113
|
+
}
|
|
114
|
+
.table-row-link:hover :where(td) {
|
|
115
|
+
@apply bg-surface-muted;
|
|
116
|
+
}
|
|
117
|
+
.table-row-link a:first-of-type::after {
|
|
118
|
+
content: "";
|
|
119
|
+
@apply absolute inset-0;
|
|
120
|
+
}
|
|
121
|
+
.table-row-link:focus-within {
|
|
122
|
+
@apply outline-2 -outline-offset-2 outline-primary;
|
|
123
|
+
}
|
|
124
|
+
}
|