Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5cc87e5
feat(dock) Update documentation to dynamically display the npm versio…
NikolaiKushner Aug 3, 2026
dff5d29
feat(tokens): adopt shadcn/ui radius scale for consistent corner radi…
NikolaiKushner Aug 3, 2026
7858dc7
feat(tokens): update color tokens to align with shadcn/ui palette
NikolaiKushner Aug 4, 2026
8a09d9d
feat(tokens): align color tokens with shadcn/ui design language
NikolaiKushner Aug 4, 2026
90d8981
feat(dialog): enhance overlay with backdrop blur and focus trap funct…
NikolaiKushner Aug 4, 2026
9910cb4
feat(ui): adopt shadcn/ui design for Button, Badge, and Skeleton comp…
NikolaiKushner Aug 4, 2026
74dff95
feat(table): restyle DataTable components to align with shadcn/ui design
NikolaiKushner Aug 4, 2026
4b4b436
feat(tokens): rename semantic tokens to align with shadcn/ui conventions
NikolaiKushner Aug 4, 2026
e3156ff
refactor(tokens, ui): standardize terminology and align with referenc…
NikolaiKushner Aug 4, 2026
bf2cf84
refactor(pagination): rename TablePagination to Pagination and update…
NikolaiKushner Aug 4, 2026
5f65c14
refactor(pagination): change versioning from major to minor for Table…
NikolaiKushner Aug 4, 2026
c37d997
feat(dataTable): implement sticky header with inset shadow for improv…
NikolaiKushner Aug 4, 2026
b739a51
fix(dataTable): unify row heights and remove redundant header rule
NikolaiKushner Aug 4, 2026
50cb77a
feat(button): introduce new size and icon props for enhanced button c…
NikolaiKushner Aug 5, 2026
6d34b16
fix(docs): resolve zebra striping issue in demo rows and restore laye…
NikolaiKushner Aug 6, 2026
4e51123
feat(tokens, ui): refine color tokens and unify component styles for …
NikolaiKushner Aug 6, 2026
69e7477
refactor(ui, tokens): enhance badge and button styles for improved co…
NikolaiKushner Aug 6, 2026
8d9767d
chore(docs): restructure roadmap and remove outdated phase specificat…
NikolaiKushner Aug 6, 2026
164ca88
chore: remove outdated changeset files and clean up documentation
NikolaiKushner Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/quiet-saas-restyle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@rowkit/tokens': minor
'rowkit': minor
---

Restyle rowkit on a shadcn/ui-derived language, then tune it for data-dense SaaS — cool chrome, indigo primary, one control geometry.

**Tokens (breaking if you override theme variables or write rowkit utility classes by hand).** Seven core semantics rename to shadcn’s names: `surface` → `card`, `surface-subtle` → `muted`, `surface-hover` → `accent`, `text` → `foreground`, `text-muted` → `muted-foreground`, `border-control` → `input`, `focus-ring` → `ring`. The greys start from shadcn’s zero-chroma ramp, then pick up rowkit identity: cooler, lighter decorative borders, a cool off-white page, brand indigo primary with a matching focus ring (not near-black), and selected rows on a quiet primary wash. Corners derive from a single `--radius`. New: overlay blur, sticky-header inset shadow, stronger sticky-column scroll shadow. Status families keep the solid/subtle/outline axis Badge and Button already expose.

**Components.** The shared focus recipe (border + translucent ring) lands on every control. Button, Input and Select share height, radius, padding and `text-sm` from `sm` up; Button adds `xs` and `icon`. Secondary is a muted fill so it never reads as another field; fields stay the outlined hollow shell. Chromatic Badge `subtle` is a soft tinted chip. Tooltip inverts foreground/background instead of painting as a primary bubble. DataTable: opaque sticky header with an inset edge that travels while scrolling, unified loaded/loading row heights, quieter hover vs selection. Dialog: blurred scrim, denser padding, footer rule, close matches an icon button. FilterBar, Field, Toast, EmptyState and Pagination follow the same chrome. Docs demos stop inheriting VitePress’s unlayered table grid and zebra over DataTable.

**API (0.x breaking).** `TablePagination` is now `Pagination` — same props, events and slots; docs move to `/components/pagination`. Marked `minor` on purpose: on a 0.x line changesets would turn a `major` into `1.0.0`, and 1.0 should wait for real apps, not a rename.
2 changes: 1 addition & 1 deletion .storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const preview: Preview = {
document.documentElement.classList.toggle('dark', theme === 'dark')
return {
components: { story },
template: `<div class="bg-background text-text p-6"><story /></div>`,
template: `<div class="bg-background text-foreground p-6"><story /></div>`,
}
},
],
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,5 @@ pnpm docs:dev # documentation site
## License

MIT © Nikolai Kushner

Design language based on [shadcn/ui](https://ui.shadcn.com) by shadcn, adapted for Vue. shadcn/ui is MIT licensed; rowkit adopts its token values and class recipes, not its code.
24 changes: 3 additions & 21 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Every component is labeled with its current stage. Nothing is marked Stable unti
### Data layer

- [x] 🟢 **DataTable** — typed column defs, sorting, row selection, sticky header
- [x] 🟢 **TablePagination** — page size, jump-to-page, total count
- [x] 🟢 **Pagination** — page size, jump-to-page, total count
- [x] 🟢 **FilterBar** — composable filter chips with applied-state display
- [x] 🟢 **EmptyState** — the screen every dashboard needs and nobody designs
- [x] 🟢 **Skeleton** — loading placeholders matched to the data components
Expand All @@ -55,26 +55,8 @@ Every component is labeled with its current stage. Nothing is marked Stable unti

## Build phases

- [x] **Phase 0 — Foundation.** Monorepo, Vite library mode, TypeScript strict, ESLint, Vitest, CI.
*Done when:* a throwaway component builds, emits correct `.d.ts`, and imports with working types in the playground.

- [x] **Phase 1 — Token system.** Color scales, semantic mappings, spacing, typography, radii, shadows, z-index, motion. Dark mode. Standalone `@rowkit/tokens` package.
*Done when:* dark mode toggles with zero hardcoded colors anywhere, and `import { tokens } from '@rowkit/tokens'` is fully typed.

- [x] **Phase 2 — Core components.** The four foundations, one at a time, each fully complete before the next.
*Done when:* all four at Stable, playground renders a working form using only rowkit components.

- [x] **Phase 3 — Data layer.** The five data components. DataTable is the centerpiece.
*Done when:* playground has a working "users admin" page — filterable, sortable, paginated, with loading and empty states.

- [x] **Phase 4 — Overlays.** Dialog, Toast, Tooltip. SSR-safe.
*Done when:* all three keyboard-navigable, focus-managed, no hydration errors in the Nuxt playground.

- [ ] **Phase 5 — Documentation site.** VitePress on rowkit.dev. Foundations, components, patterns, contributing.
*Done when:* every Stable component documented, Storybook deployed and linked.

- [ ] **Phase 6 — Ship.** Changesets, trusted publishing via GitHub Actions, `v0.1.0` on npm.
*Done when:* `npm i rowkit` works in a fresh project, following only the docs.
Planning docs live in `docs/phases/` when they exist. The previous phase specs
were retired; new ones will replace them.

---

Expand Down
4 changes: 2 additions & 2 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default defineConfig({
lang: 'en-GB',
cleanUrls: true,

// The internal planning specs are not product documentation.
// Internal planning specs (`docs/phases/`) stay off the published site.
srcExclude: ['phases/**'],

sitemap: { hostname: 'https://rowkit.dev' },
Expand Down Expand Up @@ -91,7 +91,7 @@ export default defineConfig({
text: 'Data',
items: [
{ text: 'DataTable', link: '/components/data-table' },
{ text: 'TablePagination', link: '/components/table-pagination' },
{ text: 'Pagination', link: '/components/pagination' },
{ text: 'FilterBar', link: '/components/filter-bar' },
{ text: 'EmptyState', link: '/components/empty-state' },
{ text: 'Skeleton', link: '/components/skeleton' },
Expand Down
6 changes: 3 additions & 3 deletions docs/.vitepress/theme/components/ColorScale.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,21 @@ const { copied, copy } = useCopyToken()

<template>
<div class="my-4">
<p class="!mt-0 !mb-2 font-mono text-sm text-text-muted">--color-{{ name }}-*</p>
<p class="!mt-0 !mb-2 font-mono text-sm text-muted-foreground">--color-{{ name }}-*</p>

<ul class="!m-0 flex flex-wrap gap-2 !p-0" style="list-style: none">
<li v-for="(value, step) in scale" :key="step" class="!m-0">
<button
type="button"
class="flex w-16 cursor-pointer flex-col gap-1 rounded-sm border-none bg-transparent p-0 text-left focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring"
class="flex w-16 cursor-pointer flex-col gap-1 rounded-sm border-none bg-transparent p-0 text-left focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
:aria-label="`Copy --color-${name}-${step}`"
@click="copy(`--color-${name}-${step}`)"
>
<span
class="block h-12 w-full rounded-sm border border-border"
:style="{ background: value }"
/>
<span class="font-mono text-xs text-text-muted">
<span class="font-mono text-xs text-muted-foreground">
{{ copied === `--color-${name}-${step}` ? 'copied' : step }}
</span>
</button>
Expand Down
2 changes: 1 addition & 1 deletion docs/.vitepress/theme/components/DemoBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const alignment = { start: 'items-start', center: 'items-center', end: 'items-en

<template>
<div
class="rk-demo my-6 flex gap-4 overflow-x-auto rounded-lg border border-border bg-surface p-6"
class="rk-demo my-6 flex gap-4 overflow-x-auto rounded-lg border border-border bg-card p-6"
:class="layout === 'stack' ? 'flex-col' : ['flex-wrap', alignment[align]]"
>
<slot />
Expand Down
22 changes: 22 additions & 0 deletions docs/.vitepress/theme/components/NpmVersion.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<script setup lang="ts">
import { version } from 'rowkit'

/**
* The published version, read from the package rather than typed into prose.
*
* Both places this appears make a claim about npm — "vX is on npm" — and a
* literal in markdown starts lying the moment a release lands. It already did:
* the site said `v0.1.0` for a day after `0.1.1` was published, and nothing
* anywhere flagged it, because prose has no build step that can disagree with
* it.
*
* `version` is derived from `package.json` at build time, so this renders
* whatever the docs were built against. `docs:build` builds the packages first
* for exactly that reason — reading it from a stale `dist` would reintroduce
* the drift with extra steps.
*/
</script>

<template>
<span class="rk-version">v{{ version }}</span>
</template>
4 changes: 2 additions & 2 deletions docs/.vitepress/theme/components/TokenGrid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ const { copied, copy } = useCopyToken()
<td>
<button
type="button"
class="cursor-pointer border-none bg-transparent p-0 font-mono text-sm text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring"
class="cursor-pointer border-none bg-transparent p-0 font-mono text-sm text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
:aria-label="`Copy ${prefix}-${name}`"
@click="copy(`${prefix}-${name}`)"
>
{{ copied === `${prefix}-${name}` ? 'copied' : `${prefix}-${name}` }}
</button>
</td>
<td class="font-mono text-sm text-text-muted">{{ value }}</td>
<td class="font-mono text-sm text-muted-foreground">{{ value }}</td>
<td><slot name="preview" :token="String(name)" :value="value" /></td>
</tr>
</tbody>
Expand Down
2 changes: 2 additions & 0 deletions docs/.vitepress/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import DefaultTheme from 'vitepress/theme'
import * as rowkit from 'rowkit'
import ColorScale from './components/ColorScale.vue'
import DemoBox from './components/DemoBox.vue'
import NpmVersion from './components/NpmVersion.vue'
import TokenGrid from './components/TokenGrid.vue'
import './tokens.css'

Expand All @@ -30,6 +31,7 @@ export default {
app.component('DemoBox', DemoBox)
app.component('ColorScale', ColorScale)
app.component('TokenGrid', TokenGrid)
app.component('NpmVersion', NpmVersion)

/*
* Vercel Analytics, guarded because `enhanceApp` runs during the static
Expand Down
53 changes: 49 additions & 4 deletions docs/.vitepress/theme/tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,51 @@
overflow: visible;
}

/*
* Table cells, for the same reason as the controls above.
*
* VitePress styles markdown tables as a grid, unlayered:
*
* .vp-doc th, .vp-doc td { border: 1px solid …; padding: 8px 16px }
* .vp-doc th { background: var(--vp-c-bg-soft); color: var(--vp-c-text-2) }
*
* Every one of those beat the component's layered utilities at once, so a
* DataTable demo rendered with vertical rules between every column, the wrong
* padding, a grey header band and muted header text — four separate departures
* from the real component, none of them visible in its class list.
*
* `th` and `td` only. Reverting the row would take the hover and selected
* backgrounds with it, and those are layered utilities the component wants.
*/
.rk-demo :is(th, td) {
all: revert-layer;
}

/*
* Body rows, for the third time and the same reason.
*
* .vp-doc tr { background-color: …; border-top: 1px solid …;
* transition: background-color 0.5s }
* .vp-doc tr:nth-child(2n) { background-color: var(--vp-c-bg-soft) }
*
* Zebra striping is right for a markdown table and wrong for a component that
* paints its own rows — every other row went grey, and in the loading state the
* stripe sat on top of the skeletons and hid them. The row also inherited a
* border it already draws on its cells, and a **half-second** background
* transition, which is why hover in a demo lagged behind the pointer.
*
* `revert-layer` restores the layered value rather than removing it, so the
* row's own `bg-card`, `hover:` and selected utilities all come back.
*
* `tbody tr:nth-child(2n)` is 0-2-2 against VitePress's 0-2-1. A plain
* `.rk-demo tr` is 0-1-1 and loses to the stripe, which is how this survived
* the first pass at the cells.
*/
.rk-demo tbody tr,
.rk-demo tbody tr:nth-child(2n) {
all: revert-layer;
}

/*
* The markdown below the hero shares the hero's lines.
*
Expand Down Expand Up @@ -139,11 +184,11 @@
--vp-c-brand-soft: var(--color-primary-100);

--vp-c-bg: var(--color-background);
--vp-c-bg-alt: var(--color-surface-subtle);
--vp-c-bg-soft: var(--color-surface-subtle);
--vp-c-bg-alt: var(--color-muted);
--vp-c-bg-soft: var(--color-muted);

--vp-c-text-1: var(--color-text);
--vp-c-text-2: var(--color-text-muted);
--vp-c-text-1: var(--color-foreground);
--vp-c-text-2: var(--color-muted-foreground);
--vp-c-text-3: var(--color-text-subtle);

--vp-c-divider: var(--color-border);
Expand Down
63 changes: 32 additions & 31 deletions docs/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ handling.
**Props**

- `variant: 'neutral' | 'primary' | 'success' | 'warning' | 'danger'` — default `'neutral'`. Status family. `neutral` is the "no particular status" default rather than an absence of styling.
- `appearance: 'subtle' | 'solid' | 'outline'` — default `'subtle'`. How much visual weight the badge carries. Prefer `subtle` in a table — a column of `solid` badges reads as a wall of colour and stops communicating anything.
- `appearance: 'subtle' | 'solid' | 'outline'` — default `'subtle'`. How much visual weight the badge carries. Prefer `subtle` in a table — soft tinted chip with a matching hairline, quieter than `solid` / `outline`. `solid` is for when a single badge has to carry the page.
- `size: 'sm' | 'md'` — default `'md'`. Badge size. `sm` is intended for dense table rows.
- `dot: boolean` — default `false`. Shows a filled dot before the label, inheriting the text colour.
- `class: string`. Additional classes, merged with the variant classes so a consumer's utility wins over the component's own.
Expand All @@ -95,7 +95,8 @@ handling.
**Props**

- `variant: 'primary' | 'danger' | 'secondary' | 'ghost'` — default `'primary'`. Visual weight and intent.
- `size: 'sm' | 'md' | 'lg'` — default `'md'`. Control height and text size.
- `size: 'sm' | 'md' | 'xs' | 'lg'` — default `'md'`. Control height and text size.
- `icon: boolean` — default `false`. Renders the button square, for a label that is only an icon.
- `block: boolean` — default `false`. Stretches the button to fill its container.
- `loading: boolean` — default `false`. Swaps the leading slot for a spinner and blocks activation.
- `disabled: boolean` — default `false`. Disables the button.
Expand Down Expand Up @@ -276,6 +277,35 @@ handling.
- `#leading` — Content rendered before the input, inside the control's border.
- `#trailing` — Content rendered after the input — a unit, a clear button, a spinner.

### Pagination

`import { Pagination } from 'rowkit'`

**Props**

- `total: number` _(required)_. Total number of rows across all pages.
- `pageSizeOptions: number[]` — default `() => [10, 25, 50, 100]`. Choices offered in the rows-per-page control.
- `siblingCount: number` — default `1`. How many page numbers to show on each side of the current one.
- `showEdges: boolean` — default `true`. Always show the first and last page, with ellipses between.
- `hidePageSize: boolean` — default `false`. Hides the rows-per-page control.
- `hideSummary: boolean` — default `false`. Hides the "1–10 of 247" summary.
- `pageSizeLabel: string` — default `'Rows per page'`. Label for the rows-per-page control.
- `label: string` — default `'Pagination'`. Accessible name for the navigation region.
- `previousLabel: string` — default `'Previous page'`. Accessible name for the previous-page control.
- `nextLabel: string` — default `'Next page'`. Accessible name for the next-page control.
- `size: 'sm' | 'md'` — default `'md'`. Control height and text size.
- `disabled: boolean` — default `false`. Disables every control.
- `class: string`. Additional classes, merged so a consumer's utility wins.

**v-model**

- `v-model:page` — `number`. The current page, 1-based.
- `v-model:pageSize` — `number`. Rows per page.

**Slots**

- `#summary` `(props: { from: number; to: number; total: number })` — Replaces the range summary.

### Select

`import { Select } from 'rowkit'`
Expand Down Expand Up @@ -323,35 +353,6 @@ handling.
- `as: string | Component` — default `'div'`. Element or component to render as.
- `asChild: boolean` — default `false`. Merge props onto the single child element instead of rendering a wrapper.

### TablePagination

`import { TablePagination } from 'rowkit'`

**Props**

- `total: number` _(required)_. Total number of rows across all pages.
- `pageSizeOptions: number[]` — default `() => [10, 25, 50, 100]`. Choices offered in the rows-per-page control.
- `siblingCount: number` — default `1`. How many page numbers to show on each side of the current one.
- `showEdges: boolean` — default `true`. Always show the first and last page, with ellipses between.
- `hidePageSize: boolean` — default `false`. Hides the rows-per-page control.
- `hideSummary: boolean` — default `false`. Hides the "1–10 of 247" summary.
- `pageSizeLabel: string` — default `'Rows per page'`. Label for the rows-per-page control.
- `label: string` — default `'Pagination'`. Accessible name for the navigation region.
- `previousLabel: string` — default `'Previous page'`. Accessible name for the previous-page control.
- `nextLabel: string` — default `'Next page'`. Accessible name for the next-page control.
- `size: 'sm' | 'md'` — default `'md'`. Control height and text size.
- `disabled: boolean` — default `false`. Disables every control.
- `class: string`. Additional classes, merged so a consumer's utility wins.

**v-model**

- `v-model:page` — `number`. The current page, 1-based.
- `v-model:pageSize` — `number`. Rows per page.

**Slots**

- `#summary` `(props: { from: number; to: number; total: number })` — Replaces the range summary.

### Toaster

`import { Toaster } from 'rowkit'`
Expand Down
5 changes: 3 additions & 2 deletions docs/components/button.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ grows by the width of the spinner.
| Prop | Type | Default | Description |
| -------------- | ------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------- |
| `variant` | `'primary' \| 'danger' \| 'secondary' \| 'ghost'` | `'primary'` | Visual weight and intent. |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Control height and text size. |
| `size` | `'sm' \| 'md' \| 'xs' \| 'lg'` | `'md'` | Control height and text size. |
| `icon` | `boolean` | `false` | Renders the button square, for a label that is only an icon. |
| `block` | `boolean` | `false` | Stretches the button to fill its container. |
| `loading` | `boolean` | `false` | Swaps the leading slot for a spinner and blocks activation. |
| `disabled` | `boolean` | `false` | Disables the button. |
Expand Down Expand Up @@ -121,5 +122,5 @@ pointer and the keyboard path.
- Rendered as something other than `<button>` — a link, say — `disabled`
becomes `aria-disabled`, because `<a>` has no `disabled` attribute and
setting one does nothing.
- Focus is a 2px ring offset by 2px, using `--color-focus-ring`. Never remove
- Focus is a 2px ring offset by 2px, using `--color-ring`. Never remove
it; recolour it if you must.
Loading