fix(color-picker): fix slider gradients + add live preview + label eyedropper

User report: 'los sliders son deficitarios, no muestra la banda de color
ni de transferencia, faltan textos, el color seleccionado aparte del
valor ascii, un texto que indique el gotero, etc.'

Three concrete issues addressed.

1. Slider gradients invisible
-----------------------------
Root cause: the soma channel-slider div carries BOTH `data-slider`
(from SliderProvider.props) and `data-color-picker-channel-slider`
(from mergeProps). My CSS targeted `[data-color-picker-channel-slider]
[data-slider]::before` — i.e. as a *descendant*. They're the same
element, so the selector never matched and the rainbow / alpha
gradient stayed hidden.

Fix:
- New selector `[data-color-picker-channel-slider][data-slider]::before`
  paints the gradient on the slider's own track strip.
- Transparency checker moved to the wrapper's `::after` (with
  `z-index: -1` + `isolation: isolate`) so it sits behind the gradient
  — visible only where the alpha gradient ramps to transparent.
- Adds a subtle border on the track so the strip is visible against
  the popover surface.

2. Live preview alongside the hex/rgb/hsl string
------------------------------------------------
The popover used to show only the segmented ChannelInput (digits) and
a small swatch dot on the trigger. Now the popover has a `.cp-preview-
row` with `<ColorPicker.ValueSwatch>` (2rem) + `<ColorPicker.ValueText>`
+ uppercase format tag, so the user always sees the actual color
visually alongside its numeric representation.

3. Eyedropper label
-------------------
Added a textual `Pick from screen` label next to the icon button,
plus `aria-label` on the EyeDropper. The button is no longer a
mystery glyph.

4. Channel slider row labels
----------------------------
Each ChannelSlider now sits in a `.cp-slider-row` with a leading
uppercase label (Hue / Alpha) so the user knows what each track drives
without hovering or reading aria.

5. Section header for swatches
------------------------------
'Preset colors' header above the swatch grid for visual hierarchy
inside the popover.

Files
-----
- src/uix/eidos/components/color-picker/color-picker.css — slider
  gradient routing + checker-on-::after layering.
- web/routes/uix/components/color-picker/+page.svelte — preview row,
  slider row labels, eyedropper label, section headers + local
  scoped CSS in <style>.

Checks: 67/67 component-audit PASS, 40/40 morfo:check PASS,
svelte-check 0 errors / 0 warnings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
dev 5 months ago
parent 714ee3735c
commit 709d8a108e

