From 7c975b55396201ea4b7a82e219b38e0f647d3ccc Mon Sep 17 00:00:00 2001 From: nfebe Date: Sat, 26 Sep 2026 21:30:41 +0100 Subject: [PATCH 1/4] feat: Add a table and a command search Two pieces both products were about to write twice. A table, because a question asked of sixty rows is a column rather than sixty cards. A command search, because navigation answers where a screen is and people are looking for a thing. Neither knows what a skill or a review is: the application hands them rows and groups. --- README.md | 4 +- src/components/CommandSearch.vue | 228 +++++++++++++++++++++++++++++++ src/components/Table.vue | 86 ++++++++++++ src/index.js | 2 + 4 files changed, 318 insertions(+), 2 deletions(-) create mode 100644 src/components/CommandSearch.vue create mode 100644 src/components/Table.vue diff --git a/README.md b/README.md index 76e967c..c3a5e02 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ notices the two products no longer look like one. ## What is in it, and what is not Tokens, and the pieces a screen is assembled from: `Avatar`, `Badge`, `Button`, -`Card`, `Field`, `Input`, `ItemCard`, `Logo`, `Modal`, `PageHead`, `Select`, -`Tabs`, `Textarea`, and `CodeGraph`. +`Card`, `CommandSearch`, `Field`, `Input`, `ItemCard`, `Logo`, `Modal`, +`PageHead`, `Select`, `Table`, `Tabs`, `Textarea`, and `CodeGraph`. `src/index.js` is the list that is actually true. Nothing that knows what a workspace, a review or a proposal is. Those belong to whichever application has them, and a package that grew to hold them would be diff --git a/src/components/CommandSearch.vue b/src/components/CommandSearch.vue new file mode 100644 index 0000000..b565bed --- /dev/null +++ b/src/components/CommandSearch.vue @@ -0,0 +1,228 @@ + + + diff --git a/src/components/Table.vue b/src/components/Table.vue new file mode 100644 index 0000000..2dd343d --- /dev/null +++ b/src/components/Table.vue @@ -0,0 +1,86 @@ + + + diff --git a/src/index.js b/src/index.js index 200d570..cab4e6e 100644 --- a/src/index.js +++ b/src/index.js @@ -10,12 +10,14 @@ export { default as Badge } from './components/Badge.vue' export { default as Button } from './components/Button.vue' export { default as Card } from './components/Card.vue' export { default as CodeGraph } from './components/CodeGraph.vue' +export { default as CommandSearch } from './components/CommandSearch.vue' export { default as Field } from './components/Field.vue' export { default as Input } from './components/Input.vue' export { default as ItemCard } from './components/ItemCard.vue' export { default as PageHead } from './components/PageHead.vue' export { default as Select } from './components/Select.vue' export { default as SearchableSelect } from './components/SearchableSelect.vue' +export { default as Table } from './components/Table.vue' export { default as Tabs } from './components/Tabs.vue' export { default as Textarea } from './components/Textarea.vue' export { default as ListInput } from './components/ListInput.vue' From 180f8eb1181ad254c34f4006e25fdc07bf27d106 Mon Sep 17 00:00:00 2001 From: nfebe Date: Sat, 26 Sep 2026 22:14:05 +0100 Subject: [PATCH 2/4] fix: Run the table at the density every other table runs at --- src/components/Table.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Table.vue b/src/components/Table.vue index 2dd343d..74aadf9 100644 --- a/src/components/Table.vue +++ b/src/components/Table.vue @@ -52,7 +52,7 @@ const keyOf = (row: Record, index: number) => scope="col" :style="column.width ? { width: column.width } : undefined" :class="cn( - 'px-3 py-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground', + 'px-2.5 py-1.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground', column.align === 'right' ? 'text-right' : 'text-left', column.narrow && 'hidden sm:table-cell', )" @@ -71,8 +71,8 @@ const keyOf = (row: Record, index: number) => v-for="column in columns" :key="column.id" :class="cn( - 'px-3 align-middle', - dense ? 'py-1.5' : 'py-2.5', + 'px-2.5 align-middle', + dense ? 'py-1' : 'py-1.5', column.align === 'right' ? 'text-right' : 'text-left', column.narrow && 'hidden sm:table-cell', )" From 069a5333820f71dd995a3280f09fa1275267c961 Mon Sep 17 00:00:00 2001 From: nfebe Date: Sun, 27 Sep 2026 10:58:45 +0100 Subject: [PATCH 3/4] docs(release): Prepare 0.3.0 --- CHANGELOG.md | 14 ++++++++++++++ package.json | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ad331d..0965212 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.0] - 2026-09-27 + +Two pieces both products were about to write twice. Everything from 0.2.0 keeps +working unchanged. + +### Added + +- `Table`, because a question asked of sixty rows is a column rather than sixty + cards. Cells are slots named after their column, so what a value looks like + stays with the application that knows what it means +- `CommandSearch`, the field that finds anything, opened from the keyboard. It + owns the field, the keys and the list; the application hands it groups of + results it has already narrowed + ## [0.2.0] - 2026-09-01 The components the dashboard kept for itself, so both surfaces can share one diff --git a/package.json b/package.json index 6f6d932..b386cc1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sourceant/design", - "version": "0.2.0", + "version": "0.3.0", "description": "SourceAnt design system: tokens, primitives, and the graph renderer", "license": "MIT", "type": "module", From 1a403b636a89429423cbcb530a85213bd94fc08c Mon Sep 17 00:00:00 2001 From: nfebe Date: Sun, 27 Sep 2026 17:11:45 +0100 Subject: [PATCH 4/4] refactor: Rename CommandSearch to GlobalSearch It is a global search, not a command palette. Both new components lost the paragraphs explaining why they exist. --- CHANGELOG.md | 12 +++---- README.md | 4 +-- .../{CommandSearch.vue => GlobalSearch.vue} | 33 ++++++++----------- src/components/Table.vue | 5 --- src/index.js | 2 +- 5 files changed, 20 insertions(+), 36 deletions(-) rename src/components/{CommandSearch.vue => GlobalSearch.vue} (86%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0965212..f0ba3b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,17 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.3.0] - 2026-09-27 -Two pieces both products were about to write twice. Everything from 0.2.0 keeps -working unchanged. +Everything from 0.2.0 keeps working unchanged. ### Added -- `Table`, because a question asked of sixty rows is a column rather than sixty - cards. Cells are slots named after their column, so what a value looks like - stays with the application that knows what it means -- `CommandSearch`, the field that finds anything, opened from the keyboard. It - owns the field, the keys and the list; the application hands it groups of - results it has already narrowed +- `Table`, with cells as slots named after their column +- `GlobalSearch`, a search field opened with Ctrl or Cmd K, handed groups of + results the application has already narrowed ## [0.2.0] - 2026-09-01 diff --git a/README.md b/README.md index c3a5e02..abca897 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ notices the two products no longer look like one. ## What is in it, and what is not Tokens, and the pieces a screen is assembled from: `Avatar`, `Badge`, `Button`, -`Card`, `CommandSearch`, `Field`, `Input`, `ItemCard`, `Logo`, `Modal`, -`PageHead`, `Select`, `Table`, `Tabs`, `Textarea`, and `CodeGraph`. `src/index.js` is the list that is actually true. +`Card`, `Field`, `Input`, `ItemCard`, `Logo`, `Modal`, +`GlobalSearch`, `PageHead`, `Select`, `Table`, `Tabs`, `Textarea`, and `CodeGraph`. `src/index.js` is the list that is actually true. Nothing that knows what a workspace, a review or a proposal is. Those belong to whichever application has them, and a package that grew to hold them would be diff --git a/src/components/CommandSearch.vue b/src/components/GlobalSearch.vue similarity index 86% rename from src/components/CommandSearch.vue rename to src/components/GlobalSearch.vue index b565bed..fa8c543 100644 --- a/src/components/CommandSearch.vue +++ b/src/components/GlobalSearch.vue @@ -3,36 +3,29 @@ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue' import { ArrowUpRight, Loader2, Search, X } from 'lucide-vue-next' import { cn } from '../lib/utils' -/** - * One field that finds anything, opened from the keyboard. +/** One field that finds anything, opened with Ctrl or Cmd K. * - * Navigation answers "where is that screen". This answers "where is that - * thing", which is the question somebody actually has, and the answer is - * usually not on the screen they are looking at. - * - * What the results are is the application's business: it is handed groups of - * them, already narrowed, so it can ask a server or filter what it holds. This - * owns the field, the keys and the list, because those were written out by hand - * in every product that had a search. + * Results are handed in, already narrowed, so the application decides what is + * searchable. This owns the field, the keys and the list. */ -export interface CommandSearchItem { +export interface GlobalSearchItem { id: string label: string /** A second line, for what the label alone leaves ambiguous. */ detail?: string } -export interface CommandSearchGroup { +export interface GlobalSearchGroup { id: string label: string - items: CommandSearchItem[] + items: GlobalSearchItem[] } const props = withDefaults(defineProps<{ /** The query, so the application can narrow or ask for results itself. */ modelValue?: string - groups?: CommandSearchGroup[] + groups?: GlobalSearchGroup[] open?: boolean /** Said above the results, for where the search is looking. */ context?: string @@ -55,7 +48,7 @@ const props = withDefaults(defineProps<{ const emit = defineEmits<{ 'update:modelValue': [string] 'update:open': [boolean] - select: [CommandSearchItem, string] + select: [GlobalSearchItem, string] }>() const active = ref(0) @@ -92,7 +85,7 @@ function choose(index = active.value) { emit('update:open', false) } -function indexOf(group: CommandSearchGroup, item: CommandSearchItem) { +function indexOf(group: GlobalSearchGroup, item: GlobalSearchItem) { return flat.value.findIndex(one => one.group === group.id && one.item.id === item.id) } @@ -154,9 +147,9 @@ onUnmounted(() => { :value="modelValue" role="combobox" aria-autocomplete="list" - aria-controls="command-search-results" + aria-controls="global-search-results" :aria-expanded="open" - :aria-activedescendant="flat.length ? `command-search-${active}` : undefined" + :aria-activedescendant="flat.length ? `global-search-${active}` : undefined" :aria-label="label" :placeholder="placeholder" class="h-10 min-w-0 flex-1 bg-transparent text-sm outline-none" @@ -179,7 +172,7 @@ onUnmounted(() => {

{{ context }}

    {