mirror of
https://github.com/we-promise/sure.git
synced 2026-07-21 09:15:23 +00:00
* fix(charts): restyle hover tooltips with soft shadow + larger radius Match the elevation pattern of DS::Select dropdown (shadow-lg + shadow-border-xs) and increase radius (rounded-lg → rounded-xl) + padding (p-2 → p-3) for better breathing. Drops the hard border in favour of the soft drop shadow. time_series_chart_controller: - p-2 → p-3, rounded-lg → rounded-xl - remove border border-secondary - add shadow-lg shadow-border-xs (theme-aware drop + border-edge) - add explicit text-primary (was missing per #2011's drift note) - add z-50 (matches goal_projection + sankey controllers) sankey_chart_controller: - bg-gray-700 text-white → bg-container text-primary (theme-aware; was broken in light mode after dark-bg flip) - p-2 rounded → p-3 rounded-xl - add shadow-lg shadow-border-xs - add font-sans for consistency with the other chart tooltips - add privacy-sensitive class (was missing — sankey money values were rendered in the clear with privacy mode on) DS::Tooltip (icon-trigger help, bg-inverse) is intentionally a different primitive and is not touched. Refs #2011 — className consolidation into a shared module is tracked separately and intentionally not closed by this PR; the new string applies to two of the three call sites today (time_series, sankey). The third (goal_projection_chart_controller) lives on the feat/goals-v2-architecture branch and will adopt the same string when goals v2 merges. * fix(charts): align every chart tooltip on the borderless soft-shadow card One visual contract for all three D3 tooltip surfaces, matching the design reference: p-4, rounded-2xl, shadow-xl, no edge ring in light mode. Dark mode keeps a 1px alpha-white ring since a shadow alone disappears against dark surfaces. - goal_projection_chart_controller drops its hand-copied class string (it still carried the old bordered recipe — the drift this util exists to prevent) and builds its two lines through the shared factory: secondary date line, tabular value line. - New content conventions exported alongside the container contract: context line = text-xs text-secondary, values = font-medium tabular-nums. Time-series and sankey adopt them. - Sankey node titles now escape before .html(); user-named categories were previously interpolated raw into the tooltip markup. * fix(charts): match the tooltip surface to the design reference exactly The previous pass approximated the reference with utility guesses (rounded-2xl, p-4, shadow-xl, dark ring). The actual spec is a hairline border ring composed with a soft 0 8px 24px drop shadow, 10px radius, 12x14 padding, and an 80ms left/top glide. Tailwind shadow utilities can't compose a ring with a custom drop shadow, so the surface moves into the design system as .chart-tooltip (theme-aware: dark swaps the ring to alpha-white and lets it carry the edge). Money/numeric figures also pick up the reference's mono treatment: font-mono + tabular-nums on every value across time-series, sankey, and goal-projection, so digits don't jitter while the scrubber moves. * fix(charts): tighten tooltip padding to 10x12 * feat(charts): give sankey and goal tooltips their missing context rows Chart tooltips answer three stacked questions: context (what am I looking at), value (how much), relation (vs what). Time-series already had all three; the other two were missing rows. - Sankey links showed a bare "$X (Y%)" with no indication of which flow was hovered. Links now lead with a swatch-dotted "Source → Target" context line; nodes get the same dot + name treatment, tying the card to the ribbon color. The context builder escapes node names centrally (they're user-named categories). - Goal projection adds a tertiary relation line — "52% of $20K target" — computed from the payload the chart already carries, for both the saved and projected segments. Hidden when the goal has no positive target. Template is i18n-wired like the existing tooltip strings (goals.show.projection.tooltip_target_relation). Verified with Playwright against the running app: all three surfaces pass computed-style and content assertions. * fix(charts): drop the color dot from sankey tooltip context The hover highlight on the diagram already identifies the ribbon; the swatch repeated it inside the card. Names alone keep the context line quieter. * fix(charts): use the app's sans money treatment in tooltips, not mono font-mono in this codebase marks code, keys, and admin surfaces; money is sans + tabular-nums everywhere else (cards, KPIs, tables). Keep the tabular figures for scrub stability, drop the mono. * fix(charts): address review — glide opt-in, no shadowed var, truncate cap - The 80ms left/top transition moved out of .chart-tooltip: it eased the snap-positioned goal tooltip but made cursor-following tooltips (sankey, time-series) trail the pointer by a frame. Goal projection opts back in via inline style; the component comment documents the split. - setRelation reuses _draw()'s targetAmount const instead of declaring a local 'target' that shadowed the target-date const. - Sankey context line gets max-w-64 so truncate has a constraint to fire against on deep flows. - Component comment now says 10x12 padding, matching the declaration. * Revert `schema.rb` changes --------- Co-authored-by: Juan José Mata <jjmata@jjmata.com>
251 lines
8.6 KiB
CSS
251 lines
8.6 KiB
CSS
@layer components {
|
|
/* Forms */
|
|
.form-field {
|
|
@apply flex flex-col gap-1 relative px-3 py-2 rounded-md border bg-container border-secondary shadow-xs w-full;
|
|
@apply focus-within:border-secondary focus-within:shadow-none focus-within:ring-4 focus-within:ring-alpha-black-200;
|
|
@apply transition-all duration-300;
|
|
|
|
@variant theme-dark {
|
|
@apply focus-within:ring-alpha-white-300;
|
|
}
|
|
|
|
/* Add styles for multiple select within form fields */
|
|
select[multiple] {
|
|
@apply py-2 pr-2 space-y-0.5 overflow-y-auto;
|
|
|
|
option {
|
|
@apply py-2 rounded-md;
|
|
}
|
|
|
|
option:checked {
|
|
@apply after:content-['\2713'] bg-container-inset after:text-gray-500 after:ml-2;
|
|
}
|
|
|
|
option:active,
|
|
option:focus {
|
|
@apply bg-container-inset;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* New form field structure components */
|
|
.form-field__header {
|
|
@apply flex items-center justify-between gap-2;
|
|
}
|
|
|
|
.form-field__body {
|
|
@apply flex flex-col gap-1;
|
|
}
|
|
|
|
.form-field__actions {
|
|
@apply flex items-center gap-1;
|
|
}
|
|
|
|
.form-field__label {
|
|
@apply block text-xs text-secondary peer-disabled:text-subdued;
|
|
}
|
|
|
|
.form-field__input {
|
|
@apply text-primary border-none bg-container text-sm opacity-100 w-full p-0;
|
|
@apply focus:opacity-100 focus:outline-hidden focus:ring-0;
|
|
@apply placeholder-shown:opacity-50;
|
|
@apply disabled:text-subdued;
|
|
@apply text-ellipsis overflow-hidden whitespace-nowrap;
|
|
@apply transition-opacity duration-300;
|
|
@apply placeholder:text-subdued;
|
|
|
|
@variant theme-dark {
|
|
&::-webkit-calendar-picker-indicator {
|
|
filter: invert(1);
|
|
cursor: pointer;
|
|
}
|
|
}
|
|
}
|
|
|
|
textarea.form-field__input {
|
|
@apply whitespace-normal overflow-auto;
|
|
text-overflow: clip;
|
|
}
|
|
|
|
.form-field__input--multiselect-trigger {
|
|
@apply whitespace-normal overflow-visible;
|
|
text-overflow: clip;
|
|
}
|
|
|
|
select.form-field__input,
|
|
button.form-field__input {
|
|
@apply pr-10 appearance-none;
|
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
|
background-position: right -0.15rem center;
|
|
background-repeat: no-repeat;
|
|
background-size: 1.25rem 1.25rem;
|
|
text-align: left;
|
|
}
|
|
|
|
.form-field__radio {
|
|
@apply text-primary;
|
|
}
|
|
|
|
.form-field__submit {
|
|
@apply cursor-pointer rounded-lg bg-surface p-3 text-center text-white hover:bg-surface-hover;
|
|
}
|
|
|
|
/* Checkboxes */
|
|
.checkbox {
|
|
&[type='checkbox'] {
|
|
@apply rounded-sm;
|
|
@apply transition-colors duration-300;
|
|
}
|
|
}
|
|
|
|
.checkbox--light {
|
|
&[type='checkbox'] {
|
|
@apply border-alpha-black-200 checked:bg-gray-900 checked:ring-gray-900 focus:ring-gray-900 focus-visible:ring-gray-900 checked:hover:bg-gray-300 hover:bg-gray-300;
|
|
}
|
|
|
|
&[type='checkbox']:disabled {
|
|
@apply cursor-not-allowed opacity-80 bg-gray-50 border-gray-200 checked:bg-gray-400 checked:ring-gray-400;
|
|
}
|
|
|
|
@variant theme-dark {
|
|
&[type='checkbox'] {
|
|
@apply ring-gray-900 border-alpha-white-300;
|
|
background-color: transparent;
|
|
}
|
|
|
|
&[type='checkbox']:disabled {
|
|
@apply cursor-not-allowed opacity-80 border-transparent;
|
|
background-color: var(--color-gray-700);
|
|
}
|
|
|
|
&[type='checkbox']:checked,
|
|
&[type='checkbox']:indeterminate {
|
|
@apply border-transparent;
|
|
background-color: var(--color-gray-100);
|
|
}
|
|
|
|
&[type='checkbox']:checked {
|
|
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='%23171717' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
|
|
}
|
|
|
|
&[type='checkbox']:indeterminate {
|
|
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='%23171717' xmlns='http://www.w3.org/2000/svg'%3e%3crect x='3.5' y='7' width='9' height='2' rx='1'/%3e%3c/svg%3e");
|
|
}
|
|
}
|
|
}
|
|
|
|
.checkbox--dark {
|
|
&[type='checkbox'] {
|
|
@apply ring-gray-900 checked:text-white;
|
|
}
|
|
|
|
&[type='checkbox']:disabled {
|
|
@apply cursor-not-allowed opacity-80 ring-gray-600;
|
|
}
|
|
|
|
&[type='checkbox']:checked {
|
|
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='%23111827' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
|
|
}
|
|
}
|
|
|
|
/* Tooltips */
|
|
.tooltip {
|
|
@apply hidden absolute;
|
|
}
|
|
|
|
.qrcode svg path {
|
|
fill: var(--color-black);
|
|
@variant theme-dark {
|
|
fill: var(--color-white);
|
|
}
|
|
}
|
|
|
|
/*
|
|
Segmented control (#2137) — track + equal-footprint segments. The selected
|
|
state is a single `--active` class so an optional Stimulus controller can
|
|
toggle it as one unit. Values mirror the DS tab tokens (tab-bg-group /
|
|
tab-item-active / tab-item-hover) but are inlined with `@variant theme-dark`
|
|
because `@apply`-ing a custom utility drops its dark override. The near-black
|
|
dark track (alpha-black-700) is what makes the gray-700 selected pill read —
|
|
the bespoke controls used a too-light `container-inset` track, hence the
|
|
audit's "selected pill invisible on dark." Focus = neutral outline (matches
|
|
the canonical focus ring; inlined until that token lands on main).
|
|
*/
|
|
.segmented-control {
|
|
@apply inline-flex items-center gap-0.5 p-1 rounded-lg bg-gray-50;
|
|
@variant theme-dark {
|
|
@apply bg-alpha-black-700;
|
|
}
|
|
}
|
|
|
|
.segmented-control__segment {
|
|
@apply inline-flex items-center justify-center px-2 py-1 rounded-md whitespace-nowrap;
|
|
@apply text-sm font-medium text-secondary cursor-pointer transition-colors duration-200;
|
|
@apply hover:bg-gray-200;
|
|
@apply focus-visible:outline-2 focus-visible:outline-offset-2;
|
|
@apply focus-visible:outline-alpha-black-400 theme-dark:focus-visible:outline-alpha-white-400;
|
|
@variant theme-dark {
|
|
@apply hover:bg-gray-800;
|
|
}
|
|
}
|
|
|
|
.segmented-control__segment--active {
|
|
@apply bg-white text-primary shadow-sm hover:bg-white;
|
|
@variant theme-dark {
|
|
@apply bg-gray-700 hover:bg-gray-700;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Horizontally scrollable table wrapper (#2137). `overflow-x: auto` so wide
|
|
tables scroll instead of clipping (the LLM-usage table was `overflow-hidden`
|
|
and pushed columns off-screen) or wrapping money mid-digit. The pure-CSS
|
|
"scroll shadow" gives the missing affordance: the cover gradients
|
|
(`--table-scroll-bg`, default container-inset) scroll WITH the content
|
|
(`background-attachment: local`) and hide the fixed shadow gradients at the
|
|
edges, so a soft edge-shadow only appears when there is more to scroll.
|
|
Theme-aware via `--color-shadow`. Set `--table-scroll-bg` to match the wrapper.
|
|
*/
|
|
.table-scroll {
|
|
--table-scroll-bg: var(--color-container-inset);
|
|
overflow-x: auto;
|
|
background:
|
|
linear-gradient(to right, var(--table-scroll-bg) 30%, transparent),
|
|
linear-gradient(to left, var(--table-scroll-bg) 30%, transparent) 100% 0,
|
|
radial-gradient(farthest-side at 0 50%, var(--color-shadow), transparent),
|
|
radial-gradient(farthest-side at 100% 50%, var(--color-shadow), transparent) 100% 0;
|
|
background-repeat: no-repeat;
|
|
background-color: var(--table-scroll-bg);
|
|
background-size: 32px 100%, 32px 100%, 12px 100%, 12px 100%;
|
|
background-attachment: local, local, scroll, scroll;
|
|
}
|
|
|
|
/*
|
|
Chart hover tooltip surface (see utils/chart_tooltip.js for the JS-side
|
|
contract). Matches the design reference exactly: hairline border ring
|
|
composed with a soft 8/24 drop shadow (Tailwind shadow utilities don't
|
|
compose, hence the component class), 10px radius, 10x12 padding. Dark
|
|
mode swaps the ring to alpha-white; the drop shadow is near-invisible
|
|
there, which is fine — the ring carries the edge.
|
|
|
|
No position transition here on purpose: cursor-following tooltips
|
|
(sankey, time-series) update left/top every mousemove and a transition
|
|
makes them trail the pointer. Snap-positioned tooltips (goal projection,
|
|
which jumps between dates) opt into the 80ms glide via inline style.
|
|
*/
|
|
.chart-tooltip {
|
|
background: var(--color-container);
|
|
border-radius: 10px;
|
|
padding: 10px 12px;
|
|
box-shadow:
|
|
0 0 0 1px var(--color-alpha-black-50),
|
|
0 8px 24px rgba(11, 11, 11, 0.12);
|
|
|
|
@variant theme-dark {
|
|
box-shadow:
|
|
0 0 0 1px var(--color-alpha-white-50),
|
|
0 8px 24px rgba(11, 11, 11, 0.12);
|
|
}
|
|
}
|
|
}
|