@aortl/admin-css 0.16.2 → 0.17.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aortl/admin-css",
3
- "version": "0.16.2",
3
+ "version": "0.17.0",
4
4
  "description": "Pre-built CSS design system. Drop in via <link> and use semantic class names.",
5
5
  "keywords": [
6
6
  "components",
@@ -14,13 +14,25 @@
14
14
  align-items: center;
15
15
  }
16
16
 
17
+ /* A trailing action switches to grid even without an icon. */
18
+ .alert:has(> .alert-action) {
19
+ display: grid;
20
+ grid-template-columns: minmax(0, 1fr) auto;
21
+ column-gap: 0.5rem;
22
+ row-gap: 0.25rem;
23
+ align-items: center;
24
+ }
25
+ .alert:has(> :is(i, svg):first-child):has(> .alert-action) {
26
+ grid-template-columns: auto minmax(0, 1fr) auto;
27
+ }
28
+
17
29
  .alert > :is(i, svg):first-child {
18
30
  font-size: 1rem;
19
31
  line-height: 1.25;
20
32
  }
21
33
 
22
- /* Explicit rows — the icon's `grid-row: 1 / -1` only resolves against an explicit grid. */
23
- .alert:has(> :is(i, svg):first-child):has(> .alert-title) {
34
+ /* Replaces the icon-gated explicit-rows rule rows whenever anything spans them. */
35
+ .alert:has(> .alert-title):is(:has(> :is(i, svg):first-child), :has(> .alert-action)) {
24
36
  grid-template-rows: auto auto;
25
37
  align-items: start;
26
38
  }
@@ -29,11 +41,23 @@
29
41
  grid-row: 1 / -1;
30
42
  }
31
43
 
32
- /* Grid items default to `min-width: auto` and won't shrink below their longest word. */
44
+ /* The column-2 pin becomes icon-gated so title/description auto-place into
45
+ column 1 in an action-only grid. min-width stays unconditional. */
33
46
  .alert > :is(.alert-title, .alert-description) {
34
- grid-column: 2;
35
47
  min-width: 0;
36
48
  }
49
+ .alert:has(> :is(i, svg):first-child) > :is(.alert-title, .alert-description) {
50
+ grid-column: 2;
51
+ }
52
+
53
+ /* `-2` is the last explicit column in both grids; spans both rows, centered. */
54
+ .alert > .alert-action {
55
+ @apply font-medium whitespace-nowrap;
56
+ grid-column: -2;
57
+ grid-row: 1 / -1;
58
+ justify-self: end;
59
+ align-self: center;
60
+ }
37
61
 
38
62
  /* Solid status fills. Title and icon inherit the `-content` color from the
39
63
  root, so no per-variant text rule is needed. */
@@ -60,4 +84,10 @@
60
84
  .alert-description {
61
85
  opacity: 0.85;
62
86
  }
87
+
88
+ /* `.link`'s blue is illegible on the solid fills; inherit the variant's content
89
+ color. Underline + weight carry the affordance. */
90
+ .alert .link {
91
+ @apply text-current hover:text-current hover:opacity-85 focus-visible:outline-current;
92
+ }
63
93
  }
@@ -0,0 +1,36 @@
1
+ @layer components {
2
+ .avatar {
3
+ @apply relative inline-flex items-center justify-center
4
+ size-8 rounded-full overflow-hidden
5
+ bg-surface-strong text-text-muted
6
+ text-xs font-medium leading-none tracking-tight
7
+ select-none shrink-0;
8
+ }
9
+
10
+ /* Layered over the initials so they show until the image loads (no JS). */
11
+ .avatar > img {
12
+ @apply absolute inset-0 size-full object-cover;
13
+ }
14
+
15
+ .avatar-square {
16
+ @apply rounded-md;
17
+ }
18
+
19
+ .avatar-sm {
20
+ @apply size-6 text-[0.625rem];
21
+ }
22
+ .avatar-lg {
23
+ @apply size-10 text-sm;
24
+ }
25
+
26
+ /* Overlapping stack — later siblings paint on top. */
27
+ .avatar-group {
28
+ @apply inline-flex items-center;
29
+ }
30
+ .avatar-group > .avatar {
31
+ @apply ring-2 ring-surface;
32
+ }
33
+ .avatar-group > .avatar + .avatar {
34
+ @apply -ms-2;
35
+ }
36
+ }
@@ -34,6 +34,42 @@
34
34
  @apply bg-primary text-primary-content;
