@nxgt/material 3.19.0 → 3.21.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 +14 -0
- package/README.md +2 -2
- package/dist/components/ui/display/chip/chip-parts.css +17 -30
- package/dist/{components-CjC1GISf.js → components-CbD5WNVO.js} +8831 -8790
- package/dist/components.js +1 -1
- package/dist/lib/components/ui/dates/date-field/use-typed-date.d.ts +2 -2
- package/dist/lib/components/ui/dates/picker-trigger/typed-day.d.ts +7 -2
- package/dist/lib/components/ui/display/chip/partials/chip-dismiss-button.d.ts +1 -1
- package/dist/lib/components/ui/display/entity-header/entity-header.d.ts +12 -1
- package/dist/lib/components/ui/display/entity-header/partials/entity-header-leading.d.ts +10 -0
- package/dist/lib/components/ui/selection/switcher/partials/switcher-footer.d.ts +1 -1
- package/dist/lib/components/ui/selection/switcher/partials/switcher-popover.d.ts +3 -1
- package/dist/lib/components/ui/selection/switcher/partials/switcher-sheet.d.ts +3 -1
- package/dist/lib/components/ui/selection/switcher/partials/switcher-subtitle.d.ts +9 -0
- package/dist/lib/components/ui/selection/switcher/partials/switcher-trigger.d.ts +4 -1
- package/dist/lib/components/ui/selection/switcher/switcher.d.ts +1 -1
- package/dist/lib/components/ui/selection/switcher/types.d.ts +6 -2
- package/dist/main.js +1 -1
- package/dist/material.css +1 -1
- package/docs/components/data-display.md +17 -3
- package/docs/components/date-and-time.md +8 -4
- package/docs/components/selection-and-choice.md +13 -1
- package/docs/previews/date-time-datefield.webp +0 -0
- package/docs/previews/date-time-daterangefield.webp +0 -0
- package/docs/troubleshooting.md +65 -1
- package/lib/components/ui/display/chip/chip-parts.css +17 -30
- package/package.json +1 -1
|
@@ -399,7 +399,7 @@ import { Avatar, Chip } from '@nxgt/material/components';
|
|
|
399
399
|
| `aria-pressed` | `boolean` | — | a toggle chip: selected while pressed, with a check when nothing leads |
|
|
400
400
|
| `size` | `'default' \| 'sm' \| 'lg'` | `'default'` | 28px, 24px, or `lg`, a 44px box at every pointer (the touch target); every target stays at least 24px |
|
|
401
401
|
| `color` | `'default' \| 'primary' \| 'secondary' \| 'info' \| 'success' \| 'warning' \| 'error'` | neutral | tone: the edge (outlined), the fill and label (tonal, `filled`: a status look) or the dot and tint (tag). A `tag` without `color` (or with `default`) draws no dot |
|
|
402
|
-
| `onDismiss` | `() => void` | — | adds the ✕ button, named "Remove {label}" (`material.chip.dismiss-label`), `{label}` being the text `label` shows, a node's included; "Remove" when it shows none; the ✕ is drawn 18px (16px small)
|
|
402
|
+
| `onDismiss` | `() => void` | — | adds the ✕ button, named "Remove {label}" (`material.chip.dismiss-label`), `{label}` being the text `label` shows, a node's included; "Remove" when it shows none; the ✕ is drawn 18px (16px small) in its own 24x24 box (44x44 on a coarse pointer) |
|
|
403
403
|
| `dismissLabel` | `string \| ((label: string) => string)` | — | names the ✕ instead of "Remove {label}": a string as is, or a function of the text the chip shows |
|
|
404
404
|
| `onClick` | `MouseEventHandler` | — | makes the chip (or its body, when dismissible) a button; without it the chip is a `<span>` |
|
|
405
405
|
| `leading`, `avatar`, `trailing` | `ReactNode` | — | slots; `avatar` is shrunk to fit, `trailing` (a count) is never truncated |
|
|
@@ -633,7 +633,7 @@ the grid it is in; pass `className="col-start-full"` for a full-width row.
|
|
|
633
633
|
|
|
634
634
|

