fixes
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { type ComponentPropsWithoutRef } from 'react';
|
||||
import { type ComponentPropsWithoutRef, useEffect, useRef } from 'react';
|
||||
import { ToggleGroup as RToggleGroup } from 'radix-ui';
|
||||
import { cx } from '../utils';
|
||||
|
||||
@@ -18,22 +18,49 @@ export const SegmentedControl = ({
|
||||
items,
|
||||
className,
|
||||
...props
|
||||
}: SegProps) => (
|
||||
<RToggleGroup.Root
|
||||
type="single"
|
||||
className={cx('modern-sk-seg', className)}
|
||||
value={value}
|
||||
onValueChange={(v) => v && onValueChange(v)}
|
||||
{...props}
|
||||
>
|
||||
{items.map((it) => (
|
||||
<RToggleGroup.Item
|
||||
key={it.value}
|
||||
value={it.value}
|
||||
className="modern-sk-seg__item"
|
||||
>
|
||||
{it.label}
|
||||
</RToggleGroup.Item>
|
||||
))}
|
||||
</RToggleGroup.Root>
|
||||
);
|
||||
}: SegProps) => {
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const thumbRef = useRef<HTMLSpanElement>(null);
|
||||
const initialized = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
const root = rootRef.current;
|
||||
const thumb = thumbRef.current;
|
||||
if (!root || !thumb) return;
|
||||
const selected = root.querySelector<HTMLElement>('[data-state="on"]');
|
||||
if (!selected) return;
|
||||
|
||||
if (!initialized.current) {
|
||||
thumb.style.transition = 'none';
|
||||
}
|
||||
thumb.style.transform = `translateX(${selected.offsetLeft}px)`;
|
||||
thumb.style.width = `${selected.offsetWidth}px`;
|
||||
if (!initialized.current) {
|
||||
thumb.getBoundingClientRect();
|
||||
thumb.style.transition = '';
|
||||
initialized.current = true;
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<RToggleGroup.Root
|
||||
ref={rootRef}
|
||||
type="single"
|
||||
className={cx('modern-sk-seg', className)}
|
||||
value={value}
|
||||
onValueChange={(v) => v && onValueChange(v)}
|
||||
{...props}
|
||||
>
|
||||
<span ref={thumbRef} className="modern-sk-seg__thumb" aria-hidden />
|
||||
{items.map((it) => (
|
||||
<RToggleGroup.Item
|
||||
key={it.value}
|
||||
value={it.value}
|
||||
className="modern-sk-seg__item"
|
||||
>
|
||||
{it.label}
|
||||
</RToggleGroup.Item>
|
||||
))}
|
||||
</RToggleGroup.Root>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,14 +1,62 @@
|
||||
import { type ComponentPropsWithoutRef } from 'react';
|
||||
import { Slider as RSlider } from 'radix-ui';
|
||||
|
||||
export const Slider = (props: ComponentPropsWithoutRef<typeof RSlider.Root>) => (
|
||||
<RSlider.Root className="modern-sk-slider" {...props}>
|
||||
<RSlider.Track className="modern-sk-slider__track">
|
||||
<RSlider.Range className="modern-sk-slider__range" />
|
||||
</RSlider.Track>
|
||||
<RSlider.Thumb className="modern-sk-slider__thumb" aria-label="Value" />
|
||||
</RSlider.Root>
|
||||
);
|
||||
type Step = { value: number; label?: string };
|
||||
|
||||
type SliderProps = ComponentPropsWithoutRef<typeof RSlider.Root> & {
|
||||
steps?: number | Step[];
|
||||
};
|
||||
|
||||
function resolveSteps(steps: number | Step[], min: number, max: number): Step[] {
|
||||
if (Array.isArray(steps)) return steps;
|
||||
if (steps < 2) return [];
|
||||
return Array.from({ length: steps }, (_, i) => ({
|
||||
value: min + (i / (steps - 1)) * (max - min),
|
||||
}));
|
||||
}
|
||||
|
||||
export const Slider = ({ steps, min = 0, max = 100, ...props }: SliderProps) => {
|
||||
const resolved = steps != null ? resolveSteps(steps, min, max) : [];
|
||||
const hasSteps = resolved.length > 0;
|
||||
|
||||
return (
|
||||
<RSlider.Root
|
||||
className={`modern-sk-slider${hasSteps ? ' modern-sk-slider--has-steps' : ''}`}
|
||||
min={min}
|
||||
max={max}
|
||||
{...props}
|
||||
>
|
||||
<RSlider.Track className="modern-sk-slider__track">
|
||||
<RSlider.Range className="modern-sk-slider__range" />
|
||||
{hasSteps && resolved.map((step) => (
|
||||
<div
|
||||
key={step.value}
|
||||
className="modern-sk-slider__step-dot"
|
||||
aria-hidden
|
||||
style={{ left: `${((step.value - min) / (max - min)) * 100}%` }}
|
||||
/>
|
||||
))}
|
||||
</RSlider.Track>
|
||||
<RSlider.Thumb className="modern-sk-slider__thumb" aria-label="Value" />
|
||||
{hasSteps && (
|
||||
<div className="modern-sk-slider__steps" aria-hidden>
|
||||
{resolved.map((step) => (
|
||||
<div
|
||||
key={step.value}
|
||||
className="modern-sk-slider__step"
|
||||
style={{ left: `${((step.value - min) / (max - min)) * 100}%` }}
|
||||
>
|
||||
<div className="modern-sk-slider__step-tick" />
|
||||
{step.label != null && (
|
||||
<span className="modern-sk-slider__step-label">{step.label}</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</RSlider.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export const Stepper = ({
|
||||
onDecrement,
|
||||
|
||||
@@ -9,7 +9,7 @@ type TooltipProps = {
|
||||
open?: boolean;
|
||||
defaultOpen?: boolean;
|
||||
onOpenChange?: (o: boolean) => void;
|
||||
} & Omit<ComponentPropsWithoutRef<typeof RTooltip.Content>, 'children'>;
|
||||
} & Omit<ComponentPropsWithoutRef<typeof RTooltip.Content>, 'children' | 'content'>;
|
||||
|
||||
export const Tooltip = ({
|
||||
content,
|
||||
|
||||
@@ -40,7 +40,7 @@ export const WithBody: Story = {
|
||||
description="Choose a new name for this project."
|
||||
footer={<Button variant="primary">Save</Button>}
|
||||
>
|
||||
<TextField label="Project name" defaultValue="My project" />
|
||||
<TextField placeholder="Project name" defaultValue="My project" />
|
||||
</Dialog>
|
||||
),
|
||||
};
|
||||
|
||||
@@ -23,6 +23,14 @@ const meta = {
|
||||
},
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
children: { control: false },
|
||||
content: { control: false },
|
||||
},
|
||||
args: {
|
||||
content: '',
|
||||
children: null,
|
||||
},
|
||||
} satisfies Meta<typeof Tooltip>;
|
||||
|
||||
export default meta;
|
||||
|
||||
@@ -165,10 +165,11 @@ textarea.modern-sk-field{ resize:vertical; min-height:64px; line-height:1.5; }
|
||||
.modern-sk-control{ display:inline-flex; align-items:center; gap:10px; font-size:14px; color:var(--fg-2); cursor:pointer; }
|
||||
|
||||
/* ---------- SEGMENTED CONTROL (Radix ToggleGroup) ---------- */
|
||||
.modern-sk-seg{ display:inline-flex; background:var(--steel-900); border:1px solid var(--edge-inset); border-radius:var(--r-md); padding:3px; box-shadow:var(--shadow-inset-well); gap:2px; }
|
||||
.modern-sk-seg__item{ font-family:var(--font-sans); font-size:13px; font-weight:600; color:var(--fg-2); background:transparent; border:none; padding:var(--seg-pad-y) 14px; border-radius:var(--r-sm); cursor:pointer; transition:color var(--dur-quick), background var(--dur-quick); }
|
||||
.modern-sk-seg{ position:relative; display:inline-flex; background:var(--steel-900); border:1px solid var(--edge-inset); border-radius:var(--r-md); padding:3px; box-shadow:var(--shadow-inset-well); gap:2px; }
|
||||
.modern-sk-seg__thumb{ position:absolute; top:3px; left:0; height:calc(100% - 6px); background:var(--grad-key); border-radius:var(--r-sm); box-shadow:var(--shadow-raised); pointer-events:none; transition:transform 180ms var(--ease-snap), width 180ms var(--ease-snap); will-change:transform,width; }
|
||||
.modern-sk-seg__item{ position:relative; z-index:1; font-family:var(--font-sans); font-size:13px; font-weight:600; color:var(--fg-2); background:transparent; border:none; padding:var(--seg-pad-y) 14px; border-radius:var(--r-sm); cursor:pointer; transition:color var(--dur-quick); }
|
||||
.modern-sk-seg__item:hover{ color:var(--fg-1); }
|
||||
.modern-sk-seg__item[data-state="on"]{ color:var(--fg-1); background:var(--grad-key); box-shadow:var(--shadow-raised); }
|
||||
.modern-sk-seg__item[data-state="on"]{ color:var(--fg-1); }
|
||||
|
||||
/* ---------- SLIDER ---------- */
|
||||
.modern-sk-slider{ position:relative; display:flex; align-items:center; width:200px; height:20px; user-select:none; touch-action:none; }
|
||||
@@ -176,6 +177,12 @@ textarea.modern-sk-field{ resize:vertical; min-height:64px; line-height:1.5; }
|
||||
.modern-sk-slider__range{ position:absolute; height:100%; border-radius:3px; background:linear-gradient(90deg,var(--lime-deep),var(--lime)); box-shadow:0 0 10px rgba(190,242,100,.4); }
|
||||
.modern-sk-slider__thumb{ display:block; width:20px; height:20px; border-radius:50%; background:linear-gradient(180deg,#fff,#e6e8dd); box-shadow:0 2px 5px rgba(0,0,0,.5), 0 1px 0 rgba(255,255,255,.9) inset; cursor:pointer; outline:none; }
|
||||
.modern-sk-slider__thumb:focus-visible{ box-shadow:0 2px 5px rgba(0,0,0,.5), 0 1px 0 rgba(255,255,255,.9) inset, var(--focus-ring); }
|
||||
.modern-sk-slider--has-steps{ padding-bottom:22px; }
|
||||
.modern-sk-slider__step-dot{ position:absolute; top:50%; width:4px; height:4px; border-radius:50%; background:var(--steel-500); transform:translate(-50%,-50%); z-index:1; pointer-events:none; }
|
||||
.modern-sk-slider__steps{ position:absolute; left:10px; right:10px; top:calc(50% + 6px); pointer-events:none; }
|
||||
.modern-sk-slider__step{ position:absolute; transform:translateX(-50%); display:flex; flex-direction:column; align-items:center; gap:3px; }
|
||||
.modern-sk-slider__step-tick{ width:1px; height:5px; background:var(--steel-600); }
|
||||
.modern-sk-slider__step-label{ font-family:var(--font-mono); font-size:10px; line-height:1; color:var(--fg-3); white-space:nowrap; }
|
||||
|
||||
/* ---------- STEPPER ---------- */
|
||||
.modern-sk-stepper{ display:inline-flex; border-radius:var(--r-md); overflow:hidden; box-shadow:var(--shadow-raised); border:1px solid var(--hair-strong); }
|
||||
|
||||
Reference in New Issue
Block a user