<x-chip> renders data-md-chip with its type and its states and parts as data-md-* attributes (elevated, disabled, selected, actionable, the icon, check slot, avatar, action and remove button), and every colour, size, target and transition moves into components/chip.css on tokens (plan step 36). Icons take size 18; the filter checkbox is md-visually-hidden. chips.js follows the renamed chip hooks. A disabled filter chip drawn as a label no longer shows a hover state layer, which M3 never gives a disabled control. Browser tests cover the 32px chip and the remove button's 48px target. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
445 lines
16 KiB
CSS
445 lines
16 KiB
CSS
/*
|
||
* <x-chip>: M3's assist, filter, input and suggestion chips (resources/views/components/chip.blade.php).
|
||
*
|
||
* From androidx Compose Material 3 (Chip.kt, AssistChipTokens, FilterChipTokens, InputChipTokens,
|
||
* SuggestionChipTokens at androidx/androidx 27cf9a7, Apache-2.0): 32px tall, the small corner,
|
||
* label-large, 18px icons and a 24px avatar; 16px padding beside a label and 8px beside an icon,
|
||
* 8px between (an input chip: 12px, 8px, and 4px beside an avatar). Compose draws the 1px border
|
||
* inside the chip; a CSS border takes room, so every padding beside it is 1px short of its token
|
||
* (7px for 8, 11px for 12, 15px for 16).
|
||
*
|
||
* Colours by type and state, each one role (never an opacity but M3's disabled 12% and 38%,
|
||
* tokens/state.css):
|
||
*
|
||
* flat outline-variant border; on-surface ink (assist) or on-surface-variant (the rest);
|
||
* the border turns on-surface (assist) or on-surface-variant while keyboard-focused
|
||
* elevated surface-container-low at elevation 1, 2 on hover, no border (not for input chips)
|
||
* selected a filter chip that is checked or pressed, an input chip marked `data-md-selected`:
|
||
* secondary-container with on-secondary-container ink, no border; a flat selected
|
||
* filter chip rises to elevation 1 on hover
|
||
* disabled on-surface ink at 38%; the border (flat) or the container (elevated, selected) at 12%
|
||
*
|
||
* Leading icons are primary on assist, suggestion and filter chips; on an input chip they are
|
||
* on-surface-variant, primary on a selected chip and while a chip with its own action is hovered,
|
||
* focused or pressed (`data-md-actionable`). A trailing icon is primary on assist and suggestion
|
||
* chips. A disabled chip's icons take its ink.
|
||
*
|
||
* The state layer is the chip's own ::before in the chip's ink (an input chip draws it on its
|
||
* action, since the remove button is a second control), at M3's hover, focus and press opacities;
|
||
* the focus indicator is the package's secondary ring outside the chip. Both the chip (or an input
|
||
* chip's action) and the remove button catch presses over 48×48 — M3 lets the target extend beyond
|
||
* the visible container. The filter chip's check grows in on the fast spatial spring and fades in
|
||
* on the slow effects one; it shrinks on default effects and fades on fast effects, as
|
||
* AnimatingChipContent does.
|
||
*
|
||
* [data-md-chip="assist|filter|input|suggestion"] the chip; data-md-elevated, data-md-disabled,
|
||
* data-md-selected, data-md-actionable,
|
||
* data-md-icon-start, data-md-icon-end,
|
||
* data-md-avatar, data-md-removable
|
||
* [data-md-chip-check-slot] a filter chip's check, over its icon when it has one
|
||
* [data-md-chip-icon], [data-md-chip-check]
|
||
* [data-md-chip-action] an input chip's own button or link (a <span> without one)
|
||
* [data-md-chip-avatar], [data-md-chip-icon], [data-md-chip-label]
|
||
* [data-md-chip-remove] an input chip's remove button
|
||
* [data-md-chip-label], [data-md-chip-icon], [data-md-chip-trailing]
|
||
*/
|
||
|
||
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
|
||
|
||
@import './icon.css';
|
||
/* TODO(step 36): @import './tooltip.css' once <x-tooltip> leaves Tailwind (actions stream). */
|
||
|
||
@layer material.components {
|
||
[data-md-chip] {
|
||
--chip-disabled-container: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-container-opacity) * 100%), transparent);
|
||
--chip-disabled-ink: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent);
|
||
|
||
position: relative;
|
||
display: inline-flex;
|
||
flex-shrink: 0;
|
||
align-items: center;
|
||
max-width: 100%;
|
||
height: 32px;
|
||
border: 1px solid var(--md-sys-color-outline-variant);
|
||
border-radius: var(--md-sys-shape-corner-sm);
|
||
color: var(--md-sys-color-on-surface-variant);
|
||
font: var(--md-sys-typescale-label-lg);
|
||
letter-spacing: var(--md-sys-typescale-label-lg-tracking);
|
||
font-variation-settings: normal;
|
||
white-space: nowrap;
|
||
-webkit-user-select: none;
|
||
user-select: none;
|
||
transition-property: background-color, border-color, color, box-shadow;
|
||
transition-duration: var(--md-sys-motion-effects-fast-duration);
|
||
transition-timing-function: var(--md-sys-motion-effects-fast);
|
||
}
|
||
|
||
/* ---- Assist, filter and suggestion: the chip is the control ------------------------------ */
|
||
|
||
[data-md-chip]:not([data-md-chip='input']) {
|
||
isolation: isolate;
|
||
padding-inline: 15px;
|
||
outline: none;
|
||
cursor: pointer;
|
||
|
||
/* The 48px target, past the chip's 32px. */
|
||
&::after {
|
||
content: '';
|
||
position: absolute;
|
||
inset-inline: 0;
|
||
top: 50%;
|
||
height: var(--md-sys-measurement-space600);
|
||
translate: 0 -50%;
|
||
}
|
||
|
||
&::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: -1;
|
||
border-radius: inherit;
|
||
background-color: currentColor;
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
||
}
|
||
|
||
@media (hover: hover) {
|
||
&:hover::before {
|
||
opacity: var(--md-sys-state-hover-state-layer-opacity);
|
||
}
|
||
}
|
||
|
||
&:focus-visible::before,
|
||
&:has(:focus-visible)::before {
|
||
opacity: var(--md-sys-state-focus-state-layer-opacity);
|
||
}
|
||
|
||
&:active::before {
|
||
opacity: var(--md-sys-state-pressed-state-layer-opacity);
|
||
}
|
||
|
||
&:is(:focus-visible, :has(:focus-visible)) {
|
||
outline: 3px solid var(--md-sys-color-secondary);
|
||
outline-offset: 2px;
|
||
}
|
||
|
||
&:is(:disabled, [aria-disabled='true'], [data-md-disabled])::before {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
[data-md-chip]:is([data-md-chip='filter'], [data-md-icon-start]):not([data-md-chip='input']) {
|
||
padding-inline-start: 7px;
|
||
}
|
||
|
||
[data-md-chip][data-md-icon-end] {
|
||
padding-inline-end: 7px;
|
||
}
|
||
|
||
[data-md-chip='assist'] {
|
||
color: var(--md-sys-color-on-surface);
|
||
}
|
||
|
||
[data-md-chip='assist']:not([data-md-elevated], [data-md-disabled]):focus-visible {
|
||
border-color: var(--md-sys-color-on-surface);
|
||
}
|
||
|
||
:is([data-md-chip='suggestion'], [data-md-chip='filter']):not([data-md-elevated], [data-md-disabled], :has(:checked), [aria-pressed='true']):is(:focus-visible, :has(:focus-visible)) {
|
||
border-color: var(--md-sys-color-on-surface-variant);
|
||
}
|
||
|
||
[data-md-chip][data-md-elevated] {
|
||
border-color: transparent;
|
||
background-color: var(--md-sys-color-surface-container-low);
|
||
box-shadow: var(--md-sys-elevation-1);
|
||
}
|
||
|
||
@media (hover: hover) {
|
||
[data-md-chip][data-md-elevated]:not([data-md-disabled]):hover {
|
||
box-shadow: var(--md-sys-elevation-2);
|
||
}
|
||
}
|
||
|
||
/* Selected: a checked filter chip, or a pressed one. */
|
||
[data-md-chip='filter']:is(:has(:checked), [aria-pressed='true']),
|
||
[data-md-chip='input'][data-md-selected] {
|
||
border-color: transparent;
|
||
background-color: var(--md-sys-color-secondary-container);
|
||
color: var(--md-sys-color-on-secondary-container);
|
||
}
|
||
|
||
@media (hover: hover) {
|
||
[data-md-chip='filter']:not([data-md-elevated], [data-md-disabled]):is(:has(:checked), [aria-pressed='true']):hover {
|
||
box-shadow: var(--md-sys-elevation-1);
|
||
}
|
||
}
|
||
|
||
[data-md-chip][data-md-disabled] {
|
||
border-color: var(--chip-disabled-container);
|
||
color: var(--chip-disabled-ink);
|
||
box-shadow: none;
|
||
}
|
||
|
||
[data-md-chip][data-md-disabled]:is([data-md-elevated], [data-md-selected], :has(:checked), [aria-pressed='true']) {
|
||
border-color: transparent;
|
||
background-color: var(--chip-disabled-container);
|
||
}
|
||
|
||
[data-md-chip][data-md-disabled]:not(a) {
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
/* A disabled link leaves the pointer too. */
|
||
a[data-md-chip][aria-disabled='true'] {
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* ---- Contents ------------------------------------------------------------------------------ */
|
||
|
||
[data-md-chip-label] {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
:is([data-md-chip], [data-md-chip-action]) > [data-md-chip-icon] {
|
||
margin-inline-end: var(--md-sys-measurement-space100);
|
||
}
|
||
|
||
[data-md-chip-trailing] {
|
||
margin-inline-start: var(--md-sys-measurement-space100);
|
||
}
|
||
|
||
[data-md-chip]:not([data-md-chip='input'], [data-md-disabled]) [data-md-chip-icon],
|
||
[data-md-chip='input'][data-md-selected]:not([data-md-disabled]) [data-md-chip-icon],
|
||
:is([data-md-chip='assist'], [data-md-chip='suggestion']):not([data-md-disabled]) [data-md-chip-trailing] {
|
||
color: var(--md-sys-color-primary);
|
||
}
|
||
|
||
[data-md-chip][data-md-actionable] [data-md-chip-icon] {
|
||
transition: color var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
||
}
|
||
|
||
[data-md-chip][data-md-actionable]:is(:active, :has(:focus-visible)) [data-md-chip-icon] {
|
||
color: var(--md-sys-color-primary);
|
||
}
|
||
|
||
@media (hover: hover) {
|
||
[data-md-chip][data-md-actionable]:hover [data-md-chip-icon] {
|
||
color: var(--md-sys-color-primary);
|
||
}
|
||
}
|
||
|
||
/* The filter chip's check: from nothing to 18px as it is selected, or over the icon it replaces. */
|
||
[data-md-chip-check-slot] {
|
||
display: grid;
|
||
flex-shrink: 0;
|
||
width: 0;
|
||
height: 18px;
|
||
margin-inline-end: var(--md-sys-measurement-space100);
|
||
overflow: hidden;
|
||
transition: width var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-effects-default);
|
||
}
|
||
|
||
[data-md-chip-check-slot] > * {
|
||
grid-area: 1 / 1;
|
||
}
|
||
|
||
[data-md-chip-check-slot]:has([data-md-chip-icon]) {
|
||
width: 18px;
|
||
transition: none;
|
||
}
|
||
|
||
[data-md-chip]:is(:has(:checked), [aria-pressed='true']) [data-md-chip-check-slot]:not(:has([data-md-chip-icon])) {
|
||
width: 18px;
|
||
transition-duration: var(--md-sys-motion-spatial-fast-duration);
|
||
transition-timing-function: var(--md-sys-motion-spatial-fast);
|
||
}
|
||
|
||
[data-md-chip-check-slot] [data-md-chip-icon] {
|
||
transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
||
}
|
||
|
||
[data-md-chip-check] {
|
||
opacity: 0;
|
||
transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
||
}
|
||
|
||
[data-md-chip]:is(:has(:checked), [aria-pressed='true']) [data-md-chip-check-slot] [data-md-chip-icon] {
|
||
opacity: 0;
|
||
}
|
||
|
||
[data-md-chip]:is(:has(:checked), [aria-pressed='true']) [data-md-chip-check] {
|
||
opacity: 1;
|
||
transition-duration: var(--md-sys-motion-effects-slow-duration);
|
||
transition-timing-function: var(--md-sys-motion-effects-slow);
|
||
}
|
||
|
||
/* ---- Input chips: an action of their own, and a remove button ---------------------------- */
|
||
|
||
[data-md-chip='input'] {
|
||
isolation: isolate;
|
||
}
|
||
|
||
[data-md-chip='input']:not([data-md-selected], [data-md-disabled]):has([data-md-chip-action]:focus-visible) {
|
||
border-color: var(--md-sys-color-on-surface-variant);
|
||
}
|
||
|
||
[data-md-chip='input']:has([data-md-chip-action]:focus-visible) {
|
||
outline: 3px solid var(--md-sys-color-secondary);
|
||
outline-offset: 2px;
|
||
}
|
||
|
||
[data-md-chip-action] {
|
||
display: flex;
|
||
align-items: center;
|
||
min-width: 0;
|
||
height: 100%;
|
||
padding-inline: 11px;
|
||
outline: none;
|
||
}
|
||
|
||
[data-md-chip][data-md-icon-start] [data-md-chip-action] {
|
||
padding-inline-start: 7px;
|
||
}
|
||
|
||
[data-md-chip][data-md-avatar] [data-md-chip-action] {
|
||
padding-inline-start: 3px;
|
||
}
|
||
|
||
[data-md-chip][data-md-removable] [data-md-chip-action] {
|
||
padding-inline-end: var(--md-sys-measurement-space100);
|
||
}
|
||
|
||
:is(a, button)[data-md-chip-action] {
|
||
cursor: pointer;
|
||
|
||
/* The 48px target and the state layer, across the whole chip. */
|
||
&::after {
|
||
content: '';
|
||
position: absolute;
|
||
inset-inline: 0;
|
||
top: 50%;
|
||
height: var(--md-sys-measurement-space600);
|
||
translate: 0 -50%;
|
||
}
|
||
}
|
||
|
||
[data-md-chip]:not([data-md-disabled]) :is(a, button)[data-md-chip-action] {
|
||
&::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: -1px;
|
||
z-index: -1;
|
||
border-radius: var(--md-sys-shape-corner-sm);
|
||
background-color: currentColor;
|
||
opacity: 0;
|
||
transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
||
}
|
||
|
||
@media (hover: hover) {
|
||
&:hover::before {
|
||
opacity: var(--md-sys-state-hover-state-layer-opacity);
|
||
}
|
||
}
|
||
|
||
&:focus-visible::before {
|
||
opacity: var(--md-sys-state-focus-state-layer-opacity);
|
||
}
|
||
|
||
&:active::before {
|
||
opacity: var(--md-sys-state-pressed-state-layer-opacity);
|
||
}
|
||
}
|
||
|
||
[data-md-chip][data-md-disabled] :is(a, button)[data-md-chip-action] {
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
[data-md-chip][data-md-disabled] a[data-md-chip-action] {
|
||
pointer-events: none;
|
||
}
|
||
|
||
[data-md-chip-avatar] {
|
||
flex-shrink: 0;
|
||
width: 24px;
|
||
height: 24px;
|
||
margin-inline-end: var(--md-sys-measurement-space100);
|
||
border-radius: var(--md-sys-shape-corner-full);
|
||
}
|
||
|
||
span[data-md-chip-avatar] {
|
||
display: grid;
|
||
place-items: center;
|
||
background-color: var(--md-sys-color-primary-container);
|
||
color: var(--md-sys-color-on-primary-container);
|
||
font: var(--md-sys-typescale-label-sm);
|
||
letter-spacing: var(--md-sys-typescale-label-sm-tracking);
|
||
font-variation-settings: normal;
|
||
}
|
||
|
||
img[data-md-chip-avatar] {
|
||
object-fit: cover;
|
||
}
|
||
|
||
[data-md-chip][data-md-disabled] [data-md-chip-avatar] {
|
||
opacity: var(--md-sys-state-disabled-content-opacity);
|
||
}
|
||
|
||
/* An 18px icon in a 24px state layer, catching presses 15px out on every side: M3's 48px. */
|
||
[data-md-chip-remove] {
|
||
position: relative;
|
||
display: grid;
|
||
flex-shrink: 0;
|
||
place-items: center;
|
||
width: 18px;
|
||
height: 18px;
|
||
margin-inline-end: 7px;
|
||
border-radius: var(--md-sys-shape-corner-full);
|
||
outline: none;
|
||
cursor: pointer;
|
||
|
||
&::after {
|
||
content: '';
|
||
position: absolute;
|
||
inset: -15px;
|
||
}
|
||
|
||
&:disabled {
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
&:not(:disabled) {
|
||
&::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: -3px;
|
||
border-radius: var(--md-sys-shape-corner-full);
|
||
background-color: currentColor;
|
||
opacity: 0;
|
||
transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
||
}
|
||
|
||
@media (hover: hover) {
|
||
&:hover::before {
|
||
opacity: var(--md-sys-state-hover-state-layer-opacity);
|
||
}
|
||
}
|
||
|
||
&:focus-visible {
|
||
outline: 3px solid var(--md-sys-color-secondary);
|
||
outline-offset: 2px;
|
||
}
|
||
|
||
&:focus-visible::before {
|
||
opacity: var(--md-sys-state-focus-state-layer-opacity);
|
||
}
|
||
|
||
&:active::before {
|
||
opacity: var(--md-sys-state-pressed-state-layer-opacity);
|
||
}
|
||
}
|
||
}
|
||
}
|