190 lines
4.3 KiB
Svelte
190 lines
4.3 KiB
Svelte
<script lang="ts">
|
|
import Icon from '@iconify/svelte';
|
|
import { getButtonStyle, parseColorText, getSegmentStyle } from './utils';
|
|
import { themeColors } from '$lib/stores/theme';
|
|
import type { TerminalLine, FormOption } from './types';
|
|
import { createEventDispatcher } from 'svelte';
|
|
import '$lib/assets/css/tui-radio.css';
|
|
|
|
export let line: TerminalLine;
|
|
export let inline: boolean = false;
|
|
export let value: string = '';
|
|
|
|
const dispatch = createEventDispatcher<{
|
|
change: string;
|
|
}>();
|
|
|
|
$: labelSegments = line.content ? parseColorText(line.content, $themeColors.colorMap) : [];
|
|
$: options = line.radioOptions || [];
|
|
$: isDisabled = line.inputDisabled || false;
|
|
$: isHorizontal = line.radioHorizontal || false;
|
|
|
|
function handleSelect(optionValue: string) {
|
|
if (isDisabled) return;
|
|
value = optionValue;
|
|
dispatch('change', value);
|
|
}
|
|
|
|
function handleKeydown(e: KeyboardEvent, optionValue: string) {
|
|
if (e.key === ' ' || e.key === 'Enter') {
|
|
e.preventDefault();
|
|
handleSelect(optionValue);
|
|
}
|
|
}
|
|
|
|
function getRadioSymbol(selected: boolean): string {
|
|
return selected ? '(●)' : '( )';
|
|
}
|
|
</script>
|
|
|
|
<div
|
|
class="tui-radio-group"
|
|
class:inline={inline}
|
|
class:disabled={isDisabled}
|
|
style="--radio-color: {getButtonStyle(line.style)}"
|
|
role="radiogroup"
|
|
>
|
|
{#if line.content}
|
|
<div class="radio-label">
|
|
{#if line.icon}
|
|
<Icon icon={line.icon} width="14" class="label-icon" />
|
|
{/if}
|
|
{#each labelSegments as segment}
|
|
{#if segment.icon}
|
|
<Icon icon={segment.icon} width="14" class="inline-icon" />
|
|
{:else if getSegmentStyle(segment)}
|
|
<span style={getSegmentStyle(segment)}>{segment.text}</span>
|
|
{:else}
|
|
{segment.text}
|
|
{/if}
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
|
|
<div class="radio-options" class:horizontal={isHorizontal}>
|
|
{#each options as option}
|
|
{@const isSelected = value === option.value}
|
|
{@const optionSegments = parseColorText(option.label, $themeColors.colorMap)}
|
|
<div
|
|
class="radio-option"
|
|
class:selected={isSelected}
|
|
class:option-disabled={option.disabled}
|
|
role="radio"
|
|
aria-checked={isSelected}
|
|
aria-disabled={isDisabled || option.disabled}
|
|
tabindex={isDisabled || option.disabled ? -1 : 0}
|
|
on:click={() => !option.disabled && handleSelect(option.value)}
|
|
on:keydown={(e) => !option.disabled && handleKeydown(e, option.value)}
|
|
>
|
|
<span class="radio-symbol">
|
|
{getRadioSymbol(isSelected)}
|
|
</span>
|
|
{#if option.icon}
|
|
<Icon icon={option.icon} width="14" class="option-icon" />
|
|
{/if}
|
|
<span class="option-label">
|
|
{#each optionSegments as segment}
|
|
{#if segment.icon}
|
|
<Icon icon={segment.icon} width="14" class="inline-icon" />
|
|
{:else if getSegmentStyle(segment)}
|
|
<span style={getSegmentStyle(segment)}>{segment.text}</span>
|
|
{:else}
|
|
{segment.text}
|
|
{/if}
|
|
{/each}
|
|
</span>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.tui-radio-group {
|
|
margin: 0.5rem 0;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.tui-radio-group.inline {
|
|
display: inline-flex;
|
|
flex-direction: column;
|
|
margin: 0 0.75rem 0 0;
|
|
}
|
|
|
|
.tui-radio-group.disabled {
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.radio-label {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.35rem;
|
|
margin-bottom: 0.5rem;
|
|
color: var(--terminal-muted);
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
:global(.label-icon) {
|
|
color: var(--radio-color);
|
|
}
|
|
|
|
.radio-options {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
.radio-options.horizontal {
|
|
flex-direction: row;
|
|
flex-wrap: wrap;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.radio-option {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.35rem 0.5rem;
|
|
cursor: pointer;
|
|
border-radius: 4px;
|
|
transition: all 0.15s ease;
|
|
user-select: none;
|
|
}
|
|
|
|
.radio-option:hover:not(.option-disabled) {
|
|
background: color-mix(in srgb, var(--radio-color) 10%, transparent);
|
|
}
|
|
|
|
.radio-option:focus-visible {
|
|
outline: 1px solid var(--radio-color);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.radio-option.option-disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.radio-symbol {
|
|
font-family: inherit;
|
|
font-weight: bold;
|
|
color: var(--terminal-muted);
|
|
transition: color 0.15s ease;
|
|
}
|
|
|
|
.radio-option.selected .radio-symbol {
|
|
color: var(--radio-color);
|
|
}
|
|
|
|
:global(.option-icon) {
|
|
color: var(--radio-color);
|
|
}
|
|
|
|
.option-label {
|
|
color: var(--terminal-text);
|
|
}
|
|
|
|
.radio-option.option-disabled .option-label {
|
|
color: var(--terminal-muted);
|
|
}
|
|
</style>
|