|
|
|
|
/* ── Content (variant-shared base) ──────────────────────────────────────── */
|
|
|
|
|
|
|
|
|
|
[data-floating-wrapper]:has(> [data-tooltip-content]) {
|
|
|
|
|
z-index: var(--tooltip-content-z);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[data-tooltip-content] {
|
|
|
|
|
--_tooltip-py: var(--tooltip-padding-y-md);
|
|
|
|
|
--_tooltip-px: var(--tooltip-padding-x-md);
|
|
|
|
|
--_tooltip-font-size: var(--tooltip-font-size-md);
|
|
|
|
|
--_tooltip-bg: var(--tooltip-bg);
|
|
|
|
|
--_tooltip-border: var(--tooltip-border);
|
|
|
|
|
--_tooltip-shadow: var(--tooltip-shadow);
|
|
|
|
|
--_tooltip-width: var(--_tooltip-content-width-override, var(--_tooltip-match-anchor-width, auto));
|
|
|
|
|
--_tooltip-max-width: var(--_tooltip-content-max-width-override, var(--tooltip-max-width));
|
|
|
|
|
--_tooltip-max-height: var(
|
|
|
|
|
--_tooltip-content-max-height-override,
|
|
|
|
|
var(--floating-available-height, calc(100dvh - var(--space-4)))
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
display: block;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
inline-size: var(--_tooltip-width);
|
|
|
|
|
min-inline-size: var(--_tooltip-content-min-width-override, auto);
|
|
|
|
|
max-inline-size: min(var(--_tooltip-max-width), calc(100vw - var(--space-4)));
|
|
|
|
|
block-size: var(--_tooltip-content-height-override, auto);
|
|
|
|
|
min-block-size: var(--_tooltip-content-min-height-override, auto);
|
|
|
|
|
max-block-size: var(--_tooltip-max-height);
|
|
|
|
|
padding: var(--_tooltip-py) var(--_tooltip-px);
|
|
|
|
|
overflow: auto;
|
|
|
|
|
overscroll-behavior: contain;
|
|
|
|
|
border: var(--tooltip-border-width) solid var(--_tooltip-border);
|
|
|
|
|
border-radius: var(--tooltip-radius);
|
|
|
|
|
background: var(--_tooltip-bg);
|
|
|
|
|
color: var(--tooltip-color);
|
|
|
|
|
box-shadow: var(--_tooltip-shadow);
|
|
|
|
|
transform-origin: var(--floating-transform-origin, center);
|
|
|
|
|
font-family: var(--tooltip-font-family);
|
|
|
|
|
font-size: var(--_tooltip-font-size);
|
|
|
|
|
font-weight: var(--tooltip-font-weight);
|
|
|
|
|
line-height: var(--tooltip-line-height);
|
|
|
|
|
text-wrap: pretty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ── Sizes ──────────────────────────────────────────────────────────────── */
|
|
|
|
|
|
|
|
|
|
[data-tooltip-content][data-size='sm'] {
|
|
|
|
|
--_tooltip-py: var(--tooltip-padding-y-sm);
|
|
|
|
|
--_tooltip-px: var(--tooltip-padding-x-sm);
|
|
|
|
|
--_tooltip-font-size: var(--tooltip-font-size-sm);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[data-tooltip-content][data-size='lg'] {
|
|
|
|
|
--_tooltip-py: var(--tooltip-padding-y-lg);
|
|
|
|
|
--_tooltip-px: var(--tooltip-padding-x-lg);
|
|
|
|
|
--_tooltip-font-size: var(--tooltip-font-size-lg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ── Variants ───────────────────────────────────────────────────────────── */
|
|
|
|
|
|
|
|
|
|
/* `solid` is the default surface — the base block already paints it. */
|
|
|
|
|
|
|
|
|
|
[data-tooltip-content][data-variant='outline'] {
|
|
|
|
|
--_tooltip-bg: transparent;
|
|
|
|
|
--_tooltip-border: var(--color-border-strong);
|
|
|
|
|
--_tooltip-shadow: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[data-tooltip-content][data-variant='ghost'] {
|
|
|
|
|
--_tooltip-bg: color-mix(in srgb, var(--color-surface-overlay) 70%, transparent);
|
|
|
|
|
--_tooltip-border: transparent;
|
|
|
|
|
--_tooltip-shadow: none;
|
feat(eidos): canonize theming scales + tokenize magic numbers
Audit-driven canonization of the eidos theming layer: a canonical scale that
components bypass with literals drifts into N variants. Every axis is now
retunable-by-theme AND consumed via token, never a bare literal.
Bloque A/B (scales): --blur-* (Tailwind-aligned, depth planes consume it);
inner-shadow --shadow-inset-* (mode-aware) for the recessed plane; gradient
angle/named tokens; --breakpoint-* sourced from ActiveDom (runtime, dev-settable)
+ container queries; opacity dual numeric+semantic scale; border-width
none/thin/medium/thick/heavy (adds real 3px); tracking caps/widest. Fase 7:
3 size archetypes documented + coherence guard (no px/rem in recipe
font-size/icon-size).
Bloque C (magic numbers): z-index 80/50/99 (combobox/nav-menu/drag-drop) ->
recipe content-z/preview-z tokens (overlay micro-band soma mirrors); on-scale
durations -> var(--duration-*) (dialog fast/slow, card slow); 12 single
border/ring widths -> var(--border-width-*), value-preserving. Off-scale kept
only as justified recipe tokens (continuous spinner/loading periods, sub-fast
press). Self-contained dimensional scales (avatar ring, ring-thickness) left whole.
Docs: THEMING.md SS35 + SS6 + SS29, THEMING_NOTES, README, TSC. Excludes
words/palabras/chronos (active dev tracks).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
|
|
|
backdrop-filter: blur(var(--blur-md));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ── Arrow ──────────────────────────────────────────────────────────────── */
|
|
|
|
|
|
|
|
|
|
/* Colour the SVG shapes directly. The polygon/path carry `fill="currentColor"` /
|
|
|
|
|
* `stroke="currentColor"` presentation attributes, which a `fill`/`stroke` set on
|
|
|
|
|
* the wrapping <span> does NOT override — an element's own presentation attribute
|
|
|
|
|
* beats an inherited value, so the caret rendered with `color` (dark text) instead
|
|
|
|
|
* of the panel bg. Target the polygon (body) + path (the two exposed edges) like
|
|
|
|
|
* the popover recipe. `display: block` keeps the SVG off the text baseline (which
|
|
|
|
|
* added phantom height + misaligned the caret). */
|
|
|
|
|
[data-tooltip-arrow] svg {
|
|
|
|
|
display: block;
|
|
|
|
|
overflow: visible;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[data-tooltip-arrow] polygon {
|
|
|
|
|
fill: var(--_tooltip-bg, var(--tooltip-bg));
|
|
|
|
|
stroke: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[data-tooltip-arrow] path {
|
|
|
|
|
stroke: var(--_tooltip-border, var(--tooltip-border));
|
|
|
|
|
stroke-width: var(--tooltip-border-width);
|
|
|
|
|
vector-effect: non-scaling-stroke;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Outline variant: transparent bg → fill the arrow with the surface (masks the
|
|
|
|
|
* panel-border seam) and stroke the two exposed edges. */
|
|
|
|
|
[data-tooltip-content][data-variant='outline'] ~ [data-tooltip-arrow] polygon,
|
|
|
|
|
[data-tooltip-content][data-variant='outline'] [data-tooltip-arrow] polygon {
|
|
|
|
|
fill: var(--color-surface-default);
|
|
|
|
|
}
|
|
|
|
|
[data-tooltip-content][data-variant='outline'] ~ [data-tooltip-arrow] path,
|
|
|
|
|
[data-tooltip-content][data-variant='outline'] [data-tooltip-arrow] path {
|
|
|
|
|
stroke: var(--color-border-strong);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ── Entrance / exit ─────────────────────────────────────────────────────────
|
feat(motion): tooltip entrance is now a bounce (rebote)
Replaces the tooltip's per-side 2px slide-in with a scale BOUNCE: grow from 0.7,
overshoot to 1.06, dip to 0.97 (the undershoot — the "rebote"), settle to 1, over
`--duration-moderate`. It scales from `transform-origin` (the floating origin,
toward the anchor), so it keeps a directional feel without per-side keyframes —
8 keyframes collapse to 2.
Bespoke recipe keyframe, NOT a registered `motion` preset: the tooltip's state is
3-valued (`delayed-open` / `instant-open` / `closed`), not the generic `open` /
`closed` the shared presets key on — so a registered preset wouldn't fire here.
This also confirms the architecture boundary: components whose state shape doesn't
match open/closed use a bespoke recipe animation, not the `motion` selector.
`instant-open` (keyboard focus) keeps no entrance; the reduced-motion block (kills
the animation) already covers the new rules — verified the bounce curve in-browser
(scale 0.7 → 1.06 → 0.97 → 1). Recipe-only change, no generator/base.css touched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
|
|
|
* Tooltip morfo's data-state values: 'closed' | 'delayed-open' | 'instant-open'.
|
|
|
|
|
* 'delayed-open' (hover) scales in subtly; 'instant-open' (keyboard focus)
|
|
|
|
|
* appears with no entrance; 'closed' scales out. The scale grows from
|
|
|
|
|
* `transform-origin` (the floating origin, toward the anchor — set above), so it
|
|
|
|
|
* keeps a directional feel without per-side keyframes. The tooltip's 3-value
|
|
|
|
|
* state (delayed-open, not the generic open/closed) is why this is a bespoke
|
|
|
|
|
* recipe keyframe, not a registered `motion` preset. */
|
|
|
|
|
|
feat(motion): tooltip entrance is now a bounce (rebote)
Replaces the tooltip's per-side 2px slide-in with a scale BOUNCE: grow from 0.7,
overshoot to 1.06, dip to 0.97 (the undershoot — the "rebote"), settle to 1, over
`--duration-moderate`. It scales from `transform-origin` (the floating origin,
toward the anchor), so it keeps a directional feel without per-side keyframes —
8 keyframes collapse to 2.
Bespoke recipe keyframe, NOT a registered `motion` preset: the tooltip's state is
3-valued (`delayed-open` / `instant-open` / `closed`), not the generic `open` /
`closed` the shared presets key on — so a registered preset wouldn't fire here.
This also confirms the architecture boundary: components whose state shape doesn't
match open/closed use a bespoke recipe animation, not the `motion` selector.
`instant-open` (keyboard focus) keeps no entrance; the reduced-motion block (kills
the animation) already covers the new rules — verified the bounce curve in-browser
(scale 0.7 → 1.06 → 0.97 → 1). Recipe-only change, no generator/base.css touched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
|
|
|
[data-tooltip-content][data-state='delayed-open'] {
|
|
|
|
|
animation: eidos-tooltip-enter var(--duration-normal) var(--ease-out) both;
|
|
|
|
|
}
|
feat(motion): tooltip entrance is now a bounce (rebote)
Replaces the tooltip's per-side 2px slide-in with a scale BOUNCE: grow from 0.7,
overshoot to 1.06, dip to 0.97 (the undershoot — the "rebote"), settle to 1, over
`--duration-moderate`. It scales from `transform-origin` (the floating origin,
toward the anchor), so it keeps a directional feel without per-side keyframes —
8 keyframes collapse to 2.
Bespoke recipe keyframe, NOT a registered `motion` preset: the tooltip's state is
3-valued (`delayed-open` / `instant-open` / `closed`), not the generic `open` /
`closed` the shared presets key on — so a registered preset wouldn't fire here.
This also confirms the architecture boundary: components whose state shape doesn't
match open/closed use a bespoke recipe animation, not the `motion` selector.
`instant-open` (keyboard focus) keeps no entrance; the reduced-motion block (kills
the animation) already covers the new rules — verified the bounce curve in-browser
(scale 0.7 → 1.06 → 0.97 → 1). Recipe-only change, no generator/base.css touched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
|
|
|
[data-tooltip-content][data-state='closed'] {
|
|
|
|
|
animation: eidos-tooltip-exit var(--duration-fast) var(--ease-in) both;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* instant-open: no entrance — element appears at final position. */
|
|
|
|
|
[data-tooltip-content][data-state='instant-open'] {
|
|
|
|
|
opacity: 1;
|
|
|
|
|
transform: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Subtle entrance — a gentle scale + fade from the floating origin, no
|
|
|
|
|
* overshoot/bounce. `scale` (the individual property, not the `transform`
|
|
|
|
|
* shorthand) composes with `transform-origin` and never clobbers a sibling
|
|
|
|
|
* transform. */
|
|
|
|
|
@keyframes eidos-tooltip-enter {
|
|
|
|
|
from {
|
|
|
|
|
opacity: 0;
|
|
|
|
|
scale: 0.96;
|
|
|
|
|
}
|
|
|
|
|
to {
|
|
|
|
|
opacity: 1;
|
|
|
|
|
scale: 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@keyframes eidos-tooltip-exit {
|
|
|
|
|
to {
|
|
|
|
|
opacity: 0;
|
|
|
|
|
scale: 0.97;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ── Trigger states ─────────────────────────────────────────────────────────
|
|
|
|
|
* Trigger is the wrapper around any focusable element (button, link, …).
|
|
|
|
|
* The audit (R-1.2 / R-1.5) requires the recipe to acknowledge `data-disabled`
|
|
|
|
|
* and `:focus-visible`. The wrapped element keeps its own styling; the rules
|
|
|
|
|
* here suppress the tooltip behavior cleanly when the trigger is disabled. */
|
|
|
|
|
|
|
|
|
|
[data-tooltip-trigger][data-disabled] {
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[data-tooltip-trigger]:focus-visible {
|
|
|
|
|
/* Defer the focus ring to the wrapped element — but ensure the outline
|
|
|
|
|
is at least visible if the trigger is rendered as a focusable shell. */
|
|
|
|
|
outline: var(--focus-ring-width) solid var(--focus-ring-color);
|
|
|
|
|
outline-offset: var(--focus-ring-offset);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ── Reduced motion ─────────────────────────────────────────────────────────
|
|
|
|
|
* Honour the user's OS-level preference: tooltips appear instantly with
|
|
|
|
|
* no slide-in / slide-out, just a binary visibility toggle. The exit
|
|
|
|
|
* animation is also short-circuited so the element is removed without a
|
|
|
|
|
* trailing fade. */
|
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
|
|
|
[data-tooltip-content][data-state='delayed-open'],
|
|
|
|
|
[data-tooltip-content][data-state='instant-open'],
|
|
|
|
|
[data-tooltip-content][data-state='closed'] {
|
|
|
|
|
animation: none;
|
|
|
|
|
}
|
|
|
|
|
}
|