Compare commits
4 Commits
b4e97da3a0
...
6f840fbad8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f840fbad8 | ||
|
|
a7d08a9329 | ||
|
|
df2d6bae3b | ||
|
|
ce9665a842 |
@@ -11,7 +11,6 @@ import {
|
||||
} from '$shared/shadcn/ui/item';
|
||||
import { cn } from '$shared/shadcn/utils/shadcn-utils';
|
||||
import {
|
||||
ComboControl,
|
||||
ComboControlV2,
|
||||
Drawer,
|
||||
IconButton,
|
||||
@@ -91,6 +90,7 @@ $effect(() => {
|
||||
<ComboControlV2
|
||||
control={control.instance}
|
||||
orientation="horizontal"
|
||||
reduced
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -105,12 +105,12 @@ $effect(() => {
|
||||
<div class="sm:py-2 sm:px-10 flex flex-row items-center gap-2">
|
||||
<div class="flex flex-row gap-3">
|
||||
{#each controlManager.controls as control (control.id)}
|
||||
<ComboControl
|
||||
<ComboControlV2
|
||||
control={control.instance}
|
||||
increaseLabel={control.increaseLabel}
|
||||
decreaseLabel={control.decreaseLabel}
|
||||
controlLabel={control.controlLabel}
|
||||
reduced={responsive.isMobile}
|
||||
orientation="vertical"
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -4,11 +4,29 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { TypographyControl } from '$shared/lib';
|
||||
import { Button } from '$shared/shadcn/ui/button';
|
||||
import { Root as ButtonGroupRoot } from '$shared/shadcn/ui/button-group';
|
||||
import {
|
||||
Content as PopoverContent,
|
||||
Root as PopoverRoot,
|
||||
Trigger as PopoverTrigger,
|
||||
} from '$shared/shadcn/ui/popover';
|
||||
import {
|
||||
Content as TooltipContent,
|
||||
Root as TooltipRoot,
|
||||
Trigger as TooltipTrigger,
|
||||
} from '$shared/shadcn/ui/tooltip';
|
||||
import { cn } from '$shared/shadcn/utils/shadcn-utils';
|
||||
import { Input } from '$shared/ui';
|
||||
import { Slider } from '$shared/ui';
|
||||
import type { Orientation } from 'bits-ui';
|
||||
import MinusIcon from '@lucide/svelte/icons/minus';
|
||||
import PlusIcon from '@lucide/svelte/icons/plus';
|
||||
import {
|
||||
type Orientation,
|
||||
REGEXP_ONLY_DIGITS,
|
||||
} from 'bits-ui';
|
||||
import type { ChangeEventHandler } from 'svelte/elements';
|
||||
import IconButton from '../IconButton/IconButton.svelte';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
@@ -27,6 +45,28 @@ interface Props {
|
||||
* CSS class
|
||||
*/
|
||||
class?: string;
|
||||
/**
|
||||
* Show scale flag
|
||||
*/
|
||||
showScale?: boolean;
|
||||
/**
|
||||
* Flag that change component appearance
|
||||
* from the one with increase/decrease buttons and popover with input + slider
|
||||
* to just input + slider
|
||||
*/
|
||||
reduced?: boolean;
|
||||
/**
|
||||
* Text for increase button aria-label
|
||||
*/
|
||||
increaseLabel?: string;
|
||||
/**
|
||||
* Text for decrease button aria-label
|
||||
*/
|
||||
decreaseLabel?: string;
|
||||
/**
|
||||
* Text for control button aria-label
|
||||
*/
|
||||
controlLabel?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -34,6 +74,11 @@ let {
|
||||
orientation = 'vertical',
|
||||
label,
|
||||
class: className,
|
||||
showScale = true,
|
||||
reduced = false,
|
||||
increaseLabel = 'Increase',
|
||||
decreaseLabel = 'Decrease',
|
||||
controlLabel,
|
||||
}: Props = $props();
|
||||
|
||||
let inputValue = $state(String(control.value));
|
||||
@@ -49,26 +94,29 @@ const handleInputChange: ChangeEventHandler<HTMLInputElement> = event => {
|
||||
inputValue = String(parsedValue);
|
||||
}
|
||||
};
|
||||
|
||||
function calculateScale(index: number): number | string {
|
||||
const calculate = () =>
|
||||
orientation === 'horizontal'
|
||||
? (control.min + (index * (control.max - control.min) / 4))
|
||||
: (control.max - (index * (control.max - control.min) / 4));
|
||||
return Number.isInteger(control.step)
|
||||
? Math.round(calculate())
|
||||
: (calculate()).toFixed(2);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
{#snippet ComboControl()}
|
||||
<div
|
||||
class={cn(
|
||||
'flex gap-4 sm:p-4 rounded-xl transition-all duration-300',
|
||||
'backdrop-blur-md',
|
||||
orientation === 'horizontal' ? 'flex-row items-end w-full' : 'flex-col items-center h-full',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Input
|
||||
class="h-10 rounded-lg w-12 pl-1 pr-1 sm:pr-1 md:pr-1 sm:pl-1 md:pl-1 text-center"
|
||||
value={inputValue}
|
||||
onchange={handleInputChange}
|
||||
min={control.min}
|
||||
max={control.max}
|
||||
step={control.step}
|
||||
/>
|
||||
|
||||
>
|
||||
<div class={cn('relative', orientation === 'horizontal' ? 'w-full' : 'h-full')}>
|
||||
{#if showScale}
|
||||
<div
|
||||
class={cn(
|
||||
'absolute flex justify-between',
|
||||
@@ -83,16 +131,14 @@ const handleInputChange: ChangeEventHandler<HTMLInputElement> = event => {
|
||||
)}
|
||||
>
|
||||
<span class="font-mono text-[0.375rem] text-gray-400 tabular-nums">
|
||||
{
|
||||
Number.isInteger(control.step)
|
||||
? Math.round(control.min + (i * (control.max - control.min) / 4))
|
||||
: (control.min + (i * (control.max - control.min) / 4)).toFixed(2)
|
||||
}
|
||||
{calculateScale(i)}
|
||||
</span>
|
||||
<div class={cn('bg-gray-300', orientation === 'horizontal' ? 'w-px h-1' : 'h-px w-1')}></div>
|
||||
<div class={cn('bg-gray-300', orientation === 'horizontal' ? 'w-px h-1' : 'h-px w-1')}>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Slider
|
||||
class={cn(orientation === 'horizontal' ? 'w-full' : 'h-full')}
|
||||
@@ -104,6 +150,17 @@ const handleInputChange: ChangeEventHandler<HTMLInputElement> = event => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
class="h-10 rounded-lg w-12 pl-1 pr-1 sm:pr-1 md:pr-1 sm:pl-1 md:pl-1 text-center"
|
||||
value={inputValue}
|
||||
onchange={handleInputChange}
|
||||
min={control.min}
|
||||
max={control.max}
|
||||
step={control.step}
|
||||
pattern={REGEXP_ONLY_DIGITS}
|
||||
variant="ghost"
|
||||
/>
|
||||
|
||||
{#if label}
|
||||
<div class="flex items-center gap-2 opacity-70">
|
||||
<div class="w-1 h-1 rounded-full bg-gray-900"></div>
|
||||
@@ -113,4 +170,60 @@ const handleInputChange: ChangeEventHandler<HTMLInputElement> = event => {
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#if reduced}
|
||||
{@render ComboControl()}
|
||||
{:else}
|
||||
<TooltipRoot>
|
||||
<ButtonGroupRoot class="bg-transparent border-none shadow-none">
|
||||
<TooltipTrigger class="flex items-center">
|
||||
<IconButton
|
||||
onclick={control.decrease}
|
||||
disabled={control.isAtMin}
|
||||
aria-label={decreaseLabel}
|
||||
rotation="counterclockwise"
|
||||
>
|
||||
{#snippet icon({ className })}
|
||||
<MinusIcon class={className} />
|
||||
{/snippet}
|
||||
</IconButton>
|
||||
<PopoverRoot>
|
||||
<PopoverTrigger>
|
||||
{#snippet child({ props })}
|
||||
<Button
|
||||
{...props}
|
||||
variant="ghost"
|
||||
class="hover:bg-white/50 hover:font-bold bg-white/20 border-none duration-150 will-change-transform active:scale-95 cursor-pointer"
|
||||
size="icon"
|
||||
aria-label={controlLabel}
|
||||
>
|
||||
{control.value}
|
||||
</Button>
|
||||
{/snippet}
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto h-64 sm:px-1 py-0">
|
||||
{@render ComboControl()}
|
||||
</PopoverContent>
|
||||
</PopoverRoot>
|
||||
|
||||
<IconButton
|
||||
aria-label={increaseLabel}
|
||||
onclick={control.increase}
|
||||
disabled={control.isAtMax}
|
||||
rotation="clockwise"
|
||||
>
|
||||
{#snippet icon({ className })}
|
||||
<PlusIcon class={className} />
|
||||
{/snippet}
|
||||
</IconButton>
|
||||
</TooltipTrigger>
|
||||
</ButtonGroupRoot>
|
||||
{#if controlLabel}
|
||||
<TooltipContent>
|
||||
{controlLabel}
|
||||
</TooltipContent>
|
||||
{/if}
|
||||
</TooltipRoot>
|
||||
{/if}
|
||||
|
||||
@@ -16,22 +16,29 @@ type Props = ComponentProps<typeof Input> & {
|
||||
* Additional CSS classes for the container
|
||||
*/
|
||||
class?: string;
|
||||
|
||||
variant?: 'default' | 'ghost';
|
||||
};
|
||||
|
||||
let {
|
||||
value = $bindable(''),
|
||||
class: className,
|
||||
variant = 'default',
|
||||
...rest
|
||||
}: Props = $props();
|
||||
|
||||
const isGhost = $derived(variant === 'ghost');
|
||||
</script>
|
||||
|
||||
<Input
|
||||
bind:value={value}
|
||||
class={cn(
|
||||
'h-12 sm:h-14 md:h-16 w-full text-sm sm:text-base',
|
||||
'backdrop-blur-md bg-white/80',
|
||||
'backdrop-blur-md',
|
||||
isGhost ? 'bg-transparent' : 'bg-white/80',
|
||||
'border border-gray-300/50',
|
||||
'shadow-[0_1px_3px_rgba(0,0,0,0.04)]',
|
||||
isGhost ? 'border-transparent' : 'border-gray-300/50',
|
||||
isGhost ? 'shadow-none' : 'shadow-[0_1px_3px_rgba(0,0,0,0.04)]',
|
||||
'focus-visible:border-gray-400/60',
|
||||
'focus-visible:outline-none',
|
||||
'focus-visible:ring-1',
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from '$shared/ui';
|
||||
import { comparisonStore } from '$widgets/ComparisonSlider/model';
|
||||
import AArrowUP from '@lucide/svelte/icons/a-arrow-up';
|
||||
import { type Orientation } from 'bits-ui';
|
||||
import { Spring } from 'svelte/motion';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
@@ -106,6 +107,26 @@ $effect(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
{#snippet InputComponent(className: string)}
|
||||
<Input
|
||||
class={className}
|
||||
bind:value={comparisonStore.text}
|
||||
disabled={isDragging}
|
||||
onfocusin={handleInputFocus}
|
||||
placeholder="The quick brown fox..."
|
||||
/>
|
||||
{/snippet}
|
||||
|
||||
{#snippet Controls(className: string, orientation: Orientation)}
|
||||
{#if typography.weightControl && typography.sizeControl && typography.heightControl}
|
||||
<div class={className}>
|
||||
<ComboControlV2 control={typography.weightControl} {orientation} reduced />
|
||||
<ComboControlV2 control={typography.sizeControl} {orientation} reduced />
|
||||
<ComboControlV2 control={typography.heightControl} {orientation} reduced />
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
<div
|
||||
class="z-50 will-change-transform"
|
||||
style:transform="
|
||||
@@ -117,20 +138,8 @@ $effect(() => {
|
||||
>
|
||||
{#if staticPosition}
|
||||
<div class="flex flex-col gap-6">
|
||||
<Input
|
||||
class="p-6"
|
||||
bind:value={comparisonStore.text}
|
||||
disabled={isDragging}
|
||||
onfocusin={handleInputFocus}
|
||||
placeholder="The quick brown fox..."
|
||||
/>
|
||||
{#if typography.weightControl && typography.sizeControl && typography.heightControl}
|
||||
<div class="flex flex-col justify-between items-center-safe gap-6">
|
||||
<ComboControlV2 control={typography.weightControl} orientation="horizontal" />
|
||||
<ComboControlV2 control={typography.sizeControl} orientation="horizontal" />
|
||||
<ComboControlV2 control={typography.heightControl} orientation="horizontal" />
|
||||
</div>
|
||||
{/if}
|
||||
{@render InputComponent?.('p-6')}
|
||||
{@render Controls?.('flex flex-col justify-between items-center-safe gap-6', 'horizontal')}
|
||||
</div>
|
||||
{:else}
|
||||
<ExpandableWrapper
|
||||
@@ -159,46 +168,18 @@ $effect(() => {
|
||||
{/snippet}
|
||||
|
||||
{#snippet visibleContent()}
|
||||
<div class="relative">
|
||||
<!--
|
||||
<Input
|
||||
bind:value={comparisonStore.text}
|
||||
disabled={isDragging}
|
||||
onfocusin={handleInputFocus}
|
||||
class={cn(
|
||||
isActive
|
||||
? 'h-7 sm:h-8 text-[11px] sm:text-xs text-center bg-white/40 border-none rounded-lg focus-visible:ring-indigo-500/50 text-slate-900'
|
||||
: 'bg-transparent shadow-none border-none p-0 h-auto text-sm sm:text-base font-medium focus-visible:ring-0 text-slate-900/50',
|
||||
' placeholder:text-slate-400 selection:bg-indigo-100 flex-1 transition-all duration-350 w-44 sm:w-56',
|
||||
)}
|
||||
placeholder="The quick brown fox..."
|
||||
/>
|
||||
-->
|
||||
<Input
|
||||
class={cn(
|
||||
{@render InputComponent(cn(
|
||||
'pl-1 sm:pl-3 pr-1 sm:pr-3',
|
||||
'h-6 sm:h-8 md:h-10',
|
||||
'rounded-lg',
|
||||
isActive
|
||||
? 'h-7 sm:h-8 text-[0.825rem]'
|
||||
: 'bg-transparent shadow-none border-none p-0 h-auto text-sm sm:text-base font-medium focus-visible:ring-0 text-slate-900/50',
|
||||
)}
|
||||
bind:value={comparisonStore.text}
|
||||
disabled={isDragging}
|
||||
onfocusin={handleInputFocus}
|
||||
placeholder="The quick brown fox..."
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{/snippet}
|
||||
|
||||
{#snippet hiddenContent()}
|
||||
{#if typography.weightControl && typography.sizeControl && typography.heightControl}
|
||||
<div class="flex flex-row justify-between items-center-safe gap-2 sm:gap-0">
|
||||
<ComboControlV2 control={typography.weightControl} orientation="vertical" />
|
||||
<ComboControlV2 control={typography.sizeControl} orientation="vertical" />
|
||||
<ComboControlV2 control={typography.heightControl} orientation="vertical" />
|
||||
</div>
|
||||
{/if}
|
||||
{@render Controls?.('flex flex-row justify-between items-center-safe gap-2 sm:gap-0 h-64', 'vertical')}
|
||||
{/snippet}
|
||||
</ExpandableWrapper>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user