|
|
635
635
|
|
|
636
|
-
Page header with icon, title, status, metadata and actions.
|
|
636
|
+
Page header with icon (or a `leading` control), title, status, metadata and actions.
|
|
637
637
|
|
|
638
638
|
```tsx
|
|
639
639
|
import { Edit } from 'lucide-react';
|
|
@@ -651,13 +651,27 @@ import { Badge, EntityHeader } from '@nxgt/material/components';
|
|
|
651
651
|
| --- | --- | --- | --- |
|
|
652
652
|
| `title` | `ReactNode` | — | the heading (an `h1` unless `headingLevel` says otherwise); optional: without it no heading is rendered (see below) |
|
|
653
653
|
| `headingLevel` | `1 \| 2 \| 3` | `1` | the title's heading level: pass `2` or `3` when the page already has its `h1` (a list title beside a detail header); the look does not change |
|
|
654
|
-
| `icon`, `status` | `ReactNode` | — | before and after the title; `icon` is decoration, shown in an `aria-hidden` badge, so put nothing meaningful or focusable there |
|
|
654
|
+
| `icon`, `status` | `ReactNode` | — | before and after the title; `icon` is decoration, shown in an `aria-hidden` badge, so put nothing meaningful or focusable there; for a control, use `leading` |
|
|
655
|
+
| `leading` | `ReactNode` | — | an interactive control at the band's start, such as a [Switcher](selection-and-choice.md#switcher) of either variant: drawn as it is, at its natural size (at most 18rem wide beside the title, from a 384px band; narrower, it takes the band's width), in the accessibility tree, with no badge and no `aria-hidden`; on a phone it takes the band's width above the title. **It replaces `icon` when both are given** (a development warning says so). Root `data-slot="entity-header-leading"` |
|
|
655
656
|
| `metadata` | `{ label: string; value: ReactNode }[]` | `[]` | facts under the title, a description list: a muted label beside its value, no colon; a link in a value is drawn in the primary text colour (`text-primary-text`), not the prose link's info blue, and `nxgt-plain-link` still opts out |
|
|
656
657
|
| `stats` | `EntityStat[]` | `[]` | figures (counts, a mean) as compact `size="sm"` [StatCard](#statcard) tiles in a `StatCardGroup` (2×2 on a phone, a wrapping row beside) under the metadata; a `tone` paints a tile, `0` is a figure, an empty value drops the tile (see Stats below) |
|
|
657
658
|
| `actions` | `ResponsiveButtonProps[] \| ReactNode` | `[]` | an array becomes responsive buttons (icon-only on phones) |
|
|
658
659
|
|
|
659
660
|
The root carries `data-slot="entity-header"`; the metadata is a `dl` with `data-slot="entity-metadata"` (a muted `dt` label beside its `dd` value, no colon), and the actions sit in `data-slot="entity-header-actions"` (absent when there are none). Inside an EntityHeader that replaces the old `summary-data` slot and its `Label:` text, so update a selector or a test that read "Label: value".
|
|
660
661
|
|
|
662
|
+
**A control at the start.** `leading` takes a control, never `icon`: the icon badge is `aria-hidden`, which squashes a `Switcher` and makes axe report `aria-hidden-focus` (see [Troubleshooting](../troubleshooting.md#a-control-in-an-entityheaders-icon-is-hidden-from-assistive-tech-squashed-and-reports-aria-hidden-focus)).
|
|
663
|
+
|
|
664
|
+
```tsx
|
|
665
|
+
import { Badge, EntityHeader, Switcher } from '@nxgt/material/components';
|
|
666
|
+
|
|
667
|
+
<EntityHeader
|
|
668
|
+
leading={<Switcher variant="expressive" label="Group" value={group} onValueChange={setGroup} options={groups} />}
|
|
669
|
+
title="Attendance"
|
|
670
|
+
status={<Badge variant="success">Active</Badge>}
|
|
671
|
+
metadata={[{ label: 'Year', value: '2026-2027' }]}
|
|
672
|
+
/>
|
|
673
|
+
```
|
|
674
|
+
|
|
661
675
|
**Stats.** Figures (counts, a mean) go in `stats`, shown as compact [StatCard](#statcard) tiles (`size="sm"`) in a `StatCardGroup` (2×2 on a phone, a row that wraps beside) under the metadata; facts (dates, names) stay in `metadata`. A `tone` paints a tile (tonal), none leaves it outlined; `0` is a figure, an empty value drops the tile. The row is `data-slot="entity-stats"`, and the type `EntityStat` is exported.
|
|
662
676
|
|
|
663
677
|
```tsx
|
|
@@ -186,10 +186,14 @@ export function Delivery() {
|
|
|
186
186
|
The calendar opens on the month of `value`; with no value, `defaultMonth` sets
|
|
187
187
|
it.
|
|
188
188
|
|
|
189
|
+
**Migration:** a typed date outside `minDate`/`maxDate`/`disablePast`/`disableFuture` is no longer moved to the nearest allowed day on blur. It is kept, and the form refuses it. If your schema has no min/max rule, add one.
|
|
190
|
+
|
|
189
191
|
Every other `Calendar` prop (`disablePast`, `minDate`, …) is passed to the
|
|
190
192
|
calendar. A typed date outside `minDate`, `maxDate`, `disablePast` or
|
|
191
|
-
`disableFuture` is no value while it is typed, and
|
|
192
|
-
|
|
193
|
+
`disableFuture` is no value while it is typed, and is kept as entered once
|
|
194
|
+
focus leaves the field (never moved to another day: the form's rule refuses
|
|
195
|
+
it, the calendar keeps the day unpickable). A value you set from outside
|
|
196
|
+
(`setValue`) is shown and kept the same way. A typed day that `disabledDays`
|
|
193
197
|
rules out is refused: it stays on screen with no value (it is not moved to
|
|
194
198
|
another day). An Invalid Date is no value. `id` goes on the first
|
|
195
199
|
segment (the label's target); `aria-describedby` and `data-*` attributes on
|
|
@@ -362,8 +366,8 @@ is `{ from, to: undefined }`, and no start is `undefined`. An end before the
|
|
|
362
366
|
start is no `to` (it stays on screen, and becomes the `to` once the start is
|
|
363
367
|
moved before it), and a start typed past the end drops the end, as a click in
|
|
364
368
|
the calendar does. A date outside `minDate`, `maxDate`, `disablePast` or
|
|
365
|
-
`disableFuture` is no value while it is typed and
|
|
366
|
-
|
|
369
|
+
`disableFuture` is no value while it is typed and is kept as entered once
|
|
370
|
+
focus leaves the field (the form refuses it); a day `disabledDays` rules out is
|
|
367
371
|
refused, kept on screen with no value. Retyping the start keeps the end
|
|
368
372
|
while the start is incomplete. Typing a day keeps the time of day
|
|
369
373
|
of the end it replaces (`withTime`). The ends sit on one line while they fit
|
|
@@ -1197,7 +1197,17 @@ const RouterLink: AppShellLinkComponent = ({ href, ...props }) => <Link to={href
|
|
|
1197
1197
|
/>
|
|
1198
1198
|
```
|
|
1199
1199
|
|
|
1200
|
-
A title that does not fit the trigger is cut with an ellipsis: the span carries the full title in `aria-label` (what `expectNoTextOverflow` of `@nxgt/playwright` reads), hovering or focusing the trigger shows it with the subtitle in a tooltip, and the trigger's accessible name is the label, the full title and the subtitle.
|
|
1200
|
+
A title that does not fit the trigger is cut with an ellipsis: the span carries the full title in `aria-label` (what `expectNoTextOverflow` of `@nxgt/playwright` reads), hovering or focusing the trigger shows it with the subtitle in a tooltip, and the trigger's accessible name is the label, the full title and the subtitle. An option's subtitle that does not fit, in the trigger (default variant; the expressive pill keeps it for screen readers only), the menu or the panel, is treated the same way while it is cut: the full text in `aria-label` and, in the options, in `title`, and in the trigger's tooltip. The option's accessible name still reads the title and the subtitle once each.
|
|
1201
|
+
|
|
1202
|
+
**Choosing a link closes the panel.** In link mode the popover or the phone sheet closes when an option (or a footer link) is activated, as in select mode, and focus is **not** handed back to the trigger: the navigation owns it, so a route's own focus on the new page's title runs. `open` and `onOpenChange` control the panel from the app (close it from a route effect, or open it from a shortcut):
|
|
1203
|
+
|
|
1204
|
+
```tsx
|
|
1205
|
+
const [open, setOpen] = useState(false);
|
|
1206
|
+
|
|
1207
|
+
<Switcher label="Group" value={id} options={groups} getHref={(g) => `/groups/${g.id}`} linkComponent={RouterLink} open={open} onOpenChange={setOpen} />;
|
|
1208
|
+
```
|
|
1209
|
+
|
|
1210
|
+
To put a Switcher at the start of a page header, pass it as [EntityHeader](data-display.md#entityheader)'s `leading` (not `icon`).
|
|
1201
1211
|
|
|
1202
1212
|
| Prop | Type | Default | Effect |
|
|
1203
1213
|
| --- | --- | --- | --- |
|
|
@@ -1213,6 +1223,8 @@ A title that does not fit the trigger is cut with an ellipsis: the span carries
|
|
|
1213
1223
|
| `footer` | `{ label?, onSelect?, href? }` | — | a "Manage…" button (a link with `href`) at the end of the panel; `label` defaults to `material.switcher.manage` |
|
|
1214
1224
|
| `emptyLabel` | `ReactNode` | `material.switcher.empty` | shown when the search matches nothing |
|
|
1215
1225
|
| `sheet` | `boolean` | on a phone | `true` always opens a sheet, `false` never |
|
|
1226
|
+
| `open` | `boolean` | uncontrolled | controls the panel (popover, menu or sheet); pair with `onOpenChange` |
|
|
1227
|
+
| `onOpenChange` | `(open: boolean) => void` | — | called when the panel opens or closes, by the user or after a pick |
|
|
1216
1228
|
| `name`, button props | `ComponentProps<'button'>` | — | spread on the trigger; `name` is its `data-pw` |
|
|
1217
1229
|
|
|
1218
1230
|
`SwitcherItem`: `{ id, title, subtitle?, icon?, group?, initial?, tone?, badge?, href?, disabled? }`.
|
|
Binary file
|
|
Binary file
|
package/docs/troubleshooting.md
CHANGED
|
@@ -36,6 +36,8 @@ words in your error.
|
|
|
36
36
|
- [`IconRefError` from `IconSpritesProvider`](#iconreferror-from-iconspritesprovider)
|
|
37
37
|
- [An accessibility audit reports low contrast on a badge, a button, a toast or a menu entry](#an-accessibility-audit-reports-low-contrast-on-a-badge-a-button-a-toast-or-a-menu-entry)
|
|
38
38
|
- [`ARIA hidden element must not be focusable or contain focusable elements`](#aria-hidden-element-must-not-be-focusable-or-contain-focusable-elements)
|
|
39
|
+
- [A control in an `EntityHeader`'s `icon` is hidden from assistive tech, squashed, and reports `aria-hidden-focus`](#a-control-in-an-entityheaders-icon-is-hidden-from-assistive-tech-squashed-and-reports-aria-hidden-focus)
|
|
40
|
+
- [A `Switcher` in link mode leaves focus on the trigger after a navigation, and the new page's title never gets it](#a-switcher-in-link-mode-leaves-focus-on-the-trigger-after-a-navigation-and-the-new-pages-title-never-gets-it)
|
|
39
41
|
- [An accessibility audit reports low contrast on a tonal or outlined button, a selected list tile or a hovered badge link](#an-accessibility-audit-reports-low-contrast-on-a-tonal-or-outlined-button-a-selected-list-tile-or-a-hovered-badge-link)
|
|
40
42
|
- [An accessibility audit reports low contrast on error text in the dark theme](#an-accessibility-audit-reports-low-contrast-on-error-text-in-the-dark-theme)
|
|
41
43
|
- [An accessibility audit reports low contrast on primary text in the dark theme](#an-accessibility-audit-reports-low-contrast-on-primary-text-in-the-dark-theme)
|
|
@@ -180,6 +182,8 @@ words in your error.
|
|
|
180
182
|
- [`Module '"@nxgt/material"' has no exported member 'formatAmount'`](#module-nxgtmaterial-has-no-exported-member-formatamount)
|
|
181
183
|
- [An amount in yen shows no decimals, or one in dinars shows three](#an-amount-in-yen-shows-no-decimals-or-one-in-dinars-shows-three)
|
|
182
184
|
- [A date shows the day before](#a-date-shows-the-day-before)
|
|
185
|
+
- [A `DateField` with `disableFuture` (or `disablePast`, `minDate`, `maxDate`) moved my value to the previous day](#a-datefield-with-disablefuture-or-disablepast-mindate-maxdate-moved-my-value-to-the-previous-day)
|
|
186
|
+
- [axe `target-size` flags a chip's ✕](#axe-target-size-flags-a-chips-)
|
|
183
187
|
- [`Invalid hook call` in a `PrintDocument` `footer` function](#invalid-hook-call-in-a-printdocument-footer-function)
|
|
184
188
|
- [Body text runs under the footer or the page number](#body-text-runs-under-the-footer-or-the-page-number)
|
|
185
189
|
- [The `LOGO` box in `PrintHeader` is gone after upgrading](#the-logo-box-in-printheader-is-gone-after-upgrading)
|
|
@@ -956,6 +960,35 @@ Elements mounted after the layer opens are not made inert: mount them
|
|
|
956
960
|
first, or pass them to `inertOthers` in `keep` when they must stay
|
|
957
961
|
reachable.
|
|
958
962
|
|
|
963
|
+
### A control in an `EntityHeader`'s `icon` is hidden from assistive tech, squashed, and reports `aria-hidden-focus`
|
|
964
|
+
|
|
965
|
+
**When:** an `EntityHeader` gets a `Switcher`, a button or a link as its `icon`.
|
|
966
|
+
The control is drawn small and clipped inside a filled square, a screen reader
|
|
967
|
+
skips it, and an accessibility audit (axe) reports `aria-hidden-focus`.
|
|
968
|
+
**Why:** `icon` is decoration: it is wrapped in a filled, icon-sized badge with
|
|
969
|
+
`aria-hidden="true"`, so anything focusable inside it is an `aria-hidden`
|
|
970
|
+
subtree the keyboard still reaches.
|
|
971
|
+
**Fix:** put the control in `leading`: it is drawn as it is, at its natural
|
|
972
|
+
size, beside the title, with no badge and no `aria-hidden`. When both are
|
|
973
|
+
given, `leading` replaces `icon` (and warns in development).
|
|
974
|
+
|
|
975
|
+
```tsx
|
|
976
|
+
<EntityHeader
|
|
977
|
+
leading={<Switcher label="Group" value={group} onValueChange={setGroup} options={groups} />}
|
|
978
|
+
title="Attendance"
|
|
979
|
+
/>
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
### A `Switcher` in link mode leaves focus on the trigger after a navigation, and the new page's title never gets it
|
|
983
|
+
|
|
984
|
+
**When:** a `Switcher` with `getHref` and your router's `linkComponent`: you pick an option, the URL changes, but focus sits on the Switcher's trigger (or on the clicked link while the panel is open) instead of moving to the new page's title.
|
|
985
|
+
**Why:** a closing overlay hands focus back to its trigger, which ran after your route's own focus handling. Older versions also gave you no way to close the panel yourself.
|
|
986
|
+
**Fix:** update `@nxgt/material`: activating a link option closes the popover or sheet and no longer returns focus to the trigger, so the page's focus runs. To close it from the app, control it with `open` and `onOpenChange`.
|
|
987
|
+
|
|
988
|
+
```tsx
|
|
989
|
+
<Switcher label="Group" value={id} options={groups} getHref={(g) => `/groups/${g.id}`} linkComponent={RouterLink} open={open} onOpenChange={setOpen} />
|
|
990
|
+
```
|
|
991
|
+
|
|
959
992
|
### An accessibility audit reports low contrast on a tonal or outlined button, a selected list tile or a hovered badge link
|
|
960
993
|
|
|
961
994
|
**When:** axe (or Lighthouse) reports `color-contrast` on a `tonal` or
|
|
@@ -1062,7 +1095,7 @@ by default; `hitArea: false` brings back the box check), so these controls pass
|
|
|
1062
1095
|
on a coarse pointer when their `::after` can be hit: not under a sticky bar or
|
|
1063
1096
|
a toast, not clipped by an ancestor's `overflow`, and with its 44px square on
|
|
1064
1097
|
screen. Rows, the app shell's triggers and the calendar's days are real 44px
|
|
1065
|
-
boxes and pass with either version.
|
|
1098
|
+
boxes and pass with either version. A chip's ✕ is a real box too (24x24, 44x44 on a coarse pointer).
|
|
1066
1099
|
See [Theming, touch targets](./guides/theming.md#touch-targets).
|
|
1067
1100
|
|
|
1068
1101
|
### A Switch looks bigger, or a test reads `translate-x-*`
|
|
@@ -3230,6 +3263,37 @@ formatAmount(1234.5, 'JPY', { fractionDigits: 2 }); // '¥1,234.50'
|
|
|
3230
3263
|
<CurrencyField label="Price" currency="JPY" fractionDigits={2} value={price} onChange={setPrice} />
|
|
3231
3264
|
```
|
|
3232
3265
|
|
|
3266
|
+
### A `DateField` with `disableFuture` (or `disablePast`, `minDate`, `maxDate`) moved my value to the previous day
|
|
3267
|
+
|
|
3268
|
+
**When:** before the fix, a value past a bound (a `setValue` of "now + 1 h"
|
|
3269
|
+
shortly before midnight, or a date typed past `maxDate`) came back from the
|
|
3270
|
+
field about 24 hours earlier, and a form rule such as "not in the future"
|
|
3271
|
+
passed on a date nobody chose.
|
|
3272
|
+
**Why:** when focus left the field, it moved the value onto the bound's day and
|
|
3273
|
+
kept its time of day, so tomorrow 00:30 became today 00:30.
|
|
3274
|
+
**Fix:** upgrade: the field keeps the value it is given or that was typed, the
|
|
3275
|
+
calendar keeps the out-of-bounds days unpickable, and your form refuses the
|
|
3276
|
+
value. Add the rule to the schema (and a test that does not depend on the
|
|
3277
|
+
clock):
|
|
3278
|
+
|
|
3279
|
+
```tsx
|
|
3280
|
+
const schema = z.object({
|
|
3281
|
+
at: z.date().refine((d) => d <= new Date(), 'Must not be in the future'),
|
|
3282
|
+
});
|
|
3283
|
+
<DateFormField control={form.control} name="at" label="Recorded at" showTimePicker disableFuture />;
|
|
3284
|
+
```
|
|
3285
|
+
|
|
3286
|
+
### axe `target-size` flags a chip's ✕
|
|
3287
|
+
|
|
3288
|
+
**When:** before the fix, a scan of a desktop (fine-pointer) page flagged
|
|
3289
|
+
`[data-slot="chip-dismiss"]` (a `Chip` with `onDismiss`, the `Filter` bar's pills
|
|
3290
|
+
and active chips, the `ServerTable` chips) for a 16-18px box.
|
|
3291
|
+
**Why:** the 24px target was a `::after` pseudo-element, which axe does not
|
|
3292
|
+
measure.
|
|
3293
|
+
**Fix:** upgrade: the ✕ button's own box is 24x24 (44x44 on a coarse pointer),
|
|
3294
|
+
drawn and laid out as before. Remove any exclusion of `chip-dismiss` from your
|
|
3295
|
+
axe scan.
|
|
3296
|
+
|
|
3233
3297
|
### A date shows the day before
|
|
3234
3298
|
|
|
3235
3299
|
**When:** before 2.2.0, `formatDate('2026-03-01')` printed `Feb 28, 2026` on a
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* Chip: focus, the dismiss button (drawn
|
|
2
|
-
* small
|
|
2
|
+
* small in a 24x24 box, 44x44 on a coarse pointer, its own ring), disabled, the dashed
|
|
3
3
|
* "add" chip and the tones. */
|
|
4
4
|
@layer components {
|
|
5
5
|
/* Focus: the chip's ring (2px primary-text, 2px away); the ✕ its own. */
|
|
@@ -8,54 +8,41 @@
|
|
|
8
8
|
outline-color: var(--primary-text);
|
|
9
9
|
outline-width: 2px;
|
|
10
10
|
}
|
|
11
|
+
/* The ✕ is drawn small (--chip-x), but its own box is the target (#1584):
|
|
12
|
+
* axe's `target-size` reads the bounding box, never a pseudo-element. The
|
|
13
|
+
* box is 24x24 (44x44 on a coarse pointer, #1067, #1102); the padding with
|
|
14
|
+
* `background-clip: content-box` keeps the hover disc and the focus ring at
|
|
15
|
+
* the drawn size, and the negative margin takes the extra back, so the chip
|
|
16
|
+
* and the glyph are unchanged. */
|
|
11
17
|
.nxgt-chip > [data-slot="chip-dismiss"] {
|
|
18
|
+
--chip-hit: 24px;
|
|
19
|
+
--chip-pad: calc((var(--chip-hit) - var(--chip-x)) / 2);
|
|
12
20
|
position: relative;
|
|
13
21
|
display: inline-grid;
|
|
14
22
|
place-items: center;
|
|
15
23
|
flex: none;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
24
|
+
box-sizing: border-box;
|
|
25
|
+
width: var(--chip-hit);
|
|
26
|
+
height: var(--chip-hit);
|
|
27
|
+
margin: calc(-1 * var(--chip-pad));
|
|
28
|
+
padding: var(--chip-pad);
|
|
19
29
|
border: 0;
|
|
20
30
|
border-radius: 9999px;
|
|
21
31
|
background: transparent;
|
|
32
|
+
background-clip: content-box;
|
|
22
33
|
color: color-mix(in oklab, var(--foreground) 62%, transparent);
|
|
23
34
|
cursor: pointer;
|
|
24
35
|
/* Width 0 at rest: forced colours would paint a transparent outline. */
|
|
25
36
|
outline: 0 solid transparent;
|
|
26
|
-
outline-offset: 1px;
|
|
37
|
+
outline-offset: calc(1px - var(--chip-pad));
|
|
27
38
|
transition:
|
|
28
39
|
background-color 120ms ease-out,
|
|
29
40
|
color 120ms ease-out,
|
|
30
41
|
outline-color 120ms ease-out;
|
|
31
42
|
}
|
|
32
|
-
.nxgt-chip > [data-slot="chip-dismiss"]::after {
|
|
33
|
-
content: "";
|
|
34
|
-
position: absolute;
|
|
35
|
-
inset: calc((var(--chip-x) - 24px) / 2);
|
|
36
|
-
border-radius: 9999px;
|
|
37
|
-
}
|
|
38
|
-
/* Touch (#1067, #1102): on a coarse pointer the ✕ is a real 44x44 box,
|
|
39
|
-
* not a pseudo-element, which a tap-target check and axe do not measure.
|
|
40
|
-
* The negative margin takes the extra back, so the chip is unchanged, and
|
|
41
|
-
* the padding with `background-clip: content-box` keeps the hover disc
|
|
42
|
-
* and the focus ring at the drawn size (the outline is pulled in by the
|
|
43
|
-
* padding). */
|
|
44
43
|
@media (pointer: coarse) {
|
|
45
44
|
.nxgt-chip > [data-slot="chip-dismiss"] {
|
|
46
|
-
--chip-
|
|
47
|
-
box-sizing: border-box;
|
|
48
|
-
width: 44px;
|
|
49
|
-
height: 44px;
|
|
50
|
-
margin: calc(-1 * var(--chip-pad));
|
|
51
|
-
padding: var(--chip-pad);
|
|
52
|
-
background-clip: content-box;
|
|
53
|
-
outline-offset: calc(1px - var(--chip-pad));
|
|
54
|
-
}
|
|
55
|
-
/* The box is the target now: the 24px pseudo-element would reach
|
|
56
|
-
* past it. */
|
|
57
|
-
.nxgt-chip > [data-slot="chip-dismiss"]::after {
|
|
58
|
-
inset: 0;
|
|
45
|
+
--chip-hit: 44px;
|
|
59
46
|
}
|
|
60
47
|
}
|
|
61
48
|
.nxgt-chip > [data-slot="chip-dismiss"] > svg {
|