From 53f52441535a862e4e5bb59ccb202c06cf7b4518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Kuna?= <1282324+codecalm@users.noreply.github.com> Date: Wed, 2 Sep 2026 23:42:00 +0200 Subject: [PATCH] Type the illustration name in Illustration and Empty (#2944) --- .changeset/typed-illustration-names.md | 6 ++++++ shared/layouts/ErrorLayout.astro | 3 ++- shared/lib/svg.test.ts | 12 +++++++++++- shared/lib/svg.ts | 12 ++++++++++++ shared/ui/Empty.astro | 3 ++- shared/ui/Illustration.astro | 8 +++----- 6 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 .changeset/typed-illustration-names.md diff --git a/.changeset/typed-illustration-names.md b/.changeset/typed-illustration-names.md new file mode 100644 index 000000000..f2d04116e --- /dev/null +++ b/.changeset/typed-illustration-names.md @@ -0,0 +1,6 @@ +--- +"@tabler/preview": patch +"@tabler/docs": patch +--- + +Updated the `Illustration` and `Empty` components to accept only the bundled illustration names, checked at build time. diff --git a/shared/layouts/ErrorLayout.astro b/shared/layouts/ErrorLayout.astro index dcde68a94..e4971c734 100644 --- a/shared/layouts/ErrorLayout.astro +++ b/shared/layouts/ErrorLayout.astro @@ -3,6 +3,7 @@ import BaseLayout from './BaseLayout.astro' import Empty from '@ui/Empty.astro' import errors from '@data/errors.json' +import type { IllustrationImage } from '@shared/lib/svg' interface Props { title?: string @@ -13,7 +14,7 @@ interface Props { const { title, pageError } = Astro.props type ErrorEntry = { - illustration?: string + illustration?: IllustrationImage title?: string | number header?: string description?: string diff --git a/shared/lib/svg.test.ts b/shared/lib/svg.test.ts index 8361a29dc..c23a5c279 100644 --- a/shared/lib/svg.test.ts +++ b/shared/lib/svg.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { iconSvg, iconSourceComment, illustrationSvg } from './svg' +import { freeIllustrationSource, iconSvg, iconSourceComment, illustrationSvg } from './svg' describe('iconSvg', () => { it('returns the processed outline svg with a11y attributes and the given classes', () => { @@ -34,6 +34,16 @@ describe('iconSourceComment', () => { }) }) +describe('freeIllustrationSource', () => { + it('returns the same svg with and without the ".svg" suffix', () => { + expect(freeIllustrationSource('boy-girl.svg')).toBe(freeIllustrationSource('boy-girl')) + }) + + it('returns the auto-dark source of a bundled illustration', () => { + expect(freeIllustrationSource('not-found')).toContain(' { const source = '' diff --git a/shared/lib/svg.ts b/shared/lib/svg.ts index af70038ae..9a139ffd4 100644 --- a/shared/lib/svg.ts +++ b/shared/lib/svg.ts @@ -1,6 +1,18 @@ // Inline-SVG preparation helpers: filler-path strip, a11y attribute/class swap // and the illustration attribute rewrites. import icons from '../data/icons.json' +import freeIllustrations from '../data/free-illustrations.json' + +/** Names of the illustrations bundled in @data/free-illustrations.json. */ +export type FreeIllustration = keyof typeof freeIllustrations.autodark + +/** How an illustration is named in markup — with or without the ".svg" suffix. */ +export type IllustrationImage = FreeIllustration | `${FreeIllustration}.svg` + +/** Source SVG of a bundled illustration, in its auto-dark variant. */ +export function freeIllustrationSource(image: IllustrationImage): string { + return freeIllustrations.autodark[image.replaceAll('.svg', '') as FreeIllustration] ?? '' +} type IconRecord = { svg: Record } diff --git a/shared/ui/Empty.astro b/shared/ui/Empty.astro index 96b9efcd8..552123259 100644 --- a/shared/ui/Empty.astro +++ b/shared/ui/Empty.astro @@ -2,6 +2,7 @@ import Icon from './Icon.astro' import Button from './Button.astro' import Illustration from './Illustration.astro' +import type { IllustrationImage } from '@shared/lib/svg' type PolymorphicProps = { as?: 'p' | 'h1' | 'h2' } @@ -10,7 +11,7 @@ interface Props extends PolymorphicProps { height?: number | string class?: string /** illustration file name, e.g. "computer-fix.svg" */ - illustration?: string | undefined + illustration?: IllustrationImage | undefined /** big text rendered instead of an icon/illustration */ iconText?: string | undefined title?: string diff --git a/shared/ui/Illustration.astro b/shared/ui/Illustration.astro index dd5c9d2d3..201b876b0 100644 --- a/shared/ui/Illustration.astro +++ b/shared/ui/Illustration.astro @@ -1,18 +1,16 @@ --- -import freeIllustrations from '@data/free-illustrations.json' -import { illustrationSvg } from '@shared/lib/svg' +import { freeIllustrationSource, illustrationSvg, type IllustrationImage } from '@shared/lib/svg' interface Props { /** file name of the illustration, e.g. "boy-girl.svg" (the ".svg" suffix is stripped) */ - image: string + image: IllustrationImage height?: number | string class?: string } const { image, height = 128, class: className } = Astro.props -const key = image.replaceAll('.svg', '') -const source = (freeIllustrations as { autodark: Record }).autodark[key] ?? '' +const source = freeIllustrationSource(image) const illustration = illustrationSvg(source, { ...(className ? { classes: className } : {}), height }) ---