@ -297,22 +297,39 @@
* so the slider's track shows the gradient, with a transparency checker
* behind it (via the wrapper's own ::before, z-index -1) for alpha. */
[data-color-picker-channel-slider] {
/* Note on stacking: in the soma, the same `<div>` carries both
`[data-slider]` (from SliderProvider.props) and
`[data-color-picker-channel-slider]` (from our mergeProps). So both
pseudo-elements live on the SAME element:
::before — owned by the slider recipe; it's the track strip.
::after — used here for the transparency checker behind the track.
We override the slider's `::before` background to paint the channel
gradient (rainbow for hue, white→solid for sat/bright, transparent→
solid for alpha) and put the checker on `::after` with z-index -1 so
it shows through wherever the gradient is transparent (alpha case). */
[data-color-picker-channel-slider][data-slider] {
position: relative;
inline-size: 100%;
isolation: isolate;
}
/* Transparency checker sits BEHIND the slider track for the alpha channel
(where the gradient ramps to transparent). For non-alpha channels the
gradient is opaque, so the checker stays hidden. */
[data-color-picker-channel-slider]::before {
/* The slider's track (::before) — paint with the channel gradient. */
[data-color-picker-channel-slider][data-slider]::before {
background: var(--cp-channel-gradient, var(--slider-track-bg)) !important;
border: var(--border-width) solid var(--color-border-subtle);
box-sizing: border-box;
}
/* Transparency checker BEHIND the track. Same strip geometry as the
slider's ::before so the cells align with the gradient. */
[data-color-picker-channel-slider][data-slider]::after {
content: '';
position: absolute;
z-index: 0;
z-index: -1;
inset-inline: 0;
inset-block-start: 50%;
block-size: var(--slider-track-size-md);
block-size: var(--_slider-track-size, var(--slider-track-size-md));
transform: translateY(-50%);
border-radius: var(--slider-track-radius);
background-image: var(--cp-checker);
@ -325,15 +342,10 @@
pointer-events: none;
}
/* The slider's own track (::before of [data-slider]) gets the channel
gradient. Slider.Range is hidden because color sliders show the full
channel range as a single gradient, not a "left-of-thumb" fill. */
[data-color-picker-channel-slider] [data-slider]::before {
background: var(--cp-channel-gradient, var(--slider-track-bg));
}
[data-color-picker-channel-slider] [data-slider-range] {
background: transparent;
/* Slider.Range fill is irrelevant for color sliders — the full gradient
speaks for itself. */
[data-color-picker-channel-slider][data-slider] [data-slider-range] {
background: transparent !important;
}
/* Reinforce thumb visibility against any colour: bright ring + dark shadow.

@ -245,11 +245,29 @@
<ColorPicker.Portal>
<ColorPicker.Content>
<ColorPicker.Area />
<ColorPicker.ChannelSlider channel="hue" />
<!-- Channel sliders with explicit row labels so users
know what each gradient drives. -->
<div class="cp-slider-row">
<span class="cp-slider-label">Hue</span>
<ColorPicker.ChannelSlider channel="hue" />
</div>
{#if enableAlpha}
<ColorPicker.ChannelSlider channel="alpha" />
<div class="cp-slider-row">
<span class="cp-slider-label">Alpha</span>
<ColorPicker.ChannelSlider channel="alpha" />
</div>
{/if}
<!-- Live preview + segmented channel input on one row. -->
<div class="cp-preview-row">
<ColorPicker.ValueSwatch />
<div class="cp-preview-text">
<ColorPicker.ValueText />
<span class="cp-preview-format">{format.toUpperCase()}</span>
</div>
</div>
<ColorPicker.ChannelInput>
{#snippet children({ segments }: ChannelInputSnippetProps)}
{#each segments as { part, value }, i (`${part}-${i}`)}
@ -259,6 +277,7 @@
{/snippet}
</ColorPicker.ChannelInput>
<div class="cp-section-label">Preset colors</div>
<ColorPicker.SwatchGroup aria-label="Preset colors">
{#each presets as preset}
<ColorPicker.SwatchTrigger color={preset}>
@ -268,8 +287,9 @@
{/each}
</ColorPicker.SwatchGroup>
<div style="display: flex; gap: var(--uix-space-2); align-items: center; justify-content: flex-end;">
<ColorPicker.EyeDropper />
<div class="cp-eyedropper-row">
<ColorPicker.EyeDropper aria-label="Pick color from screen" />
<span class="cp-eyedropper-label">Pick from screen</span>
</div>
{#if showFooter && (showClear || showCancel || showClose)}
@ -656,3 +676,61 @@
</section>
{/if}
</div>
<style>
.cp-slider-row {
display: flex;
align-items: center;
gap: var(--uix-space-2, 0.5rem);
}
.cp-slider-label {
flex: 0 0 3.25rem;
font-size: 11px;
font-weight: 500;
color: var(--uix-text-faint);
text-transform: uppercase;
letter-spacing: 0.04em;
}
.cp-preview-row {
display: flex;
align-items: center;
gap: var(--uix-space-3, 0.75rem);
padding: var(--uix-space-2, 0.5rem);
border-radius: var(--uix-radius-sm);
background: var(--uix-surface-sunken);
}
.cp-preview-row :global([data-color-picker-value-swatch]) {
inline-size: 2rem;
block-size: 2rem;
}
.cp-preview-text {
display: flex;
flex-direction: column;
gap: 2px;
min-inline-size: 0;
}
.cp-preview-format {
font-size: 10px;
color: var(--uix-text-faint);
text-transform: uppercase;
letter-spacing: 0.06em;
}
.cp-section-label {
font-size: 11px;
font-weight: 500;
color: var(--uix-text-faint);
text-transform: uppercase;
letter-spacing: 0.04em;
margin-block-start: var(--uix-space-1, 0.25rem);
}
.cp-eyedropper-row {
display: flex;
align-items: center;
gap: var(--uix-space-2, 0.5rem);
justify-content: flex-end;
}
.cp-eyedropper-label {
font-size: 12px;
color: var(--uix-text-faint);
}
</style>

Loading…
Cancel
Save

Powered by TurnKey Linux.