@aortl/admin-css 0.16.2 → 0.18.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/CHANGELOG.md +37 -1
- package/dist/admin.css +1060 -101
- package/dist/admin.min.css +1 -1
- package/dist/admin.scoped.css +1072 -127
- package/dist/admin.scoped.min.css +174 -6
- package/package.json +1 -1
- package/src/components/alert.css +70 -4
- package/src/components/avatar.css +42 -0
- package/src/components/badge.css +36 -0
- package/src/components/brand-tile.css +36 -0
- package/src/components/card.css +38 -0
- package/src/components/drawer.css +81 -0
- package/src/components/index.css +7 -0
- package/src/components/indicator.css +12 -0
- package/src/components/input-icon.css +65 -0
- package/src/components/item.css +76 -0
- package/src/components/menu.css +15 -0
- package/src/components/number-input.css +64 -0
- package/src/components/separator.css +13 -0
- package/src/components/stat-card.css +33 -0
- package/src/components/table.css +54 -0
- package/src/components/timeline.css +113 -0
package/src/components/card.css
CHANGED
|
@@ -96,4 +96,42 @@
|
|
|
96
96
|
|
|
97
97
|
/* No `.card-warning .card-title` rule — the yellow-400 accent fails AA on
|
|
98
98
|
the muted yellow surface. */
|
|
99
|
+
|
|
100
|
+
/* Full-bleed media. Sibling of .card-body; first/last placement picks up the
|
|
101
|
+
card's corner rounding without overflow on the root. */
|
|
102
|
+
.card-media {
|
|
103
|
+
@apply block w-full shrink-0 overflow-hidden;
|
|
104
|
+
}
|
|
105
|
+
.card-media:first-child {
|
|
106
|
+
border-top-left-radius: inherit;
|
|
107
|
+
border-top-right-radius: inherit;
|
|
108
|
+
}
|
|
109
|
+
.card-media:last-child {
|
|
110
|
+
border-bottom-left-radius: inherit;
|
|
111
|
+
border-bottom-right-radius: inherit;
|
|
112
|
+
}
|
|
113
|
+
.card-media > :is(img, video) {
|
|
114
|
+
@apply block w-full h-full object-cover;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* Scroll region — header/actions pin, body scrolls; consumer sets the height.
|
|
118
|
+
Direct-child header/actions gain padding + a divider since inside
|
|
119
|
+
.card-body they carry none. */
|
|
120
|
+
.card-scroll > .card-header {
|
|
121
|
+
@apply shrink-0 px-5 py-3 border-b border-border;
|
|
122
|
+
}
|
|
123
|
+
.card-scroll > .card-body {
|
|
124
|
+
@apply flex-1 min-h-0 overflow-y-auto;
|
|
125
|
+
}
|
|
126
|
+
.card-scroll > .card-body:last-child {
|
|
127
|
+
/* Keep the scrollbar inside the rounded corners when there's no footer. */
|
|
128
|
+
border-bottom-left-radius: inherit;
|
|
129
|
+
border-bottom-right-radius: inherit;
|
|
130
|
+
}
|
|
131
|
+
.card-scroll > .card-actions {
|
|
132
|
+
@apply shrink-0 px-5 py-3 border-t border-border;
|
|
133
|
+
}
|
|
134
|
+
.card-compact.card-scroll > :is(.card-header, .card-actions) {
|
|
135
|
+
@apply px-3 py-2;
|
|
136
|
+
}
|
|
99
137
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* A Dialog variant: the same native <dialog> machinery (showModal, ::backdrop,
|
|
3
|
+
@starting-style, allow-discrete transitions), edge-anchored instead of
|
|
4
|
+
centred. `.drawer` overrides `.dialog`'s centring; it imports after dialog.css
|
|
5
|
+
so equal-specificity rules win. The base opacity fade still applies. */
|
|
6
|
+
.drawer {
|
|
7
|
+
margin: 0;
|
|
8
|
+
max-width: none;
|
|
9
|
+
max-height: none;
|
|
10
|
+
border-radius: 0;
|
|
11
|
+
height: 100dvh;
|
|
12
|
+
width: min(28rem, 100vw);
|
|
13
|
+
/* Default: anchored to the inline-end (right in LTR) edge. */
|
|
14
|
+
margin-inline-start: auto;
|
|
15
|
+
transform: translateX(100%);
|
|
16
|
+
}
|
|
17
|
+
.drawer[open] {
|
|
18
|
+
transform: translateX(0);
|
|
19
|
+
}
|
|
20
|
+
@starting-style {
|
|
21
|
+
.drawer[open] {
|
|
22
|
+
transform: translateX(100%);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* Inline-start (left in LTR) edge. */
|
|
27
|
+
.drawer-start {
|
|
28
|
+
margin-inline-start: 0;
|
|
29
|
+
margin-inline-end: auto;
|
|
30
|
+
transform: translateX(-100%);
|
|
31
|
+
}
|
|
32
|
+
@starting-style {
|
|
33
|
+
.drawer-start[open] {
|
|
34
|
+
transform: translateX(-100%);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Bottom sheet. */
|
|
39
|
+
.drawer-bottom {
|
|
40
|
+
width: 100vw;
|
|
41
|
+
height: auto;
|
|
42
|
+
max-height: 85dvh;
|
|
43
|
+
margin-block-start: auto;
|
|
44
|
+
margin-inline: 0;
|
|
45
|
+
transform: translateY(100%);
|
|
46
|
+
}
|
|
47
|
+
.drawer-bottom[open] {
|
|
48
|
+
transform: translateY(0);
|
|
49
|
+
}
|
|
50
|
+
@starting-style {
|
|
51
|
+
.drawer-bottom[open] {
|
|
52
|
+
transform: translateY(100%);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Sizes adjust the cross-axis extent. */
|
|
57
|
+
.drawer-sm {
|
|
58
|
+
width: min(20rem, 100vw);
|
|
59
|
+
}
|
|
60
|
+
.drawer-lg {
|
|
61
|
+
width: min(36rem, 100vw);
|
|
62
|
+
}
|
|
63
|
+
.drawer-bottom.drawer-sm {
|
|
64
|
+
width: 100vw;
|
|
65
|
+
max-height: 50dvh;
|
|
66
|
+
}
|
|
67
|
+
.drawer-bottom.drawer-lg {
|
|
68
|
+
width: 100vw;
|
|
69
|
+
max-height: 95dvh;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Slide is decorative — drop it under reduced motion, keep the opacity fade. */
|
|
73
|
+
@media (prefers-reduced-motion: reduce) {
|
|
74
|
+
.drawer {
|
|
75
|
+
transition:
|
|
76
|
+
display 150ms allow-discrete,
|
|
77
|
+
overlay 150ms allow-discrete,
|
|
78
|
+
opacity 150ms ease-out;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
package/src/components/index.css
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
@import "./container.css";
|
|
5
5
|
@import "./badge.css";
|
|
6
6
|
@import "./brand-tile.css";
|
|
7
|
+
@import "./avatar.css";
|
|
7
8
|
@import "./kbd.css";
|
|
8
9
|
@import "./indicator.css";
|
|
9
10
|
@import "./spinner.css";
|
|
@@ -11,11 +12,14 @@
|
|
|
11
12
|
@import "./breadcrumbs.css";
|
|
12
13
|
@import "./pagination.css";
|
|
13
14
|
@import "./property-list.css";
|
|
15
|
+
@import "./separator.css";
|
|
14
16
|
@import "./button.css";
|
|
15
17
|
@import "./button-group.css";
|
|
16
18
|
@import "./link.css";
|
|
17
19
|
@import "./input.css";
|
|
18
20
|
@import "./input-group.css";
|
|
21
|
+
@import "./input-icon.css";
|
|
22
|
+
@import "./number-input.css";
|
|
19
23
|
@import "./textarea.css";
|
|
20
24
|
@import "./checkbox.css";
|
|
21
25
|
@import "./radio.css";
|
|
@@ -23,7 +27,10 @@
|
|
|
23
27
|
@import "./select.css";
|
|
24
28
|
@import "./card.css";
|
|
25
29
|
@import "./stat-card.css";
|
|
30
|
+
@import "./item.css";
|
|
31
|
+
@import "./timeline.css";
|
|
26
32
|
@import "./dialog.css";
|
|
33
|
+
@import "./drawer.css";
|
|
27
34
|
@import "./field.css";
|
|
28
35
|
@import "./file-input.css";
|
|
29
36
|
@import "./footer.css";
|
|
@@ -15,6 +15,18 @@
|
|
|
15
15
|
.indicator:has(> .card) {
|
|
16
16
|
--indicator-offset: 6px;
|
|
17
17
|
}
|
|
18
|
+
.indicator:has(> .avatar) {
|
|
19
|
+
--indicator-offset: 5px;
|
|
20
|
+
}
|
|
21
|
+
.indicator:has(> .avatar-sm) {
|
|
22
|
+
--indicator-offset: 3px;
|
|
23
|
+
}
|
|
24
|
+
.indicator:has(> .avatar-lg) {
|
|
25
|
+
--indicator-offset: 6px;
|
|
26
|
+
}
|
|
27
|
+
.indicator:has(> .avatar-square) {
|
|
28
|
+
--indicator-offset: 2px;
|
|
29
|
+
}
|
|
18
30
|
|
|
19
31
|
.indicator-item {
|
|
20
32
|
@apply absolute z-[1] whitespace-nowrap;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* Overlay wrapper — the .input child keeps its own border, background,
|
|
3
|
+
and focus outline; icons float above it, :has() padding clears them. */
|
|
4
|
+
.input-icon {
|
|
5
|
+
@apply relative inline-flex w-full items-center text-sm text-text-muted;
|
|
6
|
+
--input-icon-inset: calc(var(--spacing) * 3); /* matches .input px-3 */
|
|
7
|
+
--input-icon-pad: calc(var(--spacing) * 9);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.input-icon:has(> .input-sm) {
|
|
11
|
+
@apply text-xs;
|
|
12
|
+
--input-icon-inset: calc(var(--spacing) * 2.5);
|
|
13
|
+
--input-icon-pad: calc(var(--spacing) * 8);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.input-icon:has(> .input-lg) {
|
|
17
|
+
@apply text-base;
|
|
18
|
+
--input-icon-inset: calc(var(--spacing) * 4);
|
|
19
|
+
--input-icon-pad: calc(var(--spacing) * 10);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Decorative glyphs pass clicks through; an interactive trailing button doesn't. */
|
|
23
|
+
.input-icon > :where(i, svg) {
|
|
24
|
+
@apply absolute top-1/2 -translate-y-1/2 pointer-events-none;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.input-icon > :where(i, svg):first-child {
|
|
28
|
+
left: var(--input-icon-inset);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Trailing slot — a glyph or an interactive `.input-action` button after the input. */
|
|
32
|
+
.input-icon > .input ~ :where(i, svg, .input-action) {
|
|
33
|
+
right: var(--input-icon-inset);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.input-icon:has(> :where(i, svg):first-child) > .input {
|
|
37
|
+
padding-left: var(--input-icon-pad);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.input-icon:has(> .input ~ :where(i, svg, .input-action)) > .input {
|
|
41
|
+
padding-right: var(--input-icon-pad);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.input-icon:has(> .input:disabled) > :where(i, svg) {
|
|
45
|
+
@apply opacity-50;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Interactive trailing control (clear, password reveal). Positioned like the
|
|
49
|
+
trailing glyph but clickable, with a currentColor hover wash. */
|
|
50
|
+
.input-action {
|
|
51
|
+
@apply absolute top-1/2 -translate-y-1/2
|
|
52
|
+
inline-flex items-center justify-center
|
|
53
|
+
size-5 rounded
|
|
54
|
+
text-text-muted cursor-pointer
|
|
55
|
+
bg-transparent border-0
|
|
56
|
+
hover:text-text
|
|
57
|
+
focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-focus;
|
|
58
|
+
}
|
|
59
|
+
.input-action:hover {
|
|
60
|
+
background-color: color-mix(in oklab, currentColor 12%, transparent);
|
|
61
|
+
}
|
|
62
|
+
.input-icon:has(> .input:disabled) > .input-action {
|
|
63
|
+
@apply opacity-50 pointer-events-none;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* Compact low-chrome row between Table and Card: media | content | actions.
|
|
3
|
+
For settings rows, member lists, notifications, search results. */
|
|
4
|
+
.item {
|
|
5
|
+
@apply flex items-center gap-3 w-full px-3 py-2 text-sm text-text;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.item-media {
|
|
9
|
+
@apply flex items-center shrink-0 text-text-muted;
|
|
10
|
+
}
|
|
11
|
+
.item-media > :is(i, svg) {
|
|
12
|
+
font-size: 1.25rem;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* min-w-0 lets the text column shrink so long values wrap instead of pushing
|
|
16
|
+
the actions off the row. */
|
|
17
|
+
.item-content {
|
|
18
|
+
@apply flex flex-col gap-0.5 flex-1 min-w-0;
|
|
19
|
+
}
|
|
20
|
+
.item-title {
|
|
21
|
+
@apply font-medium leading-tight;
|
|
22
|
+
overflow-wrap: break-word;
|
|
23
|
+
}
|
|
24
|
+
.item-description {
|
|
25
|
+
@apply text-text-muted leading-snug;
|
|
26
|
+
text-wrap: pretty;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.item-actions {
|
|
30
|
+
@apply flex items-center gap-2 shrink-0 relative z-[1];
|
|
31
|
+
margin-inline-start: auto;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Variants — keep it flat: default / outline / muted only. */
|
|
35
|
+
.item-outline {
|
|
36
|
+
@apply border border-border rounded-lg;
|
|
37
|
+
}
|
|
38
|
+
.item-muted {
|
|
39
|
+
@apply bg-surface-muted rounded-lg;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Sizes */
|
|
43
|
+
.item-sm {
|
|
44
|
+
@apply gap-2 px-2 py-1.5 text-xs;
|
|
45
|
+
}
|
|
46
|
+
.item-lg {
|
|
47
|
+
@apply gap-4 px-4 py-3 text-base;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Divided stack. */
|
|
51
|
+
.item-group {
|
|
52
|
+
@apply flex flex-col;
|
|
53
|
+
}
|
|
54
|
+
.item-group > .item:not(:last-child) {
|
|
55
|
+
@apply border-b border-border;
|
|
56
|
+
}
|
|
57
|
+
.item-group-bordered {
|
|
58
|
+
@apply border border-border rounded-lg overflow-hidden;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Clickable row — the first nested <a> fills the row via ::after; actions keep
|
|
62
|
+
their own stacking context so they stay clickable (same trick as table-row-link). */
|
|
63
|
+
.item-link {
|
|
64
|
+
@apply relative cursor-pointer;
|
|
65
|
+
}
|
|
66
|
+
.item-link:hover {
|
|
67
|
+
@apply bg-surface-muted;
|
|
68
|
+
}
|
|
69
|
+
.item-link a:first-of-type::after {
|
|
70
|
+
content: "";
|
|
71
|
+
@apply absolute inset-0;
|
|
72
|
+
}
|
|
73
|
+
.item-link:focus-within {
|
|
74
|
+
@apply outline-2 -outline-offset-2 outline-focus;
|
|
75
|
+
}
|
|
76
|
+
}
|
package/src/components/menu.css
CHANGED
|
@@ -102,6 +102,21 @@
|
|
|
102
102
|
flex-shrink: 0;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/* Checkbox/radio items — a leading indicator column whose check shows only when
|
|
106
|
+
aria-checked. In vanilla, set role=menuitemcheckbox/menuitemradio + aria-checked;
|
|
107
|
+
the gutter is always reserved so labels stay aligned across the group. */
|
|
108
|
+
.menu-item-indicator {
|
|
109
|
+
@apply inline-flex items-center justify-center shrink-0;
|
|
110
|
+
inline-size: 1rem;
|
|
111
|
+
block-size: 1rem;
|
|
112
|
+
}
|
|
113
|
+
.menu-item-indicator > :is(i, svg) {
|
|
114
|
+
opacity: 0;
|
|
115
|
+
}
|
|
116
|
+
.menu-item[aria-checked="true"] .menu-item-indicator > :is(i, svg) {
|
|
117
|
+
opacity: 1;
|
|
118
|
+
}
|
|
119
|
+
|
|
105
120
|
.menu-separator {
|
|
106
121
|
@apply h-px my-1 bg-border border-0;
|
|
107
122
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* React's Base UI Root is a transparent wrapper; the visual group is `.number-input`. */
|
|
3
|
+
.number-input-root {
|
|
4
|
+
display: contents;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/* Connected group: − | input | +, mirroring .input-group's look. */
|
|
8
|
+
.number-input {
|
|
9
|
+
@apply inline-flex w-full items-stretch
|
|
10
|
+
rounded-lg border border-border bg-surface
|
|
11
|
+
transition-colors duration-150
|
|
12
|
+
focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-focus;
|
|
13
|
+
}
|
|
14
|
+
.number-input:hover {
|
|
15
|
+
@apply border-border-strong;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* Borderless field: right-aligned tabular digits, native spinners hidden. */
|
|
19
|
+
.number-input-field {
|
|
20
|
+
@apply flex-1 min-w-0 w-full px-3 py-2
|
|
21
|
+
bg-transparent text-sm leading-none text-text text-right tabular-nums
|
|
22
|
+
border-0 outline-none
|
|
23
|
+
placeholder:text-text-muted
|
|
24
|
+
disabled:opacity-50 disabled:cursor-not-allowed;
|
|
25
|
+
-moz-appearance: textfield;
|
|
26
|
+
}
|
|
27
|
+
.number-input-field::-webkit-inner-spin-button,
|
|
28
|
+
.number-input-field::-webkit-outer-spin-button {
|
|
29
|
+
-webkit-appearance: none;
|
|
30
|
+
margin: 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Stepper buttons. */
|
|
34
|
+
.number-input-step {
|
|
35
|
+
@apply inline-flex items-center justify-center shrink-0
|
|
36
|
+
w-8 text-text-muted bg-transparent
|
|
37
|
+
cursor-pointer select-none border-0
|
|
38
|
+
hover:bg-surface-muted hover:text-text
|
|
39
|
+
disabled:opacity-50 disabled:cursor-not-allowed;
|
|
40
|
+
}
|
|
41
|
+
.number-input-step:first-child {
|
|
42
|
+
@apply border-r border-border rounded-l-lg;
|
|
43
|
+
}
|
|
44
|
+
.number-input-step:last-child {
|
|
45
|
+
@apply border-l border-border rounded-r-lg;
|
|
46
|
+
}
|
|
47
|
+
.number-input-step > :is(i, svg) {
|
|
48
|
+
font-size: 0.875rem;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Sizes */
|
|
52
|
+
.number-input-sm .number-input-field {
|
|
53
|
+
@apply text-xs px-2.5 py-1.5;
|
|
54
|
+
}
|
|
55
|
+
.number-input-sm .number-input-step {
|
|
56
|
+
@apply w-7;
|
|
57
|
+
}
|
|
58
|
+
.number-input-lg .number-input-field {
|
|
59
|
+
@apply text-base px-4 py-2.5;
|
|
60
|
+
}
|
|
61
|
+
.number-input-lg .number-input-step {
|
|
62
|
+
@apply w-9;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* Works on <hr> (border reset) and any block element. Margins are zeroed —
|
|
3
|
+
spacing comes from the parent's gap or margin utilities. */
|
|
4
|
+
.separator {
|
|
5
|
+
@apply block h-px w-full m-0 shrink-0 border-0 bg-border;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.separator-vertical {
|
|
9
|
+
@apply w-px h-auto self-stretch;
|
|
10
|
+
/* Visible outside a stretching flex/grid cross axis. */
|
|
11
|
+
min-height: 1lh;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -21,6 +21,39 @@
|
|
|
21
21
|
@apply text-sm text-text-muted;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/* Trend delta — caret drawn from borders (no icon dependency) plus a value.
|
|
25
|
+
`data-trend` rotates the caret; `data-intent` colors it, kept independent
|
|
26
|
+
because up is not always good (a rising error rate is bad). */
|
|
27
|
+
.stat-card-trend {
|
|
28
|
+
@apply inline-flex items-center gap-1 text-sm font-medium tabular-nums text-text-muted;
|
|
29
|
+
}
|
|
30
|
+
.stat-card-trend::before {
|
|
31
|
+
content: "";
|
|
32
|
+
width: 0;
|
|
33
|
+
height: 0;
|
|
34
|
+
border-inline: 0.3em solid transparent;
|
|
35
|
+
border-block-end: 0.42em solid currentColor;
|
|
36
|
+
}
|
|
37
|
+
.stat-card-trend[data-trend="down"]::before {
|
|
38
|
+
border-block-end: 0;
|
|
39
|
+
border-block-start: 0.42em solid currentColor;
|
|
40
|
+
}
|
|
41
|
+
.stat-card-trend[data-trend="flat"]::before {
|
|
42
|
+
width: 0.55em;
|
|
43
|
+
border-inline: 0;
|
|
44
|
+
border-block-end: 0;
|
|
45
|
+
border-block-start: 0.16em solid currentColor;
|
|
46
|
+
}
|
|
47
|
+
.stat-card-trend[data-intent="positive"] {
|
|
48
|
+
@apply text-success;
|
|
49
|
+
}
|
|
50
|
+
.stat-card-trend[data-intent="negative"] {
|
|
51
|
+
@apply text-danger;
|
|
52
|
+
}
|
|
53
|
+
.stat-card-trend[data-intent="neutral"] {
|
|
54
|
+
@apply text-text-muted;
|
|
55
|
+
}
|
|
56
|
+
|
|
24
57
|
/* `.card-compact` targets `.card-body`; with no body, step the root instead. */
|
|
25
58
|
.card-compact.stat-card {
|
|
26
59
|
@apply p-3 gap-0.5;
|
package/src/components/table.css
CHANGED
|
@@ -16,6 +16,16 @@
|
|
|
16
16
|
@apply font-medium text-text-muted bg-surface-muted border-b-border-strong whitespace-nowrap;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/* Totals/footer — all tfoot rows are emphasized; the first row carries a
|
|
20
|
+
strong divider against the body. 2px so border-collapse picks it over the
|
|
21
|
+
tbody last row's 1px border-b (equal widths resolve to the upper cell). */
|
|
22
|
+
.table :where(tfoot :is(td, th)) {
|
|
23
|
+
@apply font-semibold;
|
|
24
|
+
}
|
|
25
|
+
.table :where(tfoot tr:first-child :is(td, th)) {
|
|
26
|
+
@apply border-t-2 border-t-border-strong;
|
|
27
|
+
}
|
|
28
|
+
|
|
19
29
|
/* Body cells only — break long unbreakable values (IDs, hashes, URLs) instead
|
|
20
30
|
of forcing the table into horizontal overflow. */
|
|
21
31
|
.table :where(td),
|
|
@@ -111,4 +121,48 @@
|
|
|
111
121
|
.table-row-link:focus-within {
|
|
112
122
|
@apply outline-2 -outline-offset-2 outline-focus;
|
|
113
123
|
}
|
|
124
|
+
|
|
125
|
+
/* Compact density — mirrors table-relaxed at the tight end. */
|
|
126
|
+
.table-compact :where(th, td),
|
|
127
|
+
.table-compact .table-cell,
|
|
128
|
+
.table-compact .table-header-cell {
|
|
129
|
+
@apply px-2 py-1 text-xs;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* Empty state — a centered, muted message row (author sets colspan). */
|
|
133
|
+
.table :where(td).table-empty,
|
|
134
|
+
.table-empty {
|
|
135
|
+
@apply text-center text-text-muted py-8;
|
|
136
|
+
}
|
|
137
|
+
/* No hover affordance on the empty-state row. */
|
|
138
|
+
.table tbody tr:has(> .table-empty):hover :where(td) {
|
|
139
|
+
background-color: transparent;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* Pinned first column — sticky against horizontal scroll. Requires an
|
|
143
|
+
overflow-x ancestor (the table adds no wrapper, same stance as table-sticky).
|
|
144
|
+
Each row state repaints the cell so scrolled content doesn't bleed through. */
|
|
145
|
+
.table-pin-col :where(thead th, tbody td, tfoot :is(td, th)):first-child {
|
|
146
|
+
position: sticky;
|
|
147
|
+
inset-inline-start: 0;
|
|
148
|
+
}
|
|
149
|
+
.table-pin-col tbody td:first-child {
|
|
150
|
+
z-index: 1;
|
|
151
|
+
background-color: var(--color-surface);
|
|
152
|
+
}
|
|
153
|
+
/* Header corner sits above the body column and the sticky header row. */
|
|
154
|
+
.table-pin-col :where(thead th):first-child {
|
|
155
|
+
z-index: 11;
|
|
156
|
+
}
|
|
157
|
+
.table-pin-col.table-striped tbody tr:nth-child(even) td:first-child {
|
|
158
|
+
background-color: var(--color-surface-muted);
|
|
159
|
+
}
|
|
160
|
+
.table-pin-col tbody tr:hover td:first-child {
|
|
161
|
+
background-color: var(--color-surface-muted);
|
|
162
|
+
}
|
|
163
|
+
.table-pin-col tbody tr:has(input[type="checkbox"]:checked) td:first-child,
|
|
164
|
+
.table-pin-col tbody tr:has(.checkbox[data-checked]) td:first-child,
|
|
165
|
+
.table-pin-col tbody tr[data-selected] td:first-child {
|
|
166
|
+
background-color: var(--color-primary-muted);
|
|
167
|
+
}
|
|
114
168
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* Vertical event rail: an indicator gutter and a content column per <li>, with
|
|
3
|
+
a connector line drawn down the gutter that stops at the last item. */
|
|
4
|
+
.timeline {
|
|
5
|
+
list-style: none;
|
|
6
|
+
margin: 0;
|
|
7
|
+
padding: 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.timeline-item {
|
|
11
|
+
display: grid;
|
|
12
|
+
grid-template-columns: 1.5rem 1fr;
|
|
13
|
+
column-gap: 0.75rem;
|
|
14
|
+
padding-bottom: 1rem;
|
|
15
|
+
position: relative;
|
|
16
|
+
}
|
|
17
|
+
.timeline-item:last-child {
|
|
18
|
+
padding-bottom: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Connector — vertical line centred in the gutter, hidden on the last item. */
|
|
22
|
+
.timeline-item:not(:last-child)::before {
|
|
23
|
+
content: "";
|
|
24
|
+
position: absolute;
|
|
25
|
+
inset-block: 0.75rem 0;
|
|
26
|
+
inset-inline-start: 0.75rem;
|
|
27
|
+
width: 2px;
|
|
28
|
+
transform: translateX(-50%);
|
|
29
|
+
background-color: var(--color-border);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.timeline-indicator {
|
|
33
|
+
grid-column: 1;
|
|
34
|
+
align-self: start;
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
justify-content: center;
|
|
38
|
+
height: 1.5rem;
|
|
39
|
+
position: relative;
|
|
40
|
+
z-index: 1;
|
|
41
|
+
}
|
|
42
|
+
.timeline-dot {
|
|
43
|
+
display: inline-block;
|
|
44
|
+
width: 0.625rem;
|
|
45
|
+
height: 0.625rem;
|
|
46
|
+
border-radius: 9999px;
|
|
47
|
+
background-color: var(--color-border-strong);
|
|
48
|
+
box-shadow: 0 0 0 3px var(--color-surface);
|
|
49
|
+
}
|
|
50
|
+
/* Surface backing masks the connector running behind an icon indicator. */
|
|
51
|
+
.timeline-indicator > :is(i, svg) {
|
|
52
|
+
font-size: 1rem;
|
|
53
|
+
color: var(--color-text-muted);
|
|
54
|
+
background-color: var(--color-surface);
|
|
55
|
+
border-radius: 9999px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.timeline-content {
|
|
59
|
+
grid-column: 2;
|
|
60
|
+
display: flex;
|
|
61
|
+
flex-direction: column;
|
|
62
|
+
gap: 0.125rem;
|
|
63
|
+
min-width: 0;
|
|
64
|
+
padding-block: 0.125rem 0;
|
|
65
|
+
}
|
|
66
|
+
.timeline-title {
|
|
67
|
+
@apply text-sm font-medium text-text leading-tight;
|
|
68
|
+
}
|
|
69
|
+
.timeline-time {
|
|
70
|
+
@apply text-xs text-text-muted tabular-nums;
|
|
71
|
+
}
|
|
72
|
+
.timeline-description {
|
|
73
|
+
@apply text-sm text-text-muted;
|
|
74
|
+
text-wrap: pretty;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Status accents on the dot. */
|
|
78
|
+
.timeline-item-info .timeline-dot {
|
|
79
|
+
background-color: var(--color-info);
|
|
80
|
+
}
|
|
81
|
+
.timeline-item-success .timeline-dot {
|
|
82
|
+
background-color: var(--color-success);
|
|
83
|
+
}
|
|
84
|
+
.timeline-item-warning .timeline-dot {
|
|
85
|
+
background-color: var(--color-warning);
|
|
86
|
+
}
|
|
87
|
+
.timeline-item-danger .timeline-dot {
|
|
88
|
+
background-color: var(--color-danger);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Numbered variant — the gutter holds a numbered/checked marker (steps rail). */
|
|
92
|
+
.timeline-numbered .timeline-item {
|
|
93
|
+
grid-template-columns: 1.75rem 1fr;
|
|
94
|
+
}
|
|
95
|
+
.timeline-numbered .timeline-item:not(:last-child)::before {
|
|
96
|
+
inset-inline-start: 0.875rem;
|
|
97
|
+
inset-block: 1.75rem 0;
|
|
98
|
+
}
|
|
99
|
+
.timeline-numbered .timeline-indicator {
|
|
100
|
+
height: 1.75rem;
|
|
101
|
+
}
|
|
102
|
+
.timeline-marker {
|
|
103
|
+
@apply inline-flex items-center justify-center
|
|
104
|
+
size-7 rounded-full text-xs font-semibold tabular-nums
|
|
105
|
+
bg-surface-strong text-text-muted;
|
|
106
|
+
box-shadow: 0 0 0 3px var(--color-surface);
|
|
107
|
+
}
|
|
108
|
+
/* Completed and current steps fill with the primary ink. */
|
|
109
|
+
.timeline-item-success .timeline-marker,
|
|
110
|
+
.timeline-item-current .timeline-marker {
|
|
111
|
+
@apply bg-primary text-primary-content;
|
|
112
|
+
}
|
|
113
|
+
}
|