35
35
  }
36
36
 
37
+ /* Soft — composable modifier, pairs with a variant class; bare `.badge` is already the soft neutral. */
38
+ .badge-soft.badge-info {
39
+ @apply bg-info-muted text-info border-info-muted;
40
+ }
41
+ .badge-soft.badge-success {
42
+ @apply bg-success-muted text-success border-success-muted;
43
+ }
44
+ /* Yellow accent text fails contrast on the tinted surface — same constraint as stat-card. */
45
+ .badge-soft.badge-warning {
46
+ @apply bg-warning-muted text-text border-warning-muted;
47
+ }
48
+ .badge-soft.badge-danger {
49
+ @apply bg-danger-muted text-danger border-danger-muted;
50
+ }
51
+ .badge-soft.badge-primary {
52
+ @apply bg-primary-muted text-text border-primary-muted;
53
+ }
54
+
55
+ /* Remove button — sized to the chip; hover wash from `currentColor` works on solid, soft, and neutral fills. */
56
+ .badge-remove {
57
+ @apply inline-flex items-center justify-center shrink-0
58
+ size-3.5 -mr-1 rounded-full
59
+ text-inherit cursor-pointer
60
+ transition-colors duration-150
61
+ focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-focus;
62
+ }
63
+ .badge-remove:hover {
64
+ background-color: color-mix(in oklab, currentColor 15%, transparent);
65
+ }
66
+ .badge-sm .badge-remove {
67
+ @apply size-3 -mr-0.5;
68
+ }
69
+ .badge-lg .badge-remove {
70
+ @apply size-4.5 -mr-1.5;
71
+ }
72
+
37
73
  /* Sizes (md is the default; modifiers override) */
38
74
  .badge-sm {
39
75
  @apply h-4 px-1.5 text-[0.625rem] gap-0.5;
@@ -6,4 +6,40 @@
6
6
  bg-system-accent text-system-accent-content
7
7
  shrink-0 select-none;
8
8
  }
9
+
10
+ /* Glyph sizing moves into CSS so vanilla drops the inline font-size
11
+ and React renders icons at 1em. */
12
+ .brand-tile > :is(i, svg) {
13
+ font-size: 14px;
14
+ }
15
+
16
+ .brand-tile-lg {
17
+ @apply size-10 rounded-md text-sm;
18
+ }
19
+ .brand-tile-lg > :is(i, svg) {
20
+ font-size: 20px;
21
+ }
22
+
23
+ /* Soft tints — *-muted fill, colored glyph. */
24
+ .brand-tile-soft {
25
+ @apply bg-system-accent-muted text-system-accent;
26
+ }
27
+ .brand-tile-info {
28
+ @apply bg-info-muted text-info;
29
+ }
30
+ .brand-tile-success {
31
+ @apply bg-success-muted text-success;
32
+ }
33
+ .brand-tile-danger {
34
+ @apply bg-danger-muted text-danger;
35
+ }
36
+
37
+ /* Image tile (shop logo) — :has() flips the accent fill to a bordered
38
+ surface; object-contain keeps arbitrary-ratio logos uncropped. */
39
+ .brand-tile:has(> img) {
40
+ @apply bg-surface border border-border p-0.5;
41
+ }
42
+ .brand-tile > img {
43
+ @apply size-full object-contain rounded-[inherit];
44
+ }
9
45
  }
@@ -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
  }
@@ -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,13 @@
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";
19
22
  @import "./textarea.css";
20
23
  @import "./checkbox.css";
21
24
  @import "./radio.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,46 @@
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
+ /* Clicks pass through the icon to the input beneath it. */
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
+ .input-icon > .input ~ :where(i, svg) {
32
+ right: var(--input-icon-inset);
33
+ }
34
+
35
+ .input-icon:has(> :where(i, svg):first-child) > .input {
36
+ padding-left: var(--input-icon-pad);
37
+ }
38
+
39
+ .input-icon:has(> .input ~ :where(i, svg)) > .input {
40
+ padding-right: var(--input-icon-pad);
41
+ }
42
+
43
+ .input-icon:has(> .input:disabled) > :where(i, svg) {
44
+ @apply opacity-50;
45
+ }
46
+ }
@@ -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
+ }
@@ -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),