feat(app-form): icon picker, tag/category autocomplete, typography
- Replace AppIconPicker text input with visual IconPickerButton for lucide icons (grid with search) - Add AutocompleteInput component for category field with existing category suggestions - Add TagsInput component for tags field with tag pills, autocomplete from existing tags, and keyboard navigation - Add GET /api/apps/suggestions endpoint returning all categories/tags - Add getAllTags() to appService (merges Tag model + comma-separated) - Install @tailwindcss/typography plugin to fix prose rendering (headings, lists, blockquotes now render in Note/Markdown widgets) - Fix note widget validator test for new html format
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { t } from 'svelte-i18n';
|
||||
import IconGrid from '$lib/components/ui/IconGrid.svelte';
|
||||
import IconPickerButton from '$lib/components/ui/IconPickerButton.svelte';
|
||||
import DynamicIcon from '$lib/components/ui/DynamicIcon.svelte';
|
||||
import type { IconGridItem } from '$lib/components/ui/IconGrid.svelte';
|
||||
|
||||
interface Props {
|
||||
@@ -29,6 +31,11 @@
|
||||
iconValue = target.value;
|
||||
onchange?.(iconType, iconValue);
|
||||
}
|
||||
|
||||
function handleIconPickerChange(value: string) {
|
||||
iconValue = value;
|
||||
onchange?.(iconType, iconValue);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="space-y-2">
|
||||
@@ -44,22 +51,36 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
value={iconValue}
|
||||
oninput={handleValueChange}
|
||||
placeholder={iconType === 'lucide'
|
||||
? $t('app.icon_lucide_placeholder')
|
||||
: iconType === 'simple'
|
||||
{#if iconType === 'lucide'}
|
||||
<div class="flex flex-1 items-start">
|
||||
<IconPickerButton
|
||||
value={iconValue}
|
||||
onchange={handleIconPickerChange}
|
||||
placeholder={$t('app.icon_lucide_placeholder') ?? 'Select icon'}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<input
|
||||
type="text"
|
||||
value={iconValue}
|
||||
oninput={handleValueChange}
|
||||
placeholder={iconType === 'simple'
|
||||
? $t('app.icon_simple_placeholder')
|
||||
: iconType === 'url'
|
||||
? $t('app.icon_url_placeholder')
|
||||
: $t('app.icon_emoji_placeholder')}
|
||||
class="flex-1 rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
class="flex-1 rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if iconType === 'emoji' && iconValue}
|
||||
<!-- Preview -->
|
||||
{#if iconType === 'lucide' && iconValue}
|
||||
<div class="flex items-center gap-2">
|
||||
<DynamicIcon name={iconValue} size={24} />
|
||||
<span class="text-xs text-muted-foreground">{iconValue}</span>
|
||||
</div>
|
||||
{:else if iconType === 'emoji' && iconValue}
|
||||
<div class="text-2xl">{iconValue}</div>
|
||||
{:else if iconType === 'url' && iconValue}
|
||||
<img src={iconValue} alt={$t('app.icon_preview')} class="h-8 w-8 rounded object-contain" />
|
||||
|
||||
Reference in New Issue
Block a user