Badges: live preview while typing, accordion entrance animation, register tailwindcss-animate
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
@import 'tailwindcss';
|
||||
@plugin 'tailwindcss-animate';
|
||||
|
||||
/* Enable class-based dark mode for Tailwind v4.
|
||||
Without this, Tailwind v4 defaults to @media (prefers-color-scheme: dark),
|
||||
|
||||
@@ -32,6 +32,12 @@
|
||||
font_size_affiliations?: number | null;
|
||||
/** Optional px override for location font size. */
|
||||
font_size_location?: number | null;
|
||||
/**
|
||||
* Real-time preview overrides from the controls panel.
|
||||
* Merged on top of lq__event_badge_obj so the badge renders typed
|
||||
* values immediately without waiting for Save + liveQuery round-trip.
|
||||
*/
|
||||
preview_overrides?: Record<string, any> | null;
|
||||
log_lvl?: number;
|
||||
}
|
||||
|
||||
@@ -45,6 +51,7 @@
|
||||
font_size_title,
|
||||
font_size_affiliations,
|
||||
font_size_location,
|
||||
preview_overrides = null,
|
||||
}: Props = $props();
|
||||
|
||||
// Badge layout CSS — compiled in, hot-reloads in dev.
|
||||
@@ -73,25 +80,35 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Effective badge object: merge liveQuery data with preview_overrides when a
|
||||
// field is being edited in the controls panel. Enables real-time preview while
|
||||
// the user types — no save required. preview_overrides is null when no field
|
||||
// is open, so the spread is skipped and this is a zero-cost identity.
|
||||
let eff_badge = $derived(
|
||||
(preview_overrides && $lq__event_badge_obj)
|
||||
? { ...$lq__event_badge_obj, ...preview_overrides }
|
||||
: $lq__event_badge_obj
|
||||
);
|
||||
|
||||
// --- Effective display values (override ?? base) ---
|
||||
// $derived keeps these reactive to liveQuery updates from the right panel saves.
|
||||
// Use eff_badge (not lq__event_badge_obj) so preview changes reflect immediately.
|
||||
let display_name = $derived(
|
||||
$lq__event_badge_obj?.full_name_override ?? $lq__event_badge_obj?.full_name ?? ''
|
||||
eff_badge?.full_name_override ?? eff_badge?.full_name ?? ''
|
||||
);
|
||||
let display_title = $derived(
|
||||
$lq__event_badge_obj?.professional_title_override ?? $lq__event_badge_obj?.professional_title ?? ''
|
||||
eff_badge?.professional_title_override ?? eff_badge?.professional_title ?? ''
|
||||
);
|
||||
let display_affiliations = $derived(
|
||||
$lq__event_badge_obj?.affiliations_override ?? $lq__event_badge_obj?.affiliations ?? ''
|
||||
eff_badge?.affiliations_override ?? eff_badge?.affiliations ?? ''
|
||||
);
|
||||
let display_location = $derived(
|
||||
$lq__event_badge_obj?.location_override ?? $lq__event_badge_obj?.location ?? ''
|
||||
eff_badge?.location_override ?? eff_badge?.location ?? ''
|
||||
);
|
||||
|
||||
// Effective badge type code — CSS class hook for per-event stylesheets.
|
||||
// Priority: badge_type_code_override → badge_type_code
|
||||
let effective_badge_type_code = $derived(
|
||||
$lq__event_badge_obj?.badge_type_code_override ?? $lq__event_badge_obj?.badge_type_code ?? ''
|
||||
eff_badge?.badge_type_code_override ?? eff_badge?.badge_type_code ?? ''
|
||||
);
|
||||
|
||||
// Human-readable badge type name — printed on the badge footer.
|
||||
@@ -101,9 +118,9 @@
|
||||
// A special-case attendee can share a code (and CSS styling) with "Member" but have a
|
||||
// custom displayed name like "Life Member" stored in badge_type_override.
|
||||
let badge_type_name = $derived.by(() => {
|
||||
const override_name = $lq__event_badge_obj?.badge_type_override;
|
||||
const override_name = eff_badge?.badge_type_override;
|
||||
if (override_name) return override_name;
|
||||
const base_name = $lq__event_badge_obj?.badge_type;
|
||||
const base_name = eff_badge?.badge_type;
|
||||
if (base_name) return base_name;
|
||||
const found = (badge_type_code_li as { code: string; name: string }[])
|
||||
.find(item => item.code === effective_badge_type_code);
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
* Font sizes flow back to the parent via $bindable() props so the badge render
|
||||
* (ae_comp__badge_obj_view.svelte) stays in sync without prop-drilling through
|
||||
* a third component.
|
||||
*
|
||||
* preview_overrides: merged on top of the saved badge object before passing to
|
||||
* the badge render — gives real-time preview as the user types without saving.
|
||||
*/
|
||||
|
||||
import type { key_val } from '$lib/stores/ae_stores';
|
||||
@@ -34,6 +37,12 @@
|
||||
font_size_affiliations?: number | null;
|
||||
/** Font size for location (px). null = auto. */
|
||||
font_size_location?: number | null;
|
||||
/**
|
||||
* Real-time preview overrides. Set by this component while a field is open;
|
||||
* the parent passes it straight to the badge render so the badge updates live
|
||||
* as the user types — no save required.
|
||||
*/
|
||||
preview_overrides?: Record<string, any> | null;
|
||||
log_lvl?: number;
|
||||
}
|
||||
|
||||
@@ -46,6 +55,7 @@
|
||||
font_size_title = $bindable(null),
|
||||
font_size_affiliations = $bindable(null),
|
||||
font_size_location = $bindable(null),
|
||||
preview_overrides = $bindable(null),
|
||||
log_lvl = 0
|
||||
}: Props = $props();
|
||||
|
||||
@@ -224,6 +234,46 @@
|
||||
return $lq__event_badge_obj?.[override_key] ?? $lq__event_badge_obj?.[base_key] ?? '';
|
||||
}
|
||||
|
||||
// Real-time preview: keep preview_overrides in sync with whatever field is currently
|
||||
// open and being edited. The badge render merges this on top of the saved badge object
|
||||
// so changes show up immediately as the user types. Clears when no field is open.
|
||||
$effect(() => {
|
||||
if (!active_field) {
|
||||
preview_overrides = null;
|
||||
return;
|
||||
}
|
||||
switch (active_field) {
|
||||
case 'name':
|
||||
preview_overrides = { full_name_override: edit_full_name_override || null };
|
||||
break;
|
||||
case 'title':
|
||||
preview_overrides = { professional_title_override: edit_professional_title_override || null };
|
||||
break;
|
||||
case 'affiliations':
|
||||
preview_overrides = { affiliations_override: edit_affiliations_override || null };
|
||||
break;
|
||||
case 'location':
|
||||
preview_overrides = { location_override: edit_location_override || null };
|
||||
break;
|
||||
case 'pronouns':
|
||||
preview_overrides = { pronouns_override: edit_pronouns_override || null };
|
||||
break;
|
||||
case 'allow_tracking':
|
||||
preview_overrides = { allow_tracking: edit_allow_tracking };
|
||||
break;
|
||||
case 'badge_type':
|
||||
preview_overrides = {
|
||||
badge_type_code_override: edit_badge_type_code,
|
||||
badge_type_override: edit_badge_type_code
|
||||
? (badge_type_code_li.find(item => item.code === edit_badge_type_code)?.name ?? edit_badge_type_code)
|
||||
: null
|
||||
};
|
||||
break;
|
||||
default:
|
||||
preview_overrides = null;
|
||||
}
|
||||
});
|
||||
|
||||
// Effective badge type display name: priority matches ae_comp__badge_obj_view.svelte
|
||||
let badge_type_display = $derived(
|
||||
$lq__event_badge_obj?.badge_type_override
|
||||
@@ -770,4 +820,24 @@
|
||||
.ctrl-accordion-inner {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ---- Entrance animation for form content ----
|
||||
Triggered each time .open is applied to the parent accordion.
|
||||
Pairs with the height animation: content fades + zooms in from
|
||||
slightly above while the card expands downward smoothly.
|
||||
Similar in effect to tailwindcss-animate `animate-in zoom-in-95 fade-in-0
|
||||
slide-in-from-top-2`. Uses scoped @keyframes so no plugin needed. */
|
||||
.ctrl-accordion.open .ctrl-accordion-inner > div {
|
||||
animation: field-form-enter 200ms ease-out both;
|
||||
}
|
||||
@keyframes field-form-enter {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.97) translateY(-5px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -102,6 +102,11 @@
|
||||
let font_size_title: number | null = $state(null);
|
||||
let font_size_affiliations: number | null = $state(null);
|
||||
let font_size_location: number | null = $state(null);
|
||||
|
||||
// Real-time preview: the controls panel sets this to the current edit field
|
||||
// value on every keystroke. The badge render merges it over the saved badge
|
||||
// object so the user can see changes as they type, before saving.
|
||||
let preview_overrides: Record<string, any> | null = $state(null);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -243,6 +248,7 @@
|
||||
On print: pr-0 restores full width, controls panel is hidden. -->
|
||||
<div class="print:pr-0 pr-64">
|
||||
<!-- null → Element_fit_text auto-scales; a number → manual size override from controls. -->
|
||||
<!-- preview_overrides: non-null while a field card is open in controls — gives live preview. -->
|
||||
<Comp_badge_obj_view_v2
|
||||
event_id={$lq__event_badge_obj.event_id as string}
|
||||
event_badge_id={event_badge_id as string}
|
||||
@@ -253,6 +259,7 @@
|
||||
font_size_title={font_size_title}
|
||||
font_size_affiliations={font_size_affiliations}
|
||||
font_size_location={font_size_location}
|
||||
{preview_overrides}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -271,6 +278,7 @@
|
||||
bind:font_size_title
|
||||
bind:font_size_affiliations
|
||||
bind:font_size_location
|
||||
bind:preview_overrides
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user