142 lines
5.3 KiB
CSS
142 lines
5.3 KiB
CSS
/* ============================================================================
|
|
INPUT / TEXTAREA - figma frame 18:212 (Type x State)
|
|
----------------------------------------------------------------------------
|
|
Only TIER-2 semantic tokens. The label, hint and error colours are driven off
|
|
two data attributes on the group (`data-invalid`, `data-disabled`) plus
|
|
`:focus-within`, so the whole 2x5 matrix is a handful of rules.
|
|
|
|
Layout (the column and its gaps) is plain Tailwind spacing utilities in
|
|
Input.tsx, and they name a step of the spacing scale (`gap-xs` = 8, `ms-2xs`
|
|
= 4) rather than the raw `gap-1.5` / `ms-0.5` multiples of --spacing, which
|
|
are tier-1. Everything token-driven lives here.
|
|
|
|
BOX. As with the button, Figma strokes inside the frame, so the 1.5px border
|
|
is subtracted from the 16/12 padding and the outer edge stays where it was
|
|
drawn.
|
|
|
|
DEVIATIONS FROM FIGMA, deliberate:
|
|
* Height. Figma hand-sets 44px, which does not agree with its own 12px
|
|
padding and 24px Body/Medium line-height. Following the tokens gives 48px,
|
|
which is also exactly the Medium button, so a field and a button sitting on
|
|
one row line up. Textarea keeps Figma's 120px, reached honestly as
|
|
rows={4}: 4*24 + 10.5*2 + 1.5*2.
|
|
* Focus. Figma paints a 2px grey (`stroke/hover`) border, which both moves
|
|
the box by half a pixel and is not a WCAG-visible focus indicator. The
|
|
border instead turns `--field-border-focus` at an unchanged 1.5px and the
|
|
semantic focus ring is drawn outside it. The green label on focus is kept.
|
|
* Disabled. Figma dims the whole group to 50%. The `--field-*-disabled`
|
|
tokens already encode that reading and stay opaque, so no group opacity is
|
|
applied and text on a translucent dark surface cannot smear.
|
|
* The error caption is Figma's "Caption" role (Regular 12), which the type
|
|
scale does not publish. It uses `body-sm` (14/20), the smallest published
|
|
body role - more legible for a message a user has to act on.
|
|
========================================================================= */
|
|
|
|
@layer theme, base, components, utilities;
|
|
|
|
@layer components {
|
|
/* ---------------------------------------------------------------------
|
|
Label, hint, error
|
|
------------------------------------------------------------------ */
|
|
.sh-field__label {
|
|
color: var(--text-primary);
|
|
transition: color var(--motion-duration-fast) var(--motion-ease-standard);
|
|
}
|
|
|
|
.sh-field:focus-within .sh-field__label {
|
|
color: var(--text-accent);
|
|
}
|
|
|
|
.sh-field[data-invalid="true"] .sh-field__label,
|
|
.sh-field[data-invalid="true"]:focus-within .sh-field__label {
|
|
color: var(--text-danger);
|
|
}
|
|
|
|
.sh-field[data-disabled="true"] .sh-field__label {
|
|
color: var(--text-disabled);
|
|
}
|
|
|
|
.sh-field__required {
|
|
color: var(--text-danger);
|
|
}
|
|
|
|
.sh-field__hint {
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.sh-field[data-disabled="true"] .sh-field__hint {
|
|
color: var(--text-disabled);
|
|
}
|
|
|
|
.sh-field__error {
|
|
color: var(--text-danger);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------
|
|
The control itself
|
|
------------------------------------------------------------------ */
|
|
.sh-field__control {
|
|
display: block;
|
|
inline-size: 100%;
|
|
appearance: none;
|
|
background-color: var(--field-bg);
|
|
color: var(--field-fg);
|
|
border: var(--control-border-width) solid var(--field-border);
|
|
border-radius: var(--radius-control-lg);
|
|
padding-inline: calc(var(--control-padding-inline-md) - var(--control-border-width));
|
|
padding-block: calc(var(--control-padding-block-md) - var(--control-border-width));
|
|
text-align: start;
|
|
transition:
|
|
background-color var(--motion-duration-fast) var(--motion-ease-standard),
|
|
border-color var(--motion-duration-fast) var(--motion-ease-standard),
|
|
color var(--motion-duration-fast) var(--motion-ease-standard);
|
|
}
|
|
|
|
.sh-field__control::placeholder {
|
|
color: var(--field-placeholder);
|
|
opacity: 1;
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
.sh-field__control:hover:not(:disabled) {
|
|
border-color: var(--field-border-hover);
|
|
}
|
|
}
|
|
|
|
/* Matched to the hover rule's specificity and placed after it, so focusing
|
|
a field the pointer happens to be over still shows the focus border. */
|
|
.sh-field .sh-field__control:focus-visible {
|
|
border-color: var(--field-border-focus);
|
|
outline: var(--focus-ring-width) solid var(--focus-ring-color);
|
|
outline-offset: var(--focus-ring-offset);
|
|
}
|
|
|
|
.sh-field[data-invalid="true"] .sh-field__control {
|
|
border-color: var(--field-border-error);
|
|
}
|
|
|
|
.sh-field[data-invalid="true"] .sh-field__control:focus-visible {
|
|
border-color: var(--field-border-error);
|
|
outline-color: var(--field-border-error);
|
|
}
|
|
|
|
/* Last, and specific enough to win over the invalid border - a disabled
|
|
field reads as disabled even when it is still holding an error. */
|
|
.sh-field .sh-field__control:disabled {
|
|
background-color: var(--field-bg-disabled);
|
|
color: var(--field-fg-disabled);
|
|
border-color: var(--border-subtle);
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.sh-field .sh-field__control:disabled::placeholder {
|
|
color: var(--field-fg-disabled);
|
|
}
|
|
|
|
/* Only the block axis resizes - growing a textarea sideways would break the
|
|
column it sits in. */
|
|
.sh-field__control--textarea {
|
|
resize: vertical;
|
|
}
|
|
}
|