@devslab/dds-css 0.3.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/LICENSE +74 -0
- package/README.ko.md +25 -0
- package/README.md +26 -0
- package/dist/components/avatar.css +47 -0
- package/dist/components/badge.css +47 -0
- package/dist/components/base.css +31 -0
- package/dist/components/button.css +114 -0
- package/dist/components/card.css +38 -0
- package/dist/components/checkbox-radio.css +91 -0
- package/dist/components/chip.css +51 -0
- package/dist/components/dialog.css +58 -0
- package/dist/components/divider.css +18 -0
- package/dist/components/emptystate.css +47 -0
- package/dist/components/iconbutton.css +71 -0
- package/dist/components/listrow.css +81 -0
- package/dist/components/select.css +66 -0
- package/dist/components/skeleton.css +32 -0
- package/dist/components/spinner.css +34 -0
- package/dist/components/switch.css +78 -0
- package/dist/components/tabs.css +59 -0
- package/dist/components/textarea.css +42 -0
- package/dist/components/textfield.css +63 -0
- package/dist/components/toast.css +72 -0
- package/dist/components/tooltip.css +43 -0
- package/dist/dds.css +1206 -0
- package/package.json +28 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/* IconButton — a Button whose only content is an icon (spec §4.3 v1 inventory).
|
|
2
|
+
* Same state set and sizes as .dds-btn (32/40/48 from the spacing scale) so an
|
|
3
|
+
* icon button never lands a few px off the text button next to it — the row
|
|
4
|
+
* misalignment this house rejects. The icon inherits currentColor.
|
|
5
|
+
* Markup MUST carry an accessible name: aria-label (spec §6). */
|
|
6
|
+
|
|
7
|
+
.dds-iconbtn {
|
|
8
|
+
appearance: none;
|
|
9
|
+
display: inline-flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
justify-content: center;
|
|
12
|
+
flex: none;
|
|
13
|
+
inline-size: var(--dds-space-40);
|
|
14
|
+
block-size: var(--dds-space-40);
|
|
15
|
+
border: 1px solid transparent;
|
|
16
|
+
border-radius: var(--dds-radius-md);
|
|
17
|
+
background: none;
|
|
18
|
+
color: var(--dds-color-text-primary);
|
|
19
|
+
cursor: pointer;
|
|
20
|
+
transition: background-color var(--dds-motion-duration-fast) var(--dds-motion-easing-standard);
|
|
21
|
+
-webkit-tap-highlight-color: transparent;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.dds-iconbtn:not([disabled]):hover {
|
|
25
|
+
background: var(--dds-color-overlay-hover-tint);
|
|
26
|
+
}
|
|
27
|
+
.dds-iconbtn:not([disabled]):active {
|
|
28
|
+
background: var(--dds-color-overlay-pressed-tint);
|
|
29
|
+
}
|
|
30
|
+
.dds-iconbtn:focus-visible {
|
|
31
|
+
outline: var(--dds-space-2) solid var(--dds-color-border-focus);
|
|
32
|
+
outline-offset: var(--dds-space-2);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* opacity is allowed for disabled only (spec §4.2) */
|
|
36
|
+
.dds-iconbtn[disabled] {
|
|
37
|
+
opacity: 0.45;
|
|
38
|
+
cursor: not-allowed;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* variants ---------------------------------------------------------------- */
|
|
42
|
+
|
|
43
|
+
.dds-iconbtn--secondary {
|
|
44
|
+
border-color: var(--dds-color-border-strong);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.dds-iconbtn--danger {
|
|
48
|
+
color: var(--dds-color-status-danger);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* sizes ------------------------------------------------------------------- */
|
|
52
|
+
|
|
53
|
+
/* --sm is 32×32: still above the 24×24 web floor, but below the 44×44 mobile
|
|
54
|
+
* target — on touch surfaces keep the default size (spec §6). */
|
|
55
|
+
.dds-iconbtn--sm {
|
|
56
|
+
inline-size: var(--dds-space-32);
|
|
57
|
+
block-size: var(--dds-space-32);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.dds-iconbtn--lg {
|
|
61
|
+
inline-size: var(--dds-space-48);
|
|
62
|
+
block-size: var(--dds-space-48);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@media (pointer: coarse) {
|
|
66
|
+
.dds-iconbtn,
|
|
67
|
+
.dds-iconbtn--sm {
|
|
68
|
+
min-inline-size: 44px;
|
|
69
|
+
min-block-size: 44px;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/* ListRow — one item in a list: leading slot, body (title + sub), trailing
|
|
2
|
+
* slot. House rule this encodes: rows are self-contained and rich — if the
|
|
3
|
+
* information fits in the row, it does not get deferred to a detail page.
|
|
4
|
+
*
|
|
5
|
+
* --interactive is for rows that are themselves a button/link (use a real
|
|
6
|
+
* <button> or <a> so keyboard and AT get it for free). The focus ring is
|
|
7
|
+
* inset because a full-bleed row has no outer margin to draw into. */
|
|
8
|
+
|
|
9
|
+
.dds-listrow {
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
gap: var(--dds-space-12);
|
|
13
|
+
width: 100%;
|
|
14
|
+
padding: var(--dds-space-12) var(--dds-space-16);
|
|
15
|
+
background: none;
|
|
16
|
+
border: none;
|
|
17
|
+
text-align: start;
|
|
18
|
+
font-family: var(--dds-font-family-sans);
|
|
19
|
+
color: var(--dds-color-text-primary);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.dds-listrow + .dds-listrow {
|
|
23
|
+
border-top: 1px solid var(--dds-color-border-default);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.dds-listrow__body {
|
|
27
|
+
flex: 1;
|
|
28
|
+
min-width: 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* display:block so the two lines stack whether the markup uses <p> or <span>
|
|
32
|
+
* (a <span> title and sub otherwise render on one line) */
|
|
33
|
+
.dds-listrow__title {
|
|
34
|
+
display: block;
|
|
35
|
+
margin: 0;
|
|
36
|
+
font-size: var(--dds-typo-body-2-font-size);
|
|
37
|
+
line-height: var(--dds-typo-body-2-line-height);
|
|
38
|
+
font-weight: var(--dds-typo-label-font-weight);
|
|
39
|
+
/* long titles truncate rather than reflowing the row height */
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
text-overflow: ellipsis;
|
|
42
|
+
white-space: nowrap;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.dds-listrow__sub {
|
|
46
|
+
display: block;
|
|
47
|
+
margin: 0;
|
|
48
|
+
font-size: var(--dds-typo-caption-font-size);
|
|
49
|
+
line-height: var(--dds-typo-caption-line-height);
|
|
50
|
+
color: var(--dds-color-text-secondary);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.dds-listrow__actions {
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
gap: var(--dds-space-4);
|
|
57
|
+
flex: none;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* interactive ------------------------------------------------------------- */
|
|
61
|
+
|
|
62
|
+
.dds-listrow--interactive {
|
|
63
|
+
cursor: pointer;
|
|
64
|
+
transition: background-color var(--dds-motion-duration-fast) var(--dds-motion-easing-standard);
|
|
65
|
+
}
|
|
66
|
+
.dds-listrow--interactive:not([disabled]):hover {
|
|
67
|
+
background: var(--dds-color-overlay-hover-tint);
|
|
68
|
+
}
|
|
69
|
+
.dds-listrow--interactive:not([disabled]):active {
|
|
70
|
+
background: var(--dds-color-overlay-pressed-tint);
|
|
71
|
+
}
|
|
72
|
+
.dds-listrow--interactive:focus-visible {
|
|
73
|
+
outline: var(--dds-space-2) solid var(--dds-color-border-focus);
|
|
74
|
+
outline-offset: calc(var(--dds-space-2) * -1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* opacity is allowed for disabled only (spec §4.2) */
|
|
78
|
+
.dds-listrow[disabled] {
|
|
79
|
+
opacity: 0.45;
|
|
80
|
+
cursor: not-allowed;
|
|
81
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/* Select — native <select> with the native arrow replaced. Leaving the OS
|
|
2
|
+
* arrow in place is a rejection criterion in this house: the same control
|
|
3
|
+
* then looks different on every platform and never matches the input next to
|
|
4
|
+
* it. The wrapper owns the chevron so the element stays a real <select>
|
|
5
|
+
* (keyboard, mobile pickers, form semantics all free).
|
|
6
|
+
*
|
|
7
|
+
* Markup: <span class="dds-select"><select class="dds-select__input">…
|
|
8
|
+
* Lives inside .dds-field like .dds-input, so label/help/error come for free. */
|
|
9
|
+
|
|
10
|
+
.dds-select {
|
|
11
|
+
position: relative;
|
|
12
|
+
display: inline-flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
width: 100%;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.dds-select__input {
|
|
18
|
+
appearance: none;
|
|
19
|
+
font-family: var(--dds-font-family-sans);
|
|
20
|
+
font-size: var(--dds-typo-body-2-font-size);
|
|
21
|
+
line-height: var(--dds-typo-body-2-line-height);
|
|
22
|
+
font-weight: var(--dds-typo-body-2-font-weight);
|
|
23
|
+
width: 100%;
|
|
24
|
+
height: var(--dds-space-40);
|
|
25
|
+
padding-block: 0;
|
|
26
|
+
padding-inline: var(--dds-space-12) var(--dds-space-32);
|
|
27
|
+
background: var(--dds-color-bg-default);
|
|
28
|
+
color: var(--dds-color-text-primary);
|
|
29
|
+
border: 1px solid var(--dds-color-border-strong);
|
|
30
|
+
border-radius: var(--dds-radius-md);
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.dds-select__input:focus-visible {
|
|
35
|
+
outline: var(--dds-space-2) solid var(--dds-color-border-focus);
|
|
36
|
+
outline-offset: 0;
|
|
37
|
+
border-color: transparent;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* opacity is allowed for disabled only (spec §4.2) */
|
|
41
|
+
.dds-select__input[disabled] {
|
|
42
|
+
opacity: 0.45;
|
|
43
|
+
cursor: not-allowed;
|
|
44
|
+
background: var(--dds-color-bg-subtle);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.dds-field--error .dds-select__input,
|
|
48
|
+
.dds-select__input[aria-invalid="true"] {
|
|
49
|
+
border-color: var(--dds-color-status-danger);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Chevron glyph geometry (8px box, 1.6px stroke) is drawn, not spaced — the
|
|
53
|
+
* same allowance as the 6px badge dot. translateY(-70%) centers the drawn
|
|
54
|
+
* corner, which sits below the box center once rotated. */
|
|
55
|
+
.dds-select::after {
|
|
56
|
+
content: "";
|
|
57
|
+
position: absolute;
|
|
58
|
+
inset-block-start: 50%;
|
|
59
|
+
inset-inline-end: var(--dds-space-12);
|
|
60
|
+
width: 8px;
|
|
61
|
+
height: 8px;
|
|
62
|
+
border-inline-end: 1.6px solid var(--dds-color-text-muted);
|
|
63
|
+
border-block-end: 1.6px solid var(--dds-color-text-muted);
|
|
64
|
+
transform: translateY(-70%) rotate(45deg);
|
|
65
|
+
pointer-events: none;
|
|
66
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/* Skeleton — loading placeholder. The fill is an alpha tint (spec §3.1:
|
|
2
|
+
* overlays/tints are alpha-only) so it sits naturally on default and subtle
|
|
3
|
+
* backgrounds in both themes. Size is the consumer's job (width/height on
|
|
4
|
+
* the element); --text matches a line of the surrounding font. Mark the
|
|
5
|
+
* loading region with aria-busy="true" and keep skeletons aria-hidden. */
|
|
6
|
+
|
|
7
|
+
.dds-skeleton {
|
|
8
|
+
display: block;
|
|
9
|
+
background: var(--dds-color-overlay-pressed-tint);
|
|
10
|
+
border-radius: var(--dds-radius-sm);
|
|
11
|
+
animation: dds-skeleton-pulse 1600ms var(--dds-motion-easing-standard) infinite;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.dds-skeleton--text {
|
|
15
|
+
height: 1em;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.dds-skeleton--circle {
|
|
19
|
+
border-radius: var(--dds-radius-full);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@keyframes dds-skeleton-pulse {
|
|
23
|
+
50% {
|
|
24
|
+
opacity: 0.4;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@media (prefers-reduced-motion: reduce) {
|
|
29
|
+
.dds-skeleton {
|
|
30
|
+
animation: none;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* Spinner — indeterminate progress. Inherits color via currentColor so it
|
|
2
|
+
* reads correctly on any surface (inside a primary button it spins in
|
|
3
|
+
* on-brand). The rotation is a continuous state indicator, not a transition,
|
|
4
|
+
* so its period is not a motion token; under reduced motion it slows down
|
|
5
|
+
* instead of freezing (a stopped spinner reads as a hang — the motion IS the
|
|
6
|
+
* state, spec §3.6). Standalone spinners need role="status" + aria-label. */
|
|
7
|
+
|
|
8
|
+
.dds-spinner {
|
|
9
|
+
display: inline-block;
|
|
10
|
+
flex: none;
|
|
11
|
+
width: var(--dds-space-16);
|
|
12
|
+
height: var(--dds-space-16);
|
|
13
|
+
border: 2px solid currentColor;
|
|
14
|
+
border-top-color: transparent;
|
|
15
|
+
border-radius: var(--dds-radius-full);
|
|
16
|
+
animation: dds-spin 800ms linear infinite;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.dds-spinner--lg {
|
|
20
|
+
width: var(--dds-space-24);
|
|
21
|
+
height: var(--dds-space-24);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@keyframes dds-spin {
|
|
25
|
+
to {
|
|
26
|
+
transform: rotate(360deg);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@media (prefers-reduced-motion: reduce) {
|
|
31
|
+
.dds-spinner {
|
|
32
|
+
animation-duration: 1600ms;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/* Switch — an immediate on/off (a checkbox that applies itself). Use a
|
|
2
|
+
* checkbox when the value only takes effect on submit.
|
|
3
|
+
* Markup: <input type="checkbox" role="switch" class="dds-switch__input">
|
|
4
|
+
*
|
|
5
|
+
* Track 36×20 and knob 16px are glyph geometry, not spacing. The knob is
|
|
6
|
+
* on-brand (zinc.950) when the track is brand-filled — the same rule that
|
|
7
|
+
* keeps white off cyan (spec §3.1) — and an elevated surface when off, so it
|
|
8
|
+
* reads as a raised knob in both themes. */
|
|
9
|
+
|
|
10
|
+
.dds-switch {
|
|
11
|
+
display: inline-flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
gap: var(--dds-space-8);
|
|
14
|
+
font-family: var(--dds-font-family-sans);
|
|
15
|
+
font-size: var(--dds-typo-body-2-font-size);
|
|
16
|
+
line-height: var(--dds-typo-body-2-line-height);
|
|
17
|
+
color: var(--dds-color-text-primary);
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.dds-switch__input {
|
|
22
|
+
appearance: none;
|
|
23
|
+
position: relative;
|
|
24
|
+
flex: none;
|
|
25
|
+
margin: 0;
|
|
26
|
+
width: 36px;
|
|
27
|
+
height: 20px;
|
|
28
|
+
border-radius: var(--dds-radius-full);
|
|
29
|
+
background: var(--dds-color-text-muted);
|
|
30
|
+
cursor: inherit;
|
|
31
|
+
transition: background-color var(--dds-motion-duration-base) var(--dds-motion-easing-standard);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.dds-switch__input::before {
|
|
35
|
+
content: "";
|
|
36
|
+
position: absolute;
|
|
37
|
+
inset-block-start: 2px;
|
|
38
|
+
inset-inline-start: 2px;
|
|
39
|
+
width: 16px;
|
|
40
|
+
height: 16px;
|
|
41
|
+
border-radius: var(--dds-radius-full);
|
|
42
|
+
background: var(--dds-color-bg-elevated);
|
|
43
|
+
box-shadow: var(--dds-elevation-1);
|
|
44
|
+
transition: transform var(--dds-motion-duration-base) var(--dds-motion-easing-standard);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.dds-switch__input:checked {
|
|
48
|
+
background: var(--dds-color-bg-brand);
|
|
49
|
+
}
|
|
50
|
+
.dds-switch__input:checked::before {
|
|
51
|
+
background: var(--dds-color-text-on-brand);
|
|
52
|
+
transform: translateX(16px);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
[dir="rtl"] .dds-switch__input:checked::before {
|
|
56
|
+
transform: translateX(-16px);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.dds-switch__input:focus-visible {
|
|
60
|
+
outline: var(--dds-space-2) solid var(--dds-color-border-focus);
|
|
61
|
+
outline-offset: var(--dds-space-2);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* opacity is allowed for disabled only (spec §4.2) — on the label, once */
|
|
65
|
+
.dds-switch:has(.dds-switch__input[disabled]) {
|
|
66
|
+
opacity: 0.45;
|
|
67
|
+
cursor: not-allowed;
|
|
68
|
+
}
|
|
69
|
+
.dds-switch__input[disabled] {
|
|
70
|
+
cursor: not-allowed;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@media (prefers-reduced-motion: reduce) {
|
|
74
|
+
.dds-switch__input,
|
|
75
|
+
.dds-switch__input::before {
|
|
76
|
+
transition: none;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* Tabs — ink-underline tabs (the shell the admin surfaces converged on).
|
|
2
|
+
* Markup is the ARIA tabs pattern: .dds-tabs[role="tablist"] >
|
|
3
|
+
* .dds-tab[role="tab"][aria-selected] each controlling a [role="tabpanel"].
|
|
4
|
+
* Selection is aria-selected, never a class alone — the state has to be
|
|
5
|
+
* announced, and color is not the only signal (the underline carries it too).
|
|
6
|
+
*
|
|
7
|
+
* The button border-radius reset is deliberate: a shared radius reset leaking
|
|
8
|
+
* into the underline bows its ends — this house has shipped that bug once. */
|
|
9
|
+
|
|
10
|
+
.dds-tabs {
|
|
11
|
+
display: flex;
|
|
12
|
+
gap: var(--dds-space-4);
|
|
13
|
+
border-bottom: 1px solid var(--dds-color-border-default);
|
|
14
|
+
font-family: var(--dds-font-family-sans);
|
|
15
|
+
overflow-x: auto;
|
|
16
|
+
scrollbar-width: none;
|
|
17
|
+
}
|
|
18
|
+
.dds-tabs::-webkit-scrollbar {
|
|
19
|
+
display: none;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.dds-tab {
|
|
23
|
+
appearance: none;
|
|
24
|
+
flex: none;
|
|
25
|
+
padding: var(--dds-space-12);
|
|
26
|
+
background: none;
|
|
27
|
+
border: none;
|
|
28
|
+
border-radius: 0;
|
|
29
|
+
border-bottom: var(--dds-space-2) solid transparent;
|
|
30
|
+
/* the underline sits on the tablist's own 1px rule */
|
|
31
|
+
margin-bottom: -1px;
|
|
32
|
+
font-family: inherit;
|
|
33
|
+
font-size: var(--dds-typo-label-font-size);
|
|
34
|
+
line-height: var(--dds-typo-label-line-height);
|
|
35
|
+
font-weight: var(--dds-typo-label-font-weight);
|
|
36
|
+
color: var(--dds-color-text-secondary);
|
|
37
|
+
white-space: nowrap;
|
|
38
|
+
cursor: pointer;
|
|
39
|
+
transition: color var(--dds-motion-duration-fast) var(--dds-motion-easing-standard);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.dds-tab:not([disabled]):hover {
|
|
43
|
+
color: var(--dds-color-text-primary);
|
|
44
|
+
}
|
|
45
|
+
.dds-tab:focus-visible {
|
|
46
|
+
outline: var(--dds-space-2) solid var(--dds-color-border-focus);
|
|
47
|
+
outline-offset: calc(var(--dds-space-2) * -1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.dds-tab[aria-selected="true"] {
|
|
51
|
+
color: var(--dds-color-text-brand);
|
|
52
|
+
border-bottom-color: var(--dds-color-bg-brand);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* opacity is allowed for disabled only (spec §4.2) */
|
|
56
|
+
.dds-tab[disabled] {
|
|
57
|
+
opacity: 0.45;
|
|
58
|
+
cursor: not-allowed;
|
|
59
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/* Textarea — the multi-line member of the TextField family: it lives in the
|
|
2
|
+
* same .dds-field wrapper and shares label/help/error behavior with .dds-input.
|
|
3
|
+
* Row rule from the house UI discipline: a textarea gets its own row, never a
|
|
4
|
+
* row shared with single-line controls (their heights can't be reconciled).
|
|
5
|
+
* min-height is two control heights so an empty textarea already reads as
|
|
6
|
+
* multi-line. */
|
|
7
|
+
|
|
8
|
+
.dds-textarea {
|
|
9
|
+
font-family: var(--dds-font-family-sans);
|
|
10
|
+
font-size: var(--dds-typo-body-2-font-size);
|
|
11
|
+
line-height: var(--dds-typo-body-2-line-height);
|
|
12
|
+
font-weight: var(--dds-typo-body-2-font-weight);
|
|
13
|
+
min-height: calc(var(--dds-space-40) * 2);
|
|
14
|
+
padding: var(--dds-space-8) var(--dds-space-12);
|
|
15
|
+
background: var(--dds-color-bg-default);
|
|
16
|
+
color: var(--dds-color-text-primary);
|
|
17
|
+
border: 1px solid var(--dds-color-border-strong);
|
|
18
|
+
border-radius: var(--dds-radius-md);
|
|
19
|
+
resize: vertical;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.dds-textarea::placeholder {
|
|
23
|
+
color: var(--dds-color-text-muted);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.dds-textarea:focus-visible {
|
|
27
|
+
outline: var(--dds-space-2) solid var(--dds-color-border-focus);
|
|
28
|
+
outline-offset: 0;
|
|
29
|
+
border-color: transparent;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* opacity is allowed for disabled only (spec §4.2) */
|
|
33
|
+
.dds-textarea[disabled] {
|
|
34
|
+
opacity: 0.45;
|
|
35
|
+
cursor: not-allowed;
|
|
36
|
+
background: var(--dds-color-bg-subtle);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.dds-field--error .dds-textarea,
|
|
40
|
+
.dds-textarea[aria-invalid="true"] {
|
|
41
|
+
border-color: var(--dds-color-status-danger);
|
|
42
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/* TextField — label + input + help, error via .dds-field--error OR
|
|
2
|
+
* aria-invalid="true" on the input (markup should set aria-invalid +
|
|
3
|
+
* aria-describedby; error is never conveyed by border color alone — the
|
|
4
|
+
* help line carries the message, spec §6). */
|
|
5
|
+
|
|
6
|
+
.dds-field {
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
gap: var(--dds-space-4);
|
|
10
|
+
font-family: var(--dds-font-family-sans);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.dds-field__label {
|
|
14
|
+
font-size: var(--dds-typo-label-font-size);
|
|
15
|
+
line-height: var(--dds-typo-label-line-height);
|
|
16
|
+
font-weight: var(--dds-typo-label-font-weight);
|
|
17
|
+
color: var(--dds-color-text-primary);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.dds-input {
|
|
21
|
+
font-family: var(--dds-font-family-sans);
|
|
22
|
+
font-size: var(--dds-typo-body-2-font-size);
|
|
23
|
+
line-height: var(--dds-typo-body-2-line-height);
|
|
24
|
+
font-weight: var(--dds-typo-body-2-font-weight);
|
|
25
|
+
height: var(--dds-space-40);
|
|
26
|
+
padding: 0 var(--dds-space-12);
|
|
27
|
+
background: var(--dds-color-bg-default);
|
|
28
|
+
color: var(--dds-color-text-primary);
|
|
29
|
+
border: 1px solid var(--dds-color-border-strong);
|
|
30
|
+
border-radius: var(--dds-radius-md);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.dds-input::placeholder {
|
|
34
|
+
color: var(--dds-color-text-muted);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.dds-input:focus-visible {
|
|
38
|
+
outline: var(--dds-space-2) solid var(--dds-color-border-focus);
|
|
39
|
+
outline-offset: 0;
|
|
40
|
+
border-color: transparent;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* opacity is allowed for disabled only (spec §4.2) */
|
|
44
|
+
.dds-input[disabled] {
|
|
45
|
+
opacity: 0.45;
|
|
46
|
+
cursor: not-allowed;
|
|
47
|
+
background: var(--dds-color-bg-subtle);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.dds-field__help {
|
|
51
|
+
font-size: var(--dds-typo-caption-font-size);
|
|
52
|
+
line-height: var(--dds-typo-caption-line-height);
|
|
53
|
+
font-weight: var(--dds-typo-caption-font-weight);
|
|
54
|
+
color: var(--dds-color-text-muted);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.dds-field--error .dds-input,
|
|
58
|
+
.dds-input[aria-invalid="true"] {
|
|
59
|
+
border-color: var(--dds-color-status-danger);
|
|
60
|
+
}
|
|
61
|
+
.dds-field--error .dds-field__help {
|
|
62
|
+
color: var(--dds-color-status-danger);
|
|
63
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/* Toast — transient notice. Region markup:
|
|
2
|
+
* .dds-toast-region[role="status"] (polite) — use role="alert" per-toast
|
|
3
|
+
* only for danger. Toasts stack bottom-end; entry animates with
|
|
4
|
+
* duration.base + easing.enter and respects reduced motion. The status
|
|
5
|
+
* dot repeats the tone so color is not the only signal (spec §6) — same
|
|
6
|
+
* 6px dot as the badge. */
|
|
7
|
+
|
|
8
|
+
.dds-toast-region {
|
|
9
|
+
position: fixed;
|
|
10
|
+
z-index: 1100;
|
|
11
|
+
inset-block-end: var(--dds-space-16);
|
|
12
|
+
inset-inline-end: var(--dds-space-16);
|
|
13
|
+
display: grid;
|
|
14
|
+
gap: var(--dds-space-8);
|
|
15
|
+
justify-items: end;
|
|
16
|
+
pointer-events: none;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.dds-toast {
|
|
20
|
+
pointer-events: auto;
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
gap: var(--dds-space-8);
|
|
24
|
+
background: var(--dds-color-bg-elevated);
|
|
25
|
+
color: var(--dds-color-text-primary);
|
|
26
|
+
font-family: var(--dds-font-family-sans);
|
|
27
|
+
font-size: var(--dds-typo-body-2-font-size);
|
|
28
|
+
line-height: var(--dds-typo-body-2-line-height);
|
|
29
|
+
font-weight: var(--dds-typo-body-2-font-weight);
|
|
30
|
+
border: 1px solid var(--dds-color-border-default);
|
|
31
|
+
border-radius: var(--dds-radius-md);
|
|
32
|
+
box-shadow: var(--dds-elevation-2);
|
|
33
|
+
padding: var(--dds-space-12) var(--dds-space-16);
|
|
34
|
+
max-width: min(360px, calc(100vw - var(--dds-space-32)));
|
|
35
|
+
animation: dds-toast-in var(--dds-motion-duration-base) var(--dds-motion-easing-enter);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.dds-toast--success::before,
|
|
39
|
+
.dds-toast--warning::before,
|
|
40
|
+
.dds-toast--danger::before,
|
|
41
|
+
.dds-toast--info::before {
|
|
42
|
+
content: "";
|
|
43
|
+
flex: none;
|
|
44
|
+
width: 6px;
|
|
45
|
+
height: 6px;
|
|
46
|
+
border-radius: var(--dds-radius-full);
|
|
47
|
+
}
|
|
48
|
+
.dds-toast--success::before {
|
|
49
|
+
background: var(--dds-color-status-success);
|
|
50
|
+
}
|
|
51
|
+
.dds-toast--warning::before {
|
|
52
|
+
background: var(--dds-color-status-warning);
|
|
53
|
+
}
|
|
54
|
+
.dds-toast--danger::before {
|
|
55
|
+
background: var(--dds-color-status-danger);
|
|
56
|
+
}
|
|
57
|
+
.dds-toast--info::before {
|
|
58
|
+
background: var(--dds-color-status-info);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@keyframes dds-toast-in {
|
|
62
|
+
from {
|
|
63
|
+
opacity: 0;
|
|
64
|
+
transform: translateY(var(--dds-space-8));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@media (prefers-reduced-motion: reduce) {
|
|
69
|
+
.dds-toast {
|
|
70
|
+
animation: none;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* Tooltip (web only — spec §4.3; on native the same information goes in a
|
|
2
|
+
* label or a sheet). A supplementary hint on an inverse surface, shown on
|
|
3
|
+
* hover AND focus-within so it is reachable from the keyboard.
|
|
4
|
+
*
|
|
5
|
+
* Contract: the tooltip may never carry information that is required to use
|
|
6
|
+
* the control — an icon button still needs its own aria-label, and the bubble
|
|
7
|
+
* is referenced with aria-describedby, not aria-label (a describedby bubble
|
|
8
|
+
* that never opens still reaches AT; a hover-only one does not). */
|
|
9
|
+
|
|
10
|
+
.dds-tooltip {
|
|
11
|
+
position: relative;
|
|
12
|
+
display: inline-flex;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.dds-tooltip__bubble {
|
|
16
|
+
position: absolute;
|
|
17
|
+
inset-block-end: calc(100% + var(--dds-space-8));
|
|
18
|
+
inset-inline-start: 50%;
|
|
19
|
+
transform: translateX(-50%);
|
|
20
|
+
z-index: 1200; /* above dialog (1000) and toast (1100) */
|
|
21
|
+
padding: var(--dds-space-4) var(--dds-space-8);
|
|
22
|
+
border-radius: var(--dds-radius-sm);
|
|
23
|
+
background: var(--dds-color-bg-inverse);
|
|
24
|
+
color: var(--dds-color-text-on-inverse);
|
|
25
|
+
font-family: var(--dds-font-family-sans);
|
|
26
|
+
font-size: var(--dds-typo-caption-font-size);
|
|
27
|
+
line-height: var(--dds-typo-caption-line-height);
|
|
28
|
+
white-space: nowrap;
|
|
29
|
+
pointer-events: none;
|
|
30
|
+
opacity: 0;
|
|
31
|
+
transition: opacity var(--dds-motion-duration-fast) var(--dds-motion-easing-standard);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.dds-tooltip:hover .dds-tooltip__bubble,
|
|
35
|
+
.dds-tooltip:focus-within .dds-tooltip__bubble {
|
|
36
|
+
opacity: 1;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@media (prefers-reduced-motion: reduce) {
|
|
40
|
+
.dds-tooltip__bubble {
|
|
41
|
+
transition: none;
|
|
42
|
+
}
|
|
43
|
+
}
|