refactor ElementPickerSegmented to inline styles with element-specific states
This commit is contained in:
parent
31528bdc1f
commit
55ac4df9f2
2 changed files with 282 additions and 100 deletions
|
|
@ -4,7 +4,6 @@
|
||||||
import { ToggleGroup } from 'bits-ui'
|
import { ToggleGroup } from 'bits-ui'
|
||||||
import Tooltip from '../Tooltip.svelte'
|
import Tooltip from '../Tooltip.svelte'
|
||||||
import { ELEMENT_LABELS, getElementImage } from '$lib/utils/element'
|
import { ELEMENT_LABELS, getElementImage } from '$lib/utils/element'
|
||||||
import styles from './element-picker.module.scss'
|
|
||||||
|
|
||||||
// Element display order: Fire(2) → Water(3) → Earth(4) → Wind(1) → Light(6) → Dark(5)
|
// Element display order: Fire(2) → Water(3) → Earth(4) → Wind(1) → Light(6) → Dark(5)
|
||||||
const ELEMENT_DISPLAY_ORDER = [2, 3, 4, 1, 6, 5]
|
const ELEMENT_DISPLAY_ORDER = [2, 3, 4, 1, 6, 5]
|
||||||
|
|
@ -16,6 +15,8 @@
|
||||||
includeAny?: boolean
|
includeAny?: boolean
|
||||||
contained?: boolean
|
contained?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
size?: 'small' | 'regular'
|
||||||
|
showClear?: boolean
|
||||||
class?: string
|
class?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,9 +27,31 @@
|
||||||
includeAny = false,
|
includeAny = false,
|
||||||
contained = false,
|
contained = false,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
size = 'small',
|
||||||
|
showClear = false,
|
||||||
class: className = ''
|
class: className = ''
|
||||||
}: Props = $props()
|
}: Props = $props()
|
||||||
|
|
||||||
|
|
||||||
|
// Check if any elements are selected
|
||||||
|
const hasSelection = $derived.by(() => {
|
||||||
|
if (multiple) {
|
||||||
|
const arr = Array.isArray(value) ? value : value !== undefined ? [value] : []
|
||||||
|
return arr.length > 0
|
||||||
|
}
|
||||||
|
return value !== undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleClear() {
|
||||||
|
if (multiple) {
|
||||||
|
value = []
|
||||||
|
onValueChange?.([])
|
||||||
|
} else {
|
||||||
|
value = undefined
|
||||||
|
onValueChange?.(undefined as any)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build element list based on includeAny prop
|
// Build element list based on includeAny prop
|
||||||
const elements = $derived(includeAny ? [0, ...ELEMENT_DISPLAY_ORDER] : ELEMENT_DISPLAY_ORDER)
|
const elements = $derived(includeAny ? [0, ...ELEMENT_DISPLAY_ORDER] : ELEMENT_DISPLAY_ORDER)
|
||||||
|
|
||||||
|
|
@ -64,27 +87,35 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const containerClasses = $derived(
|
const containerClasses = $derived(
|
||||||
[styles.container, contained && styles.contained, className].filter(Boolean).join(' ')
|
['container', contained && 'contained', size === 'regular' ? 'regular' : 'small', className]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ')
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class={containerClasses}>
|
{#if showClear}
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class={containerClasses}>
|
||||||
{#if multiple}
|
{#if multiple}
|
||||||
<ToggleGroup.Root
|
<ToggleGroup.Root
|
||||||
type="multiple"
|
type="multiple"
|
||||||
value={stringValue as string[]}
|
value={stringValue as string[]}
|
||||||
onValueChange={handleMultipleChange}
|
onValueChange={handleMultipleChange}
|
||||||
class={styles.group}
|
class="group"
|
||||||
{disabled}
|
{disabled}
|
||||||
>
|
>
|
||||||
{#each elements as element}
|
{#each elements as element}
|
||||||
<Tooltip content={getLabel(element)}>
|
<Tooltip content={getLabel(element)}>
|
||||||
{#snippet children()}
|
{#snippet children()}
|
||||||
<ToggleGroup.Item value={String(element)} class={styles.item} {disabled}>
|
<ToggleGroup.Item
|
||||||
|
value={String(element)}
|
||||||
|
class="item"
|
||||||
|
{disabled}
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src={getElementImage(element)}
|
src={getElementImage(element)}
|
||||||
alt={getLabel(element)}
|
alt={getLabel(element)}
|
||||||
class={styles.image}
|
class="image"
|
||||||
/>
|
/>
|
||||||
</ToggleGroup.Item>
|
</ToggleGroup.Item>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
@ -96,17 +127,21 @@
|
||||||
type="single"
|
type="single"
|
||||||
value={stringValue as string | undefined}
|
value={stringValue as string | undefined}
|
||||||
onValueChange={handleSingleChange}
|
onValueChange={handleSingleChange}
|
||||||
class={styles.group}
|
class="group"
|
||||||
{disabled}
|
{disabled}
|
||||||
>
|
>
|
||||||
{#each elements as element}
|
{#each elements as element}
|
||||||
<Tooltip content={getLabel(element)}>
|
<Tooltip content={getLabel(element)}>
|
||||||
{#snippet children()}
|
{#snippet children()}
|
||||||
<ToggleGroup.Item value={String(element)} class={styles.item} {disabled}>
|
<ToggleGroup.Item
|
||||||
|
value={String(element)}
|
||||||
|
class="item"
|
||||||
|
{disabled}
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src={getElementImage(element)}
|
src={getElementImage(element)}
|
||||||
alt={getLabel(element)}
|
alt={getLabel(element)}
|
||||||
class={styles.image}
|
class="image"
|
||||||
/>
|
/>
|
||||||
</ToggleGroup.Item>
|
</ToggleGroup.Item>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
@ -114,4 +149,202 @@
|
||||||
{/each}
|
{/each}
|
||||||
</ToggleGroup.Root>
|
</ToggleGroup.Root>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
{#if hasSelection}
|
||||||
|
<button type="button" class="clearButton" onclick={handleClear}> Clear </button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class={containerClasses}>
|
||||||
|
{#if multiple}
|
||||||
|
<ToggleGroup.Root
|
||||||
|
type="multiple"
|
||||||
|
value={stringValue as string[]}
|
||||||
|
onValueChange={handleMultipleChange}
|
||||||
|
class="group"
|
||||||
|
{disabled}
|
||||||
|
>
|
||||||
|
{#each elements as element}
|
||||||
|
<Tooltip content={getLabel(element)}>
|
||||||
|
{#snippet children()}
|
||||||
|
<ToggleGroup.Item
|
||||||
|
value={String(element)}
|
||||||
|
class="item"
|
||||||
|
{disabled}
|
||||||
|
>
|
||||||
|
<img src={getElementImage(element)} alt={getLabel(element)} class="image" />
|
||||||
|
</ToggleGroup.Item>
|
||||||
|
{/snippet}
|
||||||
|
</Tooltip>
|
||||||
|
{/each}
|
||||||
|
</ToggleGroup.Root>
|
||||||
|
{:else}
|
||||||
|
<ToggleGroup.Root
|
||||||
|
type="single"
|
||||||
|
value={stringValue as string | undefined}
|
||||||
|
onValueChange={handleSingleChange}
|
||||||
|
class="group"
|
||||||
|
{disabled}
|
||||||
|
>
|
||||||
|
{#each elements as element}
|
||||||
|
<Tooltip content={getLabel(element)}>
|
||||||
|
{#snippet children()}
|
||||||
|
<ToggleGroup.Item
|
||||||
|
value={String(element)}
|
||||||
|
class="item"
|
||||||
|
{disabled}
|
||||||
|
>
|
||||||
|
<img src={getElementImage(element)} alt={getLabel(element)} class="image" />
|
||||||
|
</ToggleGroup.Item>
|
||||||
|
{/snippet}
|
||||||
|
</Tooltip>
|
||||||
|
{/each}
|
||||||
|
</ToggleGroup.Root>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@use '$src/themes/spacing' as *;
|
||||||
|
@use '$src/themes/layout' as *;
|
||||||
|
@use '$src/themes/effects' as *;
|
||||||
|
@use '$src/themes/colors' as *;
|
||||||
|
@use '$src/themes/typography' as *;
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
gap: $unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: inline-flex;
|
||||||
|
border-radius: $full-corner;
|
||||||
|
padding: $unit-half;
|
||||||
|
|
||||||
|
&.contained {
|
||||||
|
background-color: var(--segmented-control-background-bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.group) {
|
||||||
|
display: flex;
|
||||||
|
gap: $unit-half;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.item) {
|
||||||
|
all: unset;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: $full-corner;
|
||||||
|
padding: $unit-half;
|
||||||
|
@include smooth-transition($duration-quick, background-color, opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.item:focus-visible) {
|
||||||
|
@include focus-ring($blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.item:disabled) {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Element-specific hover and selected states
|
||||||
|
:global(.item[data-value='0']:hover:not(:disabled)) {
|
||||||
|
background-color: var(--null-nav-hover-bg);
|
||||||
|
}
|
||||||
|
:global(.item[data-value='0'][data-state='on']:not(:hover)) {
|
||||||
|
background-color: var(--null-nav-selected-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.item[data-value='1']:hover:not(:disabled)) {
|
||||||
|
background-color: var(--wind-nav-hover-bg);
|
||||||
|
}
|
||||||
|
:global(.item[data-value='1'][data-state='on']:not(:hover)) {
|
||||||
|
background-color: var(--wind-nav-selected-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.item[data-value='2']:hover:not(:disabled)) {
|
||||||
|
background-color: var(--fire-nav-hover-bg);
|
||||||
|
}
|
||||||
|
:global(.item[data-value='2'][data-state='on']:not(:hover)) {
|
||||||
|
background-color: var(--fire-nav-selected-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.item[data-value='3']:hover:not(:disabled)) {
|
||||||
|
background-color: var(--water-nav-hover-bg);
|
||||||
|
}
|
||||||
|
:global(.item[data-value='3'][data-state='on']:not(:hover)) {
|
||||||
|
background-color: var(--water-nav-selected-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.item[data-value='4']:hover:not(:disabled)) {
|
||||||
|
background-color: var(--earth-nav-hover-bg);
|
||||||
|
}
|
||||||
|
:global(.item[data-value='4'][data-state='on']:not(:hover)) {
|
||||||
|
background-color: var(--earth-nav-selected-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.item[data-value='5']:hover:not(:disabled)) {
|
||||||
|
background-color: var(--dark-nav-hover-bg);
|
||||||
|
}
|
||||||
|
:global(.item[data-value='5'][data-state='on']:not(:hover)) {
|
||||||
|
background-color: var(--dark-nav-selected-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.item[data-value='6']:hover:not(:disabled)) {
|
||||||
|
background-color: var(--light-nav-hover-bg);
|
||||||
|
}
|
||||||
|
:global(.item[data-value='6'][data-state='on']:not(:hover)) {
|
||||||
|
background-color: var(--light-nav-selected-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.image) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size variants
|
||||||
|
.small {
|
||||||
|
:global(.item) {
|
||||||
|
padding: $unit-half;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.image) {
|
||||||
|
width: $unit-3x;
|
||||||
|
height: $unit-3x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.regular {
|
||||||
|
:global(.item) {
|
||||||
|
padding: $unit-half;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.image) {
|
||||||
|
width: $unit-4x;
|
||||||
|
height: $unit-4x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearButton {
|
||||||
|
all: unset;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: $font-small;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
padding: $unit-half $unit;
|
||||||
|
border-radius: $input-corner;
|
||||||
|
@include smooth-transition($duration-quick, background-color, color);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--option-bg-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
@include focus-ring($blue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
@use '$src/themes/spacing' as *;
|
|
||||||
@use '$src/themes/layout' as *;
|
|
||||||
@use '$src/themes/effects' as *;
|
|
||||||
@use '$src/themes/colors' as *;
|
|
||||||
|
|
||||||
.container {
|
|
||||||
display: inline-flex;
|
|
||||||
border-radius: $full-corner;
|
|
||||||
padding: $unit-half;
|
|
||||||
|
|
||||||
&.contained {
|
|
||||||
background-color: var(--segmented-control-background-bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.group {
|
|
||||||
display: flex;
|
|
||||||
gap: $unit-half;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item {
|
|
||||||
all: unset;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: $full-corner;
|
|
||||||
padding: $unit-half;
|
|
||||||
@include smooth-transition($duration-quick, background-color, opacity);
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
background-color: var(--option-bg-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus-visible {
|
|
||||||
@include focus-ring($blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-state='on'] {
|
|
||||||
background-color: var(--accent-subtle-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.image {
|
|
||||||
width: $unit-3x;
|
|
||||||
height: $unit-3x;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue