Files
orca/examples/plugins/components-gallery/src/data.tsx
Neil ce8ea081c0 Plugin check shows what drew; responsive Table columns; quieter Table and Stack (#37062)
* plugins: check shows what drew; responsive Table columns; quieter Table and Stack

- orca plugin check: each scenario result carries a summary (headings,
  callouts, empty states, table and tree sizes, the selected tab, loading
  placeholders, danger/warning text), printed under its line, and --json
  adds the drawn tree. dev watch's check event keeps only the summary.
  orca/testing gains panel.summary().
- Table columns take minWidth (floor for an auto column) and hideBelow
  ('sm' 360px, 'md' 480px), measured on the table itself; at least one
  column stays. Cut-off cells and headers show their text on hover. The
  kit's columnsOf(table, { width }) answers which columns fit.
- The active-row ring shows only while the grid has keyboard focus.
- A Stack with no align keeps buttons at their own width while fields,
  tables and cards still fill it; align='stretch' stretches everything.
- Typings, docs, the skill's workflow reference, regenerated references;
  the gallery's Kind column drops out in a narrow sidebar.

* plugins: check can render one scenario and return its drawn tree

For orca plugin shot --scenario (tree replay): checkPluginPanels takes
{ panel, scenario, drawnTree }, and with drawnTree returns each render's
validated tree exactly as Orca draws it (handlers as { $callback }),
re-validated on the CLI side. An unknown scenario is reported as an issue.
readPluginCheckScenarioNames(dir, entry) lists a panel's scenarios by
loading its .check file without mounting anything.
2026-11-01 09:07:14 -08:00

85 lines
2.6 KiB
TypeScript

import {
atom,
read,
set,
Stack,
Table,
Text,
Tree,
update,
type TableSort,
type TreeItem
} from 'orca'
import { folderChildren, PAGE_SIZE, sortedFiles } from './files'
export const sort = atom<TableSort>({ column: 'name', direction: 'asc' })
export const loaded = atom(PAGE_SIZE)
export const selected = atom<string[]>([])
export const opened = atom<string | null>(null)
/** Lazily loaded folder children, by folder key. */
export const folders = atom<Record<string, TreeItem[]>>({})
export const selectedItem = atom('')
/** Open folders, kept here so they stay open when the Data tab is shown again. */
export const expanded = atom<string[]>([])
const ROOTS = ['src', 'tests', 'docs']
export function DataTab() {
const files = sortedFiles(read(sort))
const rows = files.slice(0, read(loaded)).map((file) => ({
key: file.path,
cells: { name: file.name, kind: file.kind, size: file.size }
}))
const items: TreeItem[] = ROOTS.map((key) => ({
key,
label: key,
icon: 'folder',
hasChildren: true,
children: read(folders)[key]
}))
return (
<Stack gap="md">
<Table
label="Files"
columns={[
{ key: 'name', label: 'Name', sortable: true, minWidth: 'sm' },
// Secondary: drops out when the sidebar is narrow.
{ key: 'kind', label: 'Kind', width: 'sm', hideBelow: 'sm' },
{ key: 'size', label: 'Bytes', width: 'sm', align: 'end', sortable: true }
]}
rows={rows}
sort={read(sort)}
selectionMode="multiple"
selected={read(selected)}
hasMore={read(loaded) < files.length}
maxHeight="md"
onSortChange={(next) => {
set(sort, next)
set(loaded, PAGE_SIZE)
}}
onSelectionChange={({ keys }) => set(selected, keys)}
onRowAction={({ key }) => set(opened, key)}
onLoadMore={() => update(loaded, (count) => count + PAGE_SIZE)}
/>
<Text muted size="xs">
{read(selected).length} selected
{read(opened) ? `, opened ${read(opened)}` : ''}
</Text>
<Tree
label="Folders"
items={items}
selected={read(selectedItem)}
expanded={read(expanded)}
onSelect={({ key }) => set(selectedItem, key)}
onToggle={({ key, expanded: open }) => {
update(expanded, (keys) => (open ? [...keys, key] : keys.filter((k) => k !== key)))
// Children load on first expand, the way a file system or API would be read.
if (open && !read(folders)[key]) {
update(folders, (all) => ({ ...all, [key]: folderChildren(key) }))
}
}}
/>
</Stack>
)
}