Phase 3 audit found that both LumotiaStatusPill and LumotiaNotice
already shipped in v0.2 Phase 5. Phase 3 reduced to an audit-and-adapt
pass.
LumotiaStatusPill ($lib/components/StatusPill.svelte) is already
correct for quietware. Pattern: neutral pill background + neutral
label text + role colour confined to a 6x6 px dot. Colour is
supplementary, label is always perceivable. Works unchanged across
all three quietware modes. No changes shipped.
LumotiaNotice ($lib/ui/LumotiaNotice.svelte) refactored to the
left-bar accent pattern. Previously the role colour rendered on the
icon outline and on the entire surround border at 30% opacity; in
quietware light mode that left the bright #FFCD00 caution colour
faded against cream paper. New pattern:
- 4-px solid LEFT border in the tone colour, 100% opacity.
- Soft tone-tinted background (10% opacity) for ambient cue.
- Subtle neutral outer border on the other three sides.
- Icon, title and body text all use --color-text.
Visible signal lives on the large bar (works at any contrast level);
foreground text uses the always-legible neutral. Matches Material
Design "banner with leading accent" and IBM Carbon "inline
notification" guidance. ARIA semantics (role="alert" for danger,
role="status" for the rest) preserved.
Design-system-v2 preview route already imports both primitives. The
updated Notice renders correctly with the existing preview content;
no additional wiring required.
Caution remains a fill-only convention on cream by design. Any
darkened yellow reads as olive ("looks like poop", per Jake), so the
fill-only rule stays in force and this pattern delivers it cleanly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
97 lines
3.0 KiB
Svelte
97 lines
3.0 KiB
Svelte
<script lang="ts">
|
|
// v0.2 Phase 5 primitive — inline notice block. Replaces the ~10
|
|
// ad-hoc <div class="bg-{danger,warning,success}/10 …"> patterns
|
|
// scattered across pages. Four tones map to the semantic tokens.
|
|
//
|
|
// v0.3 Phase 3 update — left-bar accent pattern. The visible status
|
|
// signal now lives on a 4-px solid left border in the tone colour
|
|
// (100% opacity, fill-grade chroma); the surrounding container uses
|
|
// a soft tone-tinted background and a subtle outer border. Icon,
|
|
// title and body text all use --color-text so they remain legible
|
|
// across every theme combination, including quietware light mode
|
|
// where --color-caution is the bright #FFCD00 fill (would fail as
|
|
// text on cream). Same accessibility role semantics retained.
|
|
//
|
|
// Pattern matches Material Design "banner with leading accent" and
|
|
// IBM Carbon "inline notification" guidance: status colour on a
|
|
// large fill surface (the bar), neutral foreground text. Works for
|
|
// any tone in any theme regardless of WCAG-text contrast on the
|
|
// role colour itself.
|
|
import { Info, AlertTriangle, AlertOctagon, CheckCircle, X } from "lucide-svelte";
|
|
import type { Snippet } from "svelte";
|
|
|
|
type Tone = "info" | "caution" | "danger" | "success";
|
|
|
|
interface Props {
|
|
tone?: Tone;
|
|
title?: string;
|
|
dismissible?: boolean;
|
|
onDismiss?: () => void;
|
|
children?: Snippet;
|
|
classes?: string;
|
|
}
|
|
|
|
let {
|
|
tone = "info" as Tone,
|
|
title,
|
|
dismissible = false,
|
|
onDismiss,
|
|
children,
|
|
classes = "",
|
|
}: Props = $props();
|
|
|
|
const palette: Record<Tone, { bar: string; bg: string; icon: typeof Info }> = {
|
|
info: {
|
|
bar: "border-l-info",
|
|
bg: "bg-info/10",
|
|
icon: Info,
|
|
},
|
|
caution: {
|
|
bar: "border-l-caution",
|
|
bg: "bg-caution/10",
|
|
icon: AlertTriangle,
|
|
},
|
|
danger: {
|
|
bar: "border-l-danger",
|
|
bg: "bg-danger/10",
|
|
icon: AlertOctagon,
|
|
},
|
|
success: {
|
|
bar: "border-l-success",
|
|
bg: "bg-success/10",
|
|
icon: CheckCircle,
|
|
},
|
|
};
|
|
|
|
// Danger gets role="alert" so screen readers announce immediately;
|
|
// the others use the less-interrupting role="status".
|
|
const ariaRole = $derived(tone === "danger" ? "alert" : "status");
|
|
const tonePal = $derived(palette[tone]);
|
|
const ToneIcon = $derived(tonePal.icon);
|
|
</script>
|
|
|
|
<div
|
|
role={ariaRole}
|
|
class="px-4 py-3 rounded-r-lg rounded-l-sm border border-l-4
|
|
border-border-subtle text-[13px] flex items-start gap-2.5
|
|
{tonePal.bar} {tonePal.bg} {classes}"
|
|
>
|
|
<ToneIcon size={16} class="mt-0.5 shrink-0 text-text" aria-hidden="true" />
|
|
<div class="flex-1 min-w-0 leading-relaxed text-text">
|
|
{#if title}
|
|
<p class="font-semibold text-text mb-1">{title}</p>
|
|
{/if}
|
|
{#if children}{@render children()}{/if}
|
|
</div>
|
|
{#if dismissible}
|
|
<button
|
|
type="button"
|
|
class="shrink-0 text-text-tertiary hover:text-text size-5 flex items-center justify-center rounded"
|
|
aria-label="Dismiss notice"
|
|
onclick={onDismiss}
|
|
>
|
|
<X size={14} />
|
|
</button>
|
|
{/if}
|
|
</div>
|