{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"file-browser-block","type":"registry:block","title":"File browser","description":"Search and filter a small collection, inspect a file and download its original sample content.","registryDependencies":["https://mizu.user64bit.world/r/button.json","https://mizu.user64bit.world/r/data-table.json","https://mizu.user64bit.world/r/description-list.json","https://mizu.user64bit.world/r/empty-state.json","https://mizu.user64bit.world/r/search-field.json","https://mizu.user64bit.world/r/select-field.json"],"meta":{"client":true},"files":[{"path":"registry/blocks/FileBrowserBlock.tsx","type":"registry:component","target":"@ui/mizu/blocks/FileBrowserBlock.tsx","content":"\"use client\";\n\nimport { useState } from \"react\";\nimport { Button } from \"../button\";\nimport { DataTable } from \"../data-table\";\nimport { DescriptionList } from \"../description-list\";\nimport { EmptyState } from \"../empty-state\";\nimport { SearchField } from \"../search-field\";\nimport { SelectField } from \"../select-field\";\nimport \"./blocks.css\";\n\nconst files = [\n  {\n    id: \"readme\",\n    name: \"Readme.md\",\n    kind: \"Document\",\n    modified: \"2026-10-21\",\n    content:\n      \"# Harbour studio\\n\\nA small sample project for Mizu.\\n\\nEvery interaction should explain what just happened.\\n\",\n    mime: \"text/markdown\",\n  },\n  {\n    id: \"tokens\",\n    name: \"Tokens.css\",\n    kind: \"Stylesheet\",\n    modified: \"2026-10-20\",\n    content:\n      \":root {\\n  --harbour-ink: #171715;\\n  --harbour-paper: #f1eee8;\\n  --harbour-signal: #e86545;\\n}\\n\",\n    mime: \"text/css\",\n  },\n  {\n    id: \"brief\",\n    name: \"Interface brief.txt\",\n    kind: \"Document\",\n    modified: \"2026-10-19\",\n    content:\n      \"HARBOUR — INTERFACE BRIEF\\n\\nPurpose: help a team review work together.\\n\\nKeep the reading order clear. Make every action reversible. Design the empty state before the full one.\\n\",\n    mime: \"text/plain\",\n  },\n  {\n    id: \"traffic\",\n    name: \"Traffic.csv\",\n    kind: \"Dataset\",\n    modified: \"2026-10-18\",\n    content:\n      \"date,visits,signups\\n2026-10-15,1840,94\\n2026-10-16,2120,113\\n2026-10-17,1960,102\\n\",\n    mime: \"text/csv\",\n  },\n  {\n    id: \"release\",\n    name: \"Release notes.md\",\n    kind: \"Document\",\n    modified: \"2026-10-17\",\n    content:\n      \"# Sample release notes\\n\\n- Added a shared review queue.\\n- Kept keyboard selection aligned with pointer selection.\\n- Made failed actions retryable.\\n\",\n    mime: \"text/markdown\",\n  },\n] as const;\n\nexport function FileBrowserBlock() {\n  const [query, setQuery] = useState(\"\");\n  const [kind, setKind] = useState(\"all\");\n  const [selection, setSelection] = useState<string>(files[0].id);\n  const filtered = files.filter(\n    (file) =>\n      (kind === \"all\" || file.kind === kind) &&\n      file.name.toLowerCase().includes(query.trim().toLowerCase()),\n  );\n  const selected =\n    filtered.find((file) => file.id === selection) ?? filtered[0];\n  return (\n    <section\n      className=\"mizu-block mizu-block-files\"\n      aria-label=\"Sample file browser\"\n    >\n      <div className=\"mizu-block-toolbar\">\n        <SearchField\n          label=\"Find a file\"\n          value={query}\n          onValueChange={setQuery}\n          placeholder=\"Search sample files…\"\n        />\n        <SelectField\n          label=\"File type\"\n          value={kind}\n          onChange={(event) => setKind(event.target.value)}\n        >\n          <option value=\"all\">All types</option>\n          <option value=\"Document\">Documents</option>\n          <option value=\"Stylesheet\">Stylesheets</option>\n          <option value=\"Dataset\">Datasets</option>\n        </SelectField>\n      </div>\n      <p className=\"mizu-block-readout\" role=\"status\">\n        {filtered.length} of {files.length} sample files\n      </p>\n      <div className=\"mizu-block-file-workspace\">\n        <DataTable\n          label=\"Project files\"\n          rows={filtered}\n          getRowId={(file) => file.id}\n          empty={\n            <EmptyState\n              title=\"No matching files\"\n              action={\n                <Button\n                  variant=\"ghost\"\n                  arrow={false}\n                  onClick={() => {\n                    setQuery(\"\");\n                    setKind(\"all\");\n                  }}\n                >\n                  Clear filters\n                </Button>\n              }\n            >\n              Try another name or file type.\n            </EmptyState>\n          }\n          columns={[\n            {\n              id: \"name\",\n              header: \"Name\",\n              sortValue: (file) => file.name,\n              render: (file) => (\n                <button\n                  type=\"button\"\n                  className=\"mizu-block-file-name\"\n                  aria-pressed={selected?.id === file.id}\n                  onClick={() => setSelection(file.id)}\n                >\n                  {file.name}\n                </button>\n              ),\n            },\n            {\n              id: \"type\",\n              header: \"Type\",\n              sortValue: (file) => file.kind,\n              render: (file) => file.kind,\n            },\n            {\n              id: \"modified\",\n              header: \"Modified\",\n              sortValue: (file) => file.modified,\n              render: (file) => (\n                <time dateTime={file.modified}>{file.modified}</time>\n              ),\n            },\n          ]}\n        />\n        <aside className=\"mizu-block-file-detail\" aria-label=\"File details\">\n          {selected ? (\n            <>\n              <h3>{selected.name}</h3>\n              <DescriptionList\n                items={[\n                  { label: \"Type\", value: selected.kind },\n                  {\n                    label: \"Size\",\n                    value: `${new TextEncoder().encode(selected.content).length} bytes`,\n                  },\n                  {\n                    label: \"Modified\",\n                    value: (\n                      <time dateTime={selected.modified}>\n                        {selected.modified}\n                      </time>\n                    ),\n                  },\n                ]}\n              />\n              <pre tabIndex={0} aria-label={`${selected.name} preview`}>\n                {selected.content}\n              </pre>\n              <a\n                className=\"mizu-block-download\"\n                href={`data:${selected.mime};charset=utf-8,${encodeURIComponent(selected.content)}`}\n                download={selected.name}\n              >\n                Download sample file\n              </a>\n            </>\n          ) : (\n            <p className=\"mizu-field-hint\">\n              File details appear when a file matches your filters.\n            </p>\n          )}\n        </aside>\n      </div>\n    </section>\n  );\n}\n"},{"path":"registry/blocks/blocks.css","type":"registry:file","target":"@ui/mizu/blocks/blocks.css","content":".mizu-block {\n  min-width: 0;\n  color: var(--mizu-paper);\n  background: var(--mizu-ink);\n}\n.mizu-block h3 {\n  margin: 0 0 12px;\n  font: 600 24px / 1.2 var(--mizu-font-display);\n  letter-spacing: -0.025em;\n}\n.mizu-block p {\n  max-width: 65ch;\n}\n.mizu-block-settings {\n  display: grid;\n  grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr);\n  gap: 48px;\n}\n.mizu-block-settings-intro > p:first-of-type {\n  margin: 0 0 24px;\n  color: var(--mizu-muted);\n  line-height: 1.6;\n}\n.mizu-block-settings-intro > .mizu-field-hint {\n  margin-top: 24px;\n}\n.mizu-block-settings-form {\n  display: grid;\n  gap: 24px;\n}\n.mizu-block-settings-form > p {\n  margin: 0;\n}\n.mizu-block-settings-form > p:empty {\n  position: absolute;\n}\n.mizu-block-actions,\n.mizu-block-toolbar {\n  display: flex;\n  align-items: end;\n  justify-content: space-between;\n  gap: 16px;\n  flex-wrap: wrap;\n}\n.mizu-block-actions {\n  align-items: stretch;\n  justify-content: flex-start;\n}\n.mizu-block-text-action,\n.mizu-block-download {\n  display: inline-flex;\n  align-items: center;\n  min-height: 44px;\n  padding: 8px 0;\n  border: 0;\n  background: transparent;\n  color: var(--mizu-accent);\n  font: 11px var(--mizu-font-mono);\n  text-decoration: underline;\n  text-underline-offset: 4px;\n  cursor: pointer;\n}\n.mizu-block-text-action:hover,\n.mizu-block-download:hover {\n  color: var(--mizu-paper);\n}\n.mizu-block-text-action:focus-visible,\n.mizu-block-download:focus-visible,\n.mizu-block-file-name:focus-visible,\n.mizu-block-file-detail pre:focus-visible {\n  outline: 2px solid var(--mizu-accent);\n  outline-offset: 4px;\n}\n.mizu-block-toolbar > .mizu-search {\n  flex: 1 1 240px;\n}\n.mizu-block-toolbar > .mizu-field {\n  flex: 0 1 200px;\n}\n.mizu-block-readout {\n  margin: 16px 0;\n  color: var(--mizu-muted);\n  font: 11px var(--mizu-font-mono);\n  font-variant-numeric: tabular-nums;\n}\n.mizu-block-file-workspace {\n  display: grid;\n  grid-template-columns: minmax(0, 1.5fr) minmax(240px, 1fr);\n  gap: 32px;\n  align-items: start;\n}\n.mizu-block-file-name {\n  min-height: 44px;\n  padding: 0;\n  border: 0;\n  background: none;\n  color: var(--mizu-paper);\n  font: inherit;\n  text-align: left;\n  text-decoration: underline;\n  text-decoration-color: transparent;\n  text-underline-offset: 4px;\n  overflow-wrap: anywhere;\n  cursor: pointer;\n  transition: text-decoration-color var(--mizu-duration-fast);\n}\n.mizu-block-file-name:hover {\n  text-decoration-color: var(--mizu-line-bright);\n}\n.mizu-block-file-name[aria-pressed=\"true\"] {\n  color: var(--mizu-accent);\n}\n.mizu-block-file-detail {\n  min-width: 0;\n  padding: 24px;\n  border: 1px solid var(--mizu-line);\n  background: var(--mizu-ink-2);\n}\n.mizu-block-file-detail h3 {\n  font-size: 20px;\n  overflow-wrap: anywhere;\n}\n.mizu-block-file-detail pre {\n  max-height: 240px;\n  margin: 20px 0 8px;\n  overflow: auto;\n  white-space: pre-wrap;\n  overflow-wrap: anywhere;\n  color: var(--mizu-paper);\n  font: 11px / 1.7 var(--mizu-font-mono);\n  tab-size: 2;\n}\n.mizu-block-analytics {\n  display: grid;\n  gap: 32px;\n}\n.mizu-block-analytics-controls {\n  display: grid;\n  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);\n  gap: 48px;\n  align-items: start;\n}\n.mizu-block-analytics-summary {\n  display: grid;\n  grid-template-columns: repeat(2, minmax(0, 1fr));\n  gap: 24px;\n  padding-top: 28px;\n}\n@media (max-width: 800px) {\n  .mizu-block-settings,\n  .mizu-block-file-workspace,\n  .mizu-block-analytics-controls {\n    grid-template-columns: minmax(0, 1fr);\n    gap: 28px;\n  }\n  .mizu-block-analytics-summary {\n    padding-top: 0;\n  }\n}\n@media (max-width: 420px) {\n  .mizu-block-analytics-summary {\n    grid-template-columns: minmax(0, 1fr);\n  }\n}\n"}]}