@attrus-ds/core 1.0.19 → 1.0.20
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.
|
@@ -361,7 +361,8 @@
|
|
|
361
361
|
in a segmented control, and styling from it keeps markup and look in step. */
|
|
362
362
|
.btn-group .btn[aria-pressed="true"] {
|
|
363
363
|
box-shadow: inset 0 0 0 999px color-mix(in srgb, currentColor 14%, transparent);
|
|
364
|
-
|
|
364
|
+
/* No weight change: the fill already marks the selection, and a bolder label
|
|
365
|
+
widens the segment on toggle, nudging every neighbour sideways. */
|
|
365
366
|
/* The overlay stops at the border, and the next segment overlaps this one by a
|
|
366
367
|
hairline — so without this the selected segment ended in a 1px light strip
|
|
367
368
|
(its own untinted border, covered by the neighbour's fill). Lifting it above
|
|
@@ -1302,3 +1302,121 @@ textarea.input.input-lg { height: auto; min-height: 120px; padding: var(--space-
|
|
|
1302
1302
|
background: color-mix(in srgb, var(--color-pure-white) 6%, transparent);
|
|
1303
1303
|
border-color: var(--color-border-inverse);
|
|
1304
1304
|
}
|
|
1305
|
+
|
|
1306
|
+
/* ----------------------------------------------------------------
|
|
1307
|
+
INSET ACTION BUTTON — .fix-btn reads as a button, not a label
|
|
1308
|
+
----------------------------------------------------------------
|
|
1309
|
+
A flush segment (divider + field fill) is the right language for a
|
|
1310
|
+
SELECTOR: it qualifies the value, so it belongs to the field's surface, and
|
|
1311
|
+
the chevron already says "dropdown". But the same treatment on an ACTION
|
|
1312
|
+
made it read as a static label glued to the field. An action is its own
|
|
1313
|
+
control, so it sits INSIDE the field as a button: inset from the edge,
|
|
1314
|
+
its own border and radius, its own fill. .fix-select is untouched.
|
|
1315
|
+
The inset is 6px on every side; the button's radius is the field's minus
|
|
1316
|
+
that inset, so the two curves stay concentric.
|
|
1317
|
+
---------------------------------------------------------------- */
|
|
1318
|
+
:is(.group, .pill-search) > .fix-btn {
|
|
1319
|
+
--fix-inset: 6px;
|
|
1320
|
+
align-self: stretch;
|
|
1321
|
+
height: auto;
|
|
1322
|
+
margin: var(--fix-inset);
|
|
1323
|
+
padding: 0 var(--space-3);
|
|
1324
|
+
border: var(--border-hairline) solid var(--color-border-default);
|
|
1325
|
+
/* Concentric (field radius − inset) comes out ~2px on a 6px field, which
|
|
1326
|
+
reads as a square chip. Floor it at --radius-xs so it still reads as a
|
|
1327
|
+
button; the curves stay visually parallel at this size. */
|
|
1328
|
+
border-radius: max(var(--radius-xs), calc(var(--shape-control) - var(--fix-inset)));
|
|
1329
|
+
background: var(--color-background-surface);
|
|
1330
|
+
color: var(--color-foreground-default);
|
|
1331
|
+
box-shadow: none;
|
|
1332
|
+
}
|
|
1333
|
+
/* the flush-segment dividers do not apply to a button that has its own border */
|
|
1334
|
+
:is(.group, .pill-search) > .fix-btn.l,
|
|
1335
|
+
:is(.group, .pill-search) > .fix-btn.r { border: var(--border-hairline) solid var(--color-border-default); }
|
|
1336
|
+
:is(.group, .pill-search) > .fix-btn.l { margin-right: 0; }
|
|
1337
|
+
:is(.group, .pill-search) > .fix-btn.r { margin-left: 0; }
|
|
1338
|
+
:is(.group, .pill-search) > .fix-btn:hover { background: var(--color-background-hover); border-color: var(--color-border-strong); color: var(--color-foreground-default); }
|
|
1339
|
+
:is(.group, .pill-search) > .fix-btn:active { background: var(--color-background-press); }
|
|
1340
|
+
:is(.group, .pill-search) > .fix-btn:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--color-border-focus); }
|
|
1341
|
+
:is(.group, .pill-search) > .fix-btn.is-primary {
|
|
1342
|
+
background: var(--color-brand-solid);
|
|
1343
|
+
border-color: transparent;
|
|
1344
|
+
color: var(--color-foreground-on-solid);
|
|
1345
|
+
}
|
|
1346
|
+
:is(.group, .pill-search) > .fix-btn.is-primary:hover { background: var(--color-brand-solid-hover); border-color: transparent; color: var(--color-foreground-on-solid); }
|
|
1347
|
+
:is(.group, .pill-search) > .fix-btn.is-primary:focus-visible { box-shadow: 0 0 0 2px var(--color-border-focus); }
|
|
1348
|
+
|
|
1349
|
+
/* Pill field → pill button (concentric), and the end-padding the flush
|
|
1350
|
+
segment needed to clear the round cap is no longer needed */
|
|
1351
|
+
:is(.group.is-pill, .pill-search) > .fix-btn,
|
|
1352
|
+
:is(.group.is-pill, .pill-search) > .fix-btn:first-child,
|
|
1353
|
+
:is(.group.is-pill, .pill-search) > .fix-btn:last-child { border-radius: var(--radius-pill); padding: 0 var(--space-4); }
|
|
1354
|
+
/* the pill clips its children for the flush selector; a clipped focus ring
|
|
1355
|
+
on the inset button would be cut, so the ring is drawn inside instead */
|
|
1356
|
+
.pill-search > .fix-btn:focus-visible,
|
|
1357
|
+
.pill-search > .fix-btn.is-primary:focus-visible { box-shadow: inset 0 0 0 2px var(--color-border-focus); }
|
|
1358
|
+
.pill-search > .fix-btn.is-primary:focus-visible { box-shadow: inset 0 0 0 2px var(--color-foreground-on-solid), 0 0 0 0 transparent; }
|
|
1359
|
+
|
|
1360
|
+
/* Underline field → a compact bordered button on the rule, not floating text */
|
|
1361
|
+
.group.underline > .fix-btn {
|
|
1362
|
+
align-self: center;
|
|
1363
|
+
height: calc(100% - 12px);
|
|
1364
|
+
margin: 0 0 0 var(--space-2);
|
|
1365
|
+
padding: 0 var(--space-3);
|
|
1366
|
+
border: var(--border-hairline) solid var(--color-border-default);
|
|
1367
|
+
border-radius: var(--shape-control);
|
|
1368
|
+
background: var(--color-background-surface);
|
|
1369
|
+
color: var(--color-foreground-default);
|
|
1370
|
+
font-weight: var(--w-regular);
|
|
1371
|
+
text-decoration: none;
|
|
1372
|
+
}
|
|
1373
|
+
.group.underline > .fix-btn:first-child { margin: 0 var(--space-2) 0 0; }
|
|
1374
|
+
.group.underline > .fix-btn:last-child { margin-right: 0; }
|
|
1375
|
+
.group.underline > .fix-btn.is-primary { background: var(--color-brand-solid); border-color: transparent; color: var(--color-foreground-on-solid); font-weight: var(--w-regular); }
|
|
1376
|
+
.group.underline > .fix-btn.is-primary:hover { background: var(--color-brand-solid-hover); color: var(--color-foreground-on-solid); }
|
|
1377
|
+
.group.underline > .fix-btn:focus-visible,
|
|
1378
|
+
.group.underline > .fix-btn.is-primary:focus-visible { box-shadow: 0 0 0 2px var(--color-border-focus); text-decoration: none; }
|
|
1379
|
+
|
|
1380
|
+
/* on-inverse keeps the same shape, tinted for dark surfaces */
|
|
1381
|
+
.group.on-inverse > .fix-btn:not(.is-primary),
|
|
1382
|
+
.pill-search.on-inverse > .fix-btn:not(.is-primary) {
|
|
1383
|
+
background: color-mix(in srgb, var(--color-pure-white) 8%, transparent);
|
|
1384
|
+
border-color: var(--color-border-inverse);
|
|
1385
|
+
color: var(--color-foreground-inverse-default);
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/* ----------------------------------------------------------------
|
|
1389
|
+
ADDON VARIANTS — the inset button speaks the Button vocabulary
|
|
1390
|
+
----------------------------------------------------------------
|
|
1391
|
+
.fix-btn takes the same emphasis ladder as .btn, with the same tokens, so
|
|
1392
|
+
"Apply" inside a field and "Apply" beside it read identically:
|
|
1393
|
+
(none) / .is-secondary neutral surface + hairline border (default)
|
|
1394
|
+
.is-primary brand fill — THE action of this field
|
|
1395
|
+
.is-tertiary soft neutral fill, borderless — quiet affirmative
|
|
1396
|
+
.is-ghost nothing until hover — secondary tools (copy, clear)
|
|
1397
|
+
.is-danger destructive solid
|
|
1398
|
+
.is-danger-outline destructive, contextual
|
|
1399
|
+
.is-success strong positive (rare)
|
|
1400
|
+
Not offered: .link (it would read as a label again — the problem this button
|
|
1401
|
+
exists to solve) and .inverse (the field's own on-inverse handles dark).
|
|
1402
|
+
---------------------------------------------------------------- */
|
|
1403
|
+
:is(.group, .pill-search) > .fix-btn.is-tertiary {
|
|
1404
|
+
background: color-mix(in srgb, var(--color-foreground-default) 7%, transparent);
|
|
1405
|
+
border-color: transparent;
|
|
1406
|
+
color: var(--color-foreground-default);
|
|
1407
|
+
}
|
|
1408
|
+
:is(.group, .pill-search) > .fix-btn.is-tertiary:hover { background: color-mix(in srgb, var(--color-foreground-default) 12%, transparent); border-color: transparent; }
|
|
1409
|
+
:is(.group, .pill-search) > .fix-btn.is-ghost { background: transparent; border-color: transparent; color: var(--color-foreground-secondary); }
|
|
1410
|
+
:is(.group, .pill-search) > .fix-btn.is-ghost:hover { background: var(--color-background-hover); border-color: transparent; color: var(--color-foreground-default); }
|
|
1411
|
+
:is(.group, .pill-search) > .fix-btn.is-danger { background: var(--color-danger-500); border-color: transparent; color: var(--color-foreground-on-solid); }
|
|
1412
|
+
:is(.group, .pill-search) > .fix-btn.is-danger:hover { background: var(--color-danger-700); border-color: transparent; color: var(--color-foreground-on-solid); }
|
|
1413
|
+
:is(.group, .pill-search) > .fix-btn.is-danger-outline { background: var(--color-background-surface); border-color: var(--color-feedback-danger-border); color: var(--color-danger-500); }
|
|
1414
|
+
:is(.group, .pill-search) > .fix-btn.is-danger-outline:hover { background: var(--color-feedback-danger-background); border-color: var(--color-danger-500); color: var(--color-danger-500); }
|
|
1415
|
+
:is(.group, .pill-search) > .fix-btn.is-success { background: var(--color-success-500); border-color: transparent; color: var(--color-foreground-on-solid); }
|
|
1416
|
+
:is(.group, .pill-search) > .fix-btn.is-success:hover { background: var(--color-success-700); border-color: transparent; color: var(--color-foreground-on-solid); }
|
|
1417
|
+
/* the underline rules above set a surface fill; restate the variants there */
|
|
1418
|
+
.group.underline > .fix-btn.is-tertiary { background: color-mix(in srgb, var(--color-foreground-default) 7%, transparent); border-color: transparent; }
|
|
1419
|
+
.group.underline > .fix-btn.is-ghost { background: transparent; border-color: transparent; color: var(--color-foreground-secondary); }
|
|
1420
|
+
.group.underline > .fix-btn.is-danger { background: var(--color-danger-500); border-color: transparent; color: var(--color-foreground-on-solid); }
|
|
1421
|
+
.group.underline > .fix-btn.is-danger-outline { background: var(--color-background-surface); border-color: var(--color-feedback-danger-border); color: var(--color-danger-500); }
|
|
1422
|
+
.group.underline > .fix-btn.is-success { background: var(--color-success-500); border-color: transparent; color: var(--color-foreground-on-solid); }
|
|
@@ -3,9 +3,15 @@ import * as React from 'react';
|
|
|
3
3
|
export type InputSize = 'sm' | 'md' | 'lg';
|
|
4
4
|
export type InputStatus = 'default' | 'error' | 'success' | 'warning';
|
|
5
5
|
|
|
6
|
+
export type InputAddonVariant =
|
|
7
|
+
| 'primary' | 'secondary' | 'tertiary' | 'ghost'
|
|
8
|
+
| 'danger' | 'danger-outline' | 'success';
|
|
9
|
+
|
|
6
10
|
export type InputAddon =
|
|
7
11
|
| { type: 'select'; options: Array<string | { value: string; label: React.ReactNode }>; value?: string; onChange?: (value: string) => void; 'aria-label': string }
|
|
8
|
-
| { type: 'button'; label: React.ReactNode; icon?: React.ReactNode; onClick?: () => void;
|
|
12
|
+
| { type: 'button'; label: React.ReactNode; icon?: React.ReactNode; onClick?: () => void; variant?: InputAddonVariant;
|
|
13
|
+
/** @deprecated use variant: 'primary' */
|
|
14
|
+
primary?: boolean; disabled?: boolean; 'aria-label'?: string };
|
|
9
15
|
|
|
10
16
|
export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'prefix'> {
|
|
11
17
|
/** Field label (renders the .field wrapper). */
|
|
@@ -22,6 +22,13 @@ export type InputStatus = 'default' | 'error' | 'success' | 'warning';
|
|
|
22
22
|
`.fix-btn`). Static text belongs in `prefix`/`suffix`; use an addon only
|
|
23
23
|
when the cell is a CONTROL — a qualifier the user picks, or an action that
|
|
24
24
|
consumes this field. */
|
|
25
|
+
/** Button emphasis available to an addon. Same names and tokens as Button;
|
|
26
|
+
'link' and 'inverse' are left out — a link inside a field reads as a label,
|
|
27
|
+
and dark surfaces are the field's own on-inverse job. */
|
|
28
|
+
export type InputAddonVariant =
|
|
29
|
+
| 'primary' | 'secondary' | 'tertiary' | 'ghost'
|
|
30
|
+
| 'danger' | 'danger-outline' | 'success';
|
|
31
|
+
|
|
25
32
|
export type InputAddon =
|
|
26
33
|
| {
|
|
27
34
|
type: 'select';
|
|
@@ -36,7 +43,10 @@ export type InputAddon =
|
|
|
36
43
|
label: React.ReactNode;
|
|
37
44
|
icon?: React.ReactNode;
|
|
38
45
|
onClick?: () => void;
|
|
39
|
-
/**
|
|
46
|
+
/** Emphasis — the Button ladder, minus link/inverse. Default 'secondary'.
|
|
47
|
+
'primary' only when this is THE action of the field. */
|
|
48
|
+
variant?: InputAddonVariant;
|
|
49
|
+
/** @deprecated use variant: 'primary' */
|
|
40
50
|
primary?: boolean;
|
|
41
51
|
disabled?: boolean;
|
|
42
52
|
'aria-label'?: string;
|
|
@@ -103,7 +113,7 @@ function renderAddon(a: InputAddon, side: 'l' | 'r'): React.ReactNode {
|
|
|
103
113
|
return (
|
|
104
114
|
<button
|
|
105
115
|
type="button"
|
|
106
|
-
className={['fix-btn', side, a.primary ? '
|
|
116
|
+
className={['fix-btn', side, (() => { const v = a.variant || (a.primary ? 'primary' : 'secondary'); return v === 'secondary' ? '' : 'is-' + v; })()].filter(Boolean).join(' ')}
|
|
107
117
|
onClick={a.onClick}
|
|
108
118
|
disabled={a.disabled}
|
|
109
119
|
aria-label={a['aria-label']}
|
|
@@ -63,7 +63,7 @@ function renderAddon(a: InputAddon, side: 'l' | 'r'): React.ReactNode {
|
|
|
63
63
|
);
|
|
64
64
|
}
|
|
65
65
|
return (
|
|
66
|
-
<button type="button" className={['fix-btn', side, a.primary ? '
|
|
66
|
+
<button type="button" className={['fix-btn', side, (() => { const v = a.variant || (a.primary ? 'primary' : 'secondary'); return v === 'secondary' ? '' : 'is-' + v; })()].filter(Boolean).join(' ')}
|
|
67
67
|
onClick={a.onClick} disabled={a.disabled} aria-label={a['aria-label']}>
|
|
68
68
|
{a.icon}
|
|
69
69
|
{a.label}
|
package/package.json
CHANGED