feature/responsive #22
@@ -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,8 +94,19 @@ 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>
|
||||
|
||||
{#snippet ComboControl()}
|
||||
<div
|
||||
class={cn(
|
||||
'flex gap-4 sm:p-4 rounded-xl transition-all duration-300',
|
||||
@@ -59,16 +115,8 @@ const handleInputChange: ChangeEventHandler<HTMLInputElement> = event => {
|
||||
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>
|
||||
@@ -114,3 +171,59 @@ const handleInputChange: ChangeEventHandler<HTMLInputElement> = event => {
|
||||
</div>
|
||||
{/if}
|
||||
</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}
|
||||
|
||||
Reference in New Issue
Block a user