Repository navigation
feat(sve-ui): add Kbd, ButtonGroup, Empty and InputGroup - #81
Merged
Merged
Conversation
The library finished wrapping all 41 Bits UI primitives, so there are no cheap components left — these four are hand-built, and were picked by diffing the catalog against shadcn-svelte for gaps people fill with a hand-rolled div. - Kbd: a keycap, with `label` so `⌘` is announced as "Command" instead of being skipped as punctuation. `Command` shipped as a palette with no way to draw the shortcut it advertises. - ButtonGroup: attaches independent buttons, collapses the doubled border, lifts the focused child so its ring is not clipped. A name is required because `role="group"` without one names a boundary it cannot describe. - Empty: media always `aria-hidden`, title a `<p>` unless given a level, and `announce` opt-in for emptiness that replaces results after a search. - InputGroup: the group owns the border and focus ring, the input owns neither. Addons are `aria-hidden` by default and cannot be the label. Two findings came out of tightening a guard rather than reading code: The example's coverage guard matched `\bEmpty\b` against `Command.Empty`, so a namespace member counted as proof of a top-level export. A negative lookbehind fixes it — and with it fixed the guard reported the standalone `Calendar` as never rendered, which had been true for months. It is on the booking screen now. A test asserting `Empty.Root` is not a live region by default was asserting its fixture's default instead. Flipping the component default to `true` left it green. The fixture now renders the prop genuinely absent, and the mutation fails it. The `aria-hidden` defaults on Empty.Media and InputGroup.Addon were verified the same way.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
baselines for the four new component pages: kbd, button-group, empty, input-group 308 baselines. Generated by the visual-baselines workflow, which is the only sanctioned path: they are byte-compared and macOS font rasterisation does not match this runner.
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Four components, and the first ones added since the library finished wrapping all 41 of Bits UI's primitives — so there are no cheap components left and all four are hand-built.
They were picked by diffing the catalog against shadcn-svelte rather than by taste. Each is a gap people currently fill with a hand-rolled div, and none needs a new dependency (the package still has exactly one:
bits-ui).KbdCommand, a command palette, with no way to draw the shortcut it advertises.labelis the substance:⌘,⇧,⌥are punctuation to a screen reader and some voices skip them entirely, so a glyph-only shortcut can announce as nothing.ButtonGroupEmptyTable, list and search needs one, and today each consumer writes it again.InputGroupReview path
Start with the three
index.ts/ component headers — they carry the reasoning, and the defaults are the design:Empty/index.ts—Mediaalwaysaria-hidden(the icon restates the title),Titlea<p>not a heading (an empty state in a card must not inject "No results" into the document outline),announceopt-in.InputGroup/index.ts— the group owns the border and:focus-withinring, the input owns neither.Addonisaria-hiddenby default and cannot be the label.ButtonGroup.svelte— why a name is required, and theisolation/z-indexpair.Then
apps/example/scripts/check-coverage.mjs, which is where the two findings below came from.Intentionally out of scope: the rest of the diff —
Chart(needs a charting dependency; the package has one dep and that number is an asset),CarouselandResizable(real, and real work), and a rows-as-dataDataTable, whichTable/index.tsalready refuses in writing.Two findings that came from tightening a guard, not from reading code
1. The example's coverage guard accepted a namespace member as proof of a top-level export.
\bEmpty\bmatchedCommand.Emptyon the browse screen, so the guard reportedEmptyas covered while nothing rendered it. Same shape as thesve-nav-menufalse positive already documented in the roadmap: a word-boundary match cannot tell a qualified name from a bare one. A negative lookbehind fixes it.With it fixed, the guard immediately reported the standalone
Calendaras never rendered — and that was true, for months. OnlyDatePicker.CalendarandRangeCalendar.*were on screen. A calendar inside a picker is a different mount: it opens in a portal, on demand. The bare month grid is now on the booking screen.2. A test of mine was measuring nothing.
Empty > is not a live region unless askedpassedannounce: falsefrom the fixture, so it asserted the fixture's default, not the component's — flipping the component default totrueleft all six tests green. The fixture now renders the prop genuinely absent, and the mutation fails it.Mutation-verified, all three load-bearing defaults:
Changes
packages/sve-ui/src/lib/components/{Kbd,ButtonGroup,Empty,InputGroup}src/lib/index.tssrc/tests/A11yFixture, whichssr.test.tsreuses — so each gets axe and server-render coverageapps/docs/src/routes/components/{kbd,button-group,empty,input-group}apps/docs/src/lib/docs/registry.tsnewpackages/sve-ui/README.mdskills/sve-ui-usage/references/components.mdapps/example/**Calendarapps/example/scripts/check-coverage.mjsapps/docs/render-baseline.jsonOn that
+8 elementsThe render guard reported a uniform
+8on all 67 existing pages, which is the kind of number worth explaining before rubber-stamping.DocsNavrenders each registry item as an<a>plus, forstatus: 'new', a<span>tag — 2 elements × 4 new components = 8, on every page carrying the sidebar. Verified before updating the baseline.Test plan
turbo run test --force— 665 tests across 76 files (was 644/72)pnpm lint && pnpm check && pnpm buildgreen;pnpm format:checkcleancheck-dts189 types,check-css-coverage187 components,check-docs-coverage64/64/64,check-readme-coverage64,publintcleancheck-example-coverage: all 66 exports rendered (was silently 64 of 66)check:render— 71 pages, baseline updated in this commitgen:props:check—props.jsonis 208 pure insertions, 0 deletions, and its 9 new keys match thePropsTable component="…"names on the docs pagesupdateSnapshots: 'none'makes a missing baseline a failure, and they can only be written on Linux. Triggeringvisual-baselineson this branch next, so the PNGs land here as a reviewable diff.Checklist
pnpm test)pnpm lint && pnpm check && pnpm buildall greenminor