remove suggestion badge components

This commit is contained in:
Justin Edmund 2026-01-04 00:13:19 -08:00
parent cbcd6afd88
commit 2b22d539eb
2 changed files with 0 additions and 479 deletions

View file

@ -1,185 +0,0 @@
<svelte:options runes={true} />
<script lang="ts">
import { Tooltip as TooltipBase } from 'bits-ui'
import Icon from '$lib/components/Icon.svelte'
interface Props {
/** The suggested value to display */
suggestion: string | number | boolean | null | undefined
/** Label for the suggestion (e.g., field name) */
label?: string
/** Whether the suggestion has been dismissed */
dismissed?: boolean
/** Callback when user accepts the suggestion */
onAccept?: () => void
/** Callback when user dismisses the suggestion */
onDismiss?: () => void
}
let { suggestion, label, dismissed = false, onAccept, onDismiss }: Props = $props()
// Format the suggestion for display
const displayValue = $derived(() => {
if (suggestion === null || suggestion === undefined) return 'None'
if (typeof suggestion === 'boolean') return suggestion ? 'Yes' : 'No'
return String(suggestion)
})
let isOpen = $state(false)
</script>
{#if suggestion !== undefined && suggestion !== null && !dismissed}
<TooltipBase.Root bind:open={isOpen} delayDuration={0}>
<TooltipBase.Trigger>
{#snippet child({ props })}
<button
{...props}
type="button"
class="suggestion-badge"
aria-label="Wiki suggestion available"
>
<Icon name="sparkles" size={14} />
</button>
{/snippet}
</TooltipBase.Trigger>
<TooltipBase.Content class="suggestion-tooltip" sideOffset={4}>
<div class="suggestion-content">
{#if label}
<span class="suggestion-label">{label}:</span>
{/if}
<span class="suggestion-value">{displayValue()}</span>
</div>
<div class="suggestion-actions">
{#if onAccept}
<button
type="button"
class="action accept"
onclick={() => {
onAccept?.()
isOpen = false
}}
title="Accept suggestion"
>
<Icon name="check" size={14} />
</button>
{/if}
{#if onDismiss}
<button
type="button"
class="action dismiss"
onclick={() => {
onDismiss?.()
isOpen = false
}}
title="Dismiss suggestion"
>
<Icon name="x" size={14} />
</button>
{/if}
</div>
</TooltipBase.Content>
</TooltipBase.Root>
{/if}
<style lang="scss">
@use '$src/themes/layout' as *;
@use '$src/themes/typography' as *;
@use '$src/themes/spacing' as *;
@use '$src/themes/colors' as *;
.suggestion-badge {
display: inline-flex;
align-items: center;
justify-content: center;
width: calc($unit * 2.5);
height: calc($unit * 2.5);
border-radius: 50%;
background: linear-gradient(135deg, $wind-text-20 0%, $water-text-20 100%);
border: none;
cursor: pointer;
padding: 0;
margin-left: $unit-half;
flex-shrink: 0;
transition: transform 0.15s ease, box-shadow 0.15s ease;
&:hover {
transform: scale(1.1);
box-shadow: 0 0 8px rgba($wind-text-20, 0.5);
}
:global(svg) {
color: white;
}
}
:global(.suggestion-tooltip) {
background: var(--tooltip-bg, #2a2a2a);
color: var(--tooltip-text, white);
padding: $unit;
border-radius: $item-corner;
font-size: $font-small;
z-index: 1000;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
gap: $unit;
min-width: 120px;
}
.suggestion-content {
display: flex;
flex-direction: column;
gap: $unit-fourth;
}
.suggestion-label {
font-size: calc($font-small * 0.9);
color: var(--text-secondary);
font-weight: $normal;
}
.suggestion-value {
font-weight: $medium;
color: white;
word-break: break-word;
}
.suggestion-actions {
display: flex;
gap: $unit-half;
justify-content: flex-end;
padding-top: $unit-half;
border-top: 1px solid rgba(white, 0.1);
}
.action {
display: flex;
align-items: center;
justify-content: center;
width: calc($unit * 3.5);
height: calc($unit * 3.5);
border-radius: $item-corner-small;
border: none;
cursor: pointer;
transition: background-color 0.15s ease;
&.accept {
background: $wind-text-20;
color: white;
&:hover {
background: $wind-text-10;
}
}
&.dismiss {
background: $grey-60;
color: white;
&:hover {
background: $grey-50;
}
}
}
</style>

View file

@ -1,294 +0,0 @@
<svelte:options runes={true} />
<script lang="ts">
import type { Snippet } from 'svelte'
import Input from './Input.svelte'
import Select from './Select.svelte'
import Checkbox from './checkbox/Checkbox.svelte'
import DatePicker from './DatePicker.svelte'
import SuggestionBadge from './SuggestionBadge.svelte'
import Icon from '../Icon.svelte'
interface SelectOption {
value: string | number
label: string
disabled?: boolean
}
let {
label,
sublabel,
value = $bindable(),
children,
editable = false,
type = 'text',
options,
placeholder,
element,
onchange,
width,
linkUrl,
hasLinkButton = false,
// Suggestion props
suggestion,
suggestionLabel,
dismissedSuggestion = false,
onAcceptSuggestion,
onDismissSuggestion
}: {
label: string
/** Secondary label displayed below the main label */
sublabel?: string
value?: string | number | boolean | null | undefined
children?: Snippet
editable?: boolean
type?: 'text' | 'number' | 'select' | 'checkbox' | 'date'
options?: SelectOption[]
placeholder?: string
element?: 'wind' | 'fire' | 'water' | 'earth' | 'dark' | 'light'
/** Callback for checkbox type when value changes */
onchange?: (checked: boolean) => void
/** Custom width for the input field (e.g., '320px') */
width?: string
/** URL to open when link button is clicked */
linkUrl?: string | null
/** Whether to show the link button (disabled when linkUrl is empty) */
hasLinkButton?: boolean
// Suggestion props
/** The suggested value from wiki */
suggestion?: string | number | boolean | null | undefined
/** Label for the suggestion tooltip */
suggestionLabel?: string
/** Whether the suggestion has been dismissed */
dismissedSuggestion?: boolean
/** Callback when user accepts the suggestion */
onAcceptSuggestion?: () => void
/** Callback when user dismisses the suggestion */
onDismissSuggestion?: () => void
} = $props()
// For checkbox type, derive the checked state from value
const checkboxValue = $derived(type === 'checkbox' ? Boolean(value) : false)
// Show link button when hasLinkButton is true or linkUrl is provided
const showLinkButton = $derived(hasLinkButton || !!linkUrl)
const linkDisabled = $derived(!linkUrl)
// Handle checkbox change and call onchange if provided
function handleCheckboxChange(checked: boolean) {
value = checked as any
onchange?.(checked)
}
// Open URL in new tab
function openLink() {
if (linkUrl) {
window.open(linkUrl, '_blank', 'noopener,noreferrer')
}
}
// Show suggestion badge when:
// 1. We have a suggestion
// 2. The suggestion hasn't been dismissed
// 3. The current value is different from the suggestion (or value is empty)
const showSuggestion = $derived(
suggestion !== undefined &&
suggestion !== null &&
!dismissedSuggestion &&
(value === undefined || value === null || value === '' || value !== suggestion)
)
// Format suggestion for display in badge
const formattedSuggestion = $derived(() => {
if (type === 'select' && options && suggestion !== undefined && suggestion !== null) {
const opt = options.find((o) => o.value === suggestion)
return opt?.label ?? suggestion
}
return suggestion
})
</script>
<div class="detail-item" class:editable class:hasChildren={!!children}>
<div class="label-container">
<div class="label-row">
<span class="label">{label}</span>
{#if editable && showSuggestion}
<SuggestionBadge
suggestion={formattedSuggestion()}
label={suggestionLabel || label}
dismissed={dismissedSuggestion}
onAccept={onAcceptSuggestion}
onDismiss={onDismissSuggestion}
/>
{/if}
</div>
{#if sublabel}
<span class="sublabel">{sublabel}</span>
{/if}
</div>
{#if editable}
<div class="edit-value" style:--custom-width={width}>
{#if type === 'select' && options}
<Select
bind:value={value as string | number | undefined}
{options}
{placeholder}
size="medium"
contained
/>
{:else if type === 'checkbox'}
<Checkbox
checked={checkboxValue}
onCheckedChange={handleCheckboxChange}
contained
{element}
/>
{:else if type === 'number'}
<Input
bind:value
type="number"
variant="number"
contained={true}
{placeholder}
alignRight={true}
/>
{:else if type === 'date'}
<DatePicker bind:value={value as string | null} contained={true} {placeholder} />
{:else}
<Input bind:value type="text" contained={true} {placeholder} alignRight={false} />
{#if showLinkButton}
<button
type="button"
class="link-button"
onclick={openLink}
disabled={linkDisabled}
title={linkDisabled ? 'No link available' : 'Open link'}
>
<Icon name="link" size={16} />
</button>
{/if}
{/if}
</div>
{:else if children}
<div class="value" class:edit-value={editable}>
{@render children()}
</div>
{:else}
<span class="value">{value || '—'}</span>
{/if}
</div>
<style lang="scss">
@use '$src/themes/colors' as colors;
@use '$src/themes/layout' as layout;
@use '$src/themes/spacing' as spacing;
@use '$src/themes/typography' as typography;
@use '$src/themes/effects' as effects;
.detail-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: spacing.$unit 0;
border-radius: layout.$item-corner;
font-size: typography.$font-regular;
min-height: calc(spacing.$unit * 5);
&:not(.editable) {
padding: spacing.$unit;
margin: 0 calc(spacing.$unit * -1);
}
&:hover:not(.editable):not(.hasChildren) {
background: colors.$grey-90;
}
&.editable:focus-within,
&.hasChildren:focus-within {
background: var(--input-bg-hover);
}
&.editable,
&.hasChildren {
background: var(--input-bg);
}
.label-container {
display: flex;
flex-direction: column;
flex-shrink: 0;
margin-right: spacing.$unit-2x;
gap: spacing.$unit-fourth;
}
.label-row {
display: flex;
align-items: center;
}
.label {
font-weight: typography.$medium;
color: var(--text-secondary);
}
.sublabel {
font-size: typography.$font-small;
color: var(--text-tertiary);
font-weight: typography.$normal;
}
.value {
color: colors.$grey-30;
display: flex;
align-items: center;
}
.edit-value {
flex: 1;
display: flex;
flex-grow: 0;
justify-content: flex-end;
align-items: center;
gap: spacing.$unit-half;
:global(.input),
:global(.select) {
width: var(--custom-width, 240px);
}
:global(.input.number) {
width: var(--custom-width, 120px);
}
.link-button {
display: flex;
align-items: center;
justify-content: center;
width: 42px;
height: 42px;
padding: 0;
border: none;
border-radius: layout.$item-corner;
background: transparent;
color: var(--text-secondary);
cursor: pointer;
flex-shrink: 0;
@include effects.smooth-transition(effects.$duration-quick, background-color, color, opacity);
&:hover:not(:disabled) {
background: colors.$grey-90;
color: colors.$grey-30;
}
&:active:not(:disabled) {
background: colors.$grey-80;
}
&:disabled {
cursor: not-allowed;
opacity: 0.4;
}
}
}
}
</style>