@nanisoft/prism-llms 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/README.md +88 -0
  2. package/THIRD-PARTY-NOTICES.md +48 -0
  3. package/dist/data.json +3996 -387
  4. package/dist/demo-graph.d.ts +21 -12
  5. package/dist/demo-graph.js +37 -21
  6. package/dist/extractor.d.ts +24 -17
  7. package/dist/extractor.js +269 -153
  8. package/dist/index.d.ts +12 -7
  9. package/dist/index.js +9 -7
  10. package/dist/llms-full.txt +7968 -2013
  11. package/dist/llms.txt +97 -93
  12. package/dist/markdown.d.ts +69 -23
  13. package/dist/markdown.js +131 -72
  14. package/dist/md/blocks/app-shell-01.md +135 -0
  15. package/dist/md/blocks/auth-form-01.md +135 -0
  16. package/dist/md/blocks/cta-01.md +88 -0
  17. package/dist/md/blocks/data-table-01.md +196 -0
  18. package/dist/md/blocks/feature-grid-01.md +92 -0
  19. package/dist/md/blocks/hero-01.md +90 -0
  20. package/dist/md/blocks/instrument-panel-01.md +109 -0
  21. package/dist/md/blocks/logo-strip-01.md +97 -0
  22. package/dist/md/blocks/note-grid-01.md +102 -0
  23. package/dist/md/blocks/page-header-01.md +97 -0
  24. package/dist/md/blocks/pricing-01.md +91 -0
  25. package/dist/md/blocks/process-rail-01.md +106 -0
  26. package/dist/md/blocks/product-grid-01.md +113 -0
  27. package/dist/md/blocks/settings-panel-01.md +148 -0
  28. package/dist/md/blocks/site-footer.md +103 -11
  29. package/dist/md/blocks/site-header.md +111 -11
  30. package/dist/md/blocks/stack-grid-01.md +108 -0
  31. package/dist/md/blocks/stats-01.md +89 -0
  32. package/dist/md/blocks/status-ledger-01.md +121 -0
  33. package/dist/md/changelogs/prism-llms.md +206 -0
  34. package/dist/md/changelogs/prism-mcp-server.md +88 -0
  35. package/dist/md/changelogs/prism-tokens.md +126 -0
  36. package/dist/md/changelogs/prism-ui.md +429 -0
  37. package/dist/md/components/accordion.md +108 -0
  38. package/dist/md/components/alert.md +90 -7
  39. package/dist/md/components/avatar.md +89 -7
  40. package/dist/md/components/badge.md +74 -7
  41. package/dist/md/components/breadcrumb.md +84 -7
  42. package/dist/md/components/button.md +90 -7
  43. package/dist/md/components/card.md +90 -7
  44. package/dist/md/components/checkbox.md +101 -7
  45. package/dist/md/components/cta-link.md +98 -0
  46. package/dist/md/components/diagram.md +124 -0
  47. package/dist/md/components/dialog.md +128 -0
  48. package/dist/md/components/dropdown-menu.md +125 -0
  49. package/dist/md/components/fact-list.md +85 -0
  50. package/dist/md/components/field.md +116 -0
  51. package/dist/md/components/input.md +86 -7
  52. package/dist/md/components/kbd.md +77 -0
  53. package/dist/md/components/pagination.md +95 -7
  54. package/dist/md/components/popover.md +95 -7
  55. package/dist/md/components/product-mark.md +123 -0
  56. package/dist/md/components/product-switcher.md +88 -0
  57. package/dist/md/components/progress.md +88 -7
  58. package/dist/md/components/prose.md +91 -0
  59. package/dist/md/components/radio-group.md +111 -0
  60. package/dist/md/components/section.md +91 -0
  61. package/dist/md/components/select.md +143 -7
  62. package/dist/md/components/separator.md +77 -0
  63. package/dist/md/components/skeleton.md +71 -7
  64. package/dist/md/components/slider.md +90 -7
  65. package/dist/md/components/switch.md +104 -7
  66. package/dist/md/components/table.md +104 -7
  67. package/dist/md/components/tabs.md +105 -7
  68. package/dist/md/components/textarea.md +101 -0
  69. package/dist/md/components/tooltip.md +95 -7
  70. package/dist/md/components/typography.md +82 -7
  71. package/dist/md/content/about-content.md +36 -0
  72. package/dist/md/content/audience.md +43 -0
  73. package/dist/md/content/glossary.md +69 -0
  74. package/dist/md/content/product-vocabulary.md +53 -0
  75. package/dist/md/content/voice.md +52 -0
  76. package/dist/md/content/writing-mechanics.md +58 -0
  77. package/dist/md/foundation/colors.md +10 -0
  78. package/dist/md/foundation/iconography.md +14 -0
  79. package/dist/md/foundation/motion.md +10 -0
  80. package/dist/md/foundation/radii.md +9 -0
  81. package/dist/md/foundation/shadows.md +9 -0
  82. package/dist/md/foundation/spacings.md +8 -0
  83. package/dist/md/foundation/typography.md +9 -0
  84. package/dist/md/foundation/variables.md +9 -0
  85. package/dist/md/overview/architecture.md +76 -0
  86. package/dist/md/overview/brand.md +41 -0
  87. package/dist/md/overview/composition.md +77 -0
  88. package/dist/md/overview/quickstart.md +97 -0
  89. package/dist/md/overview/theming.md +89 -0
  90. package/dist/md/overview/using-llms.md +83 -0
  91. package/dist/md/pages/auth-page.md +149 -0
  92. package/dist/md/pages/blog-post-page.md +136 -0
  93. package/dist/md/pages/dashboard-page.md +241 -0
  94. package/dist/md/pages/marketing-page.md +199 -0
  95. package/dist/md/pages/not-found-page.md +105 -0
  96. package/dist/md/pages/settings-page.md +206 -0
  97. package/dist/prism-skill.md +50 -0
  98. package/dist/store.d.ts +203 -0
  99. package/dist/store.js +317 -0
  100. package/package.json +29 -26
  101. package/dist/demo-graph.d.ts.map +0 -1
  102. package/dist/demo-graph.js.map +0 -1
  103. package/dist/extractor.d.ts.map +0 -1
  104. package/dist/extractor.js.map +0 -1
  105. package/dist/index.d.ts.map +0 -1
  106. package/dist/index.js.map +0 -1
  107. package/dist/markdown.d.ts.map +0 -1
  108. package/dist/markdown.js.map +0 -1
  109. package/dist/md/blocks/component-demo.md +0 -19
  110. package/dist/md/blocks/page-header.md +0 -19
  111. package/dist/md/components/affix.md +0 -14
  112. package/dist/md/components/anchor.md +0 -14
  113. package/dist/md/components/auto-complete.md +0 -14
  114. package/dist/md/components/back-top.md +0 -14
  115. package/dist/md/components/border-beam.md +0 -14
  116. package/dist/md/components/calendar.md +0 -14
  117. package/dist/md/components/carousel.md +0 -14
  118. package/dist/md/components/cascader.md +0 -14
  119. package/dist/md/components/col.md +0 -14
  120. package/dist/md/components/collapse.md +0 -14
  121. package/dist/md/components/color-picker.md +0 -14
  122. package/dist/md/components/date-picker.md +0 -14
  123. package/dist/md/components/descriptions.md +0 -14
  124. package/dist/md/components/display-title.md +0 -20
  125. package/dist/md/components/divider.md +0 -14
  126. package/dist/md/components/drawer.md +0 -14
  127. package/dist/md/components/dropdown.md +0 -14
  128. package/dist/md/components/empty.md +0 -14
  129. package/dist/md/components/flex.md +0 -14
  130. package/dist/md/components/float-button.md +0 -14
  131. package/dist/md/components/form.md +0 -14
  132. package/dist/md/components/grid.md +0 -14
  133. package/dist/md/components/image.md +0 -14
  134. package/dist/md/components/input-number.md +0 -14
  135. package/dist/md/components/layout.md +0 -14
  136. package/dist/md/components/list.md +0 -14
  137. package/dist/md/components/listy.md +0 -14
  138. package/dist/md/components/masonry.md +0 -14
  139. package/dist/md/components/mentions.md +0 -14
  140. package/dist/md/components/menu.md +0 -14
  141. package/dist/md/components/modal.md +0 -14
  142. package/dist/md/components/popconfirm.md +0 -14
  143. package/dist/md/components/qrcode.md +0 -14
  144. package/dist/md/components/radio.md +0 -14
  145. package/dist/md/components/rate.md +0 -14
  146. package/dist/md/components/result.md +0 -14
  147. package/dist/md/components/row.md +0 -14
  148. package/dist/md/components/segmented.md +0 -14
  149. package/dist/md/components/space.md +0 -14
  150. package/dist/md/components/spin.md +0 -14
  151. package/dist/md/components/splitter.md +0 -14
  152. package/dist/md/components/statistic.md +0 -14
  153. package/dist/md/components/steps.md +0 -14
  154. package/dist/md/components/tag.md +0 -14
  155. package/dist/md/components/time-picker.md +0 -14
  156. package/dist/md/components/timeline.md +0 -14
  157. package/dist/md/components/tour.md +0 -14
  158. package/dist/md/components/transfer.md +0 -14
  159. package/dist/md/components/tree-select.md +0 -14
  160. package/dist/md/components/tree.md +0 -14
  161. package/dist/md/components/upload.md +0 -14
  162. package/dist/md/components/watermark.md +0 -14
  163. package/dist/md/docs/brand.md +0 -41
  164. package/dist/md/pages/blog-layout.md +0 -19
  165. package/dist/md/pages/docs-shell.md +0 -23
  166. package/dist/md/theme/blue-dark.md +0 -127
  167. package/dist/md/theme/blue-light.md +0 -129
  168. package/dist/md/theme/green-dark.md +0 -127
  169. package/dist/md/theme/green-light.md +0 -129
  170. package/dist/md/theme/lavender-dark.md +0 -127
  171. package/dist/md/theme/lavender-light.md +0 -129
  172. package/dist/md/theme/peach-dark.md +0 -127
  173. package/dist/md/theme/peach-light.md +0 -129
  174. package/dist/md/theme/rose-dark.md +0 -127
  175. package/dist/md/theme/rose-light.md +0 -129
@@ -0,0 +1,136 @@
1
+ # BlogPostPage
2
+
3
+ A complete blog post screen: title, standfirst, byline with both the display and the machine date, the body at the measure and the trail to the neighbouring posts.
4
+
5
+ ```tsx
6
+ import { BlogPostPage } from '@nanisoft/prism-ui/pages/blog-post-page'
7
+ ```
8
+
9
+ ## Overview
10
+
11
+ BlogPostPage is a complete blog post screen: the title, the standfirst, the
12
+ byline, the body at the reading measure, and the trail to the neighbouring posts.
13
+
14
+ All four NaniSoft sites publish a blog, each with four posts and the same
15
+ frontmatter: a title, a description, a date and a list of tags. Each one then
16
+ rendered that frontmatter in its own component, with its own wrapper, its own
17
+ measure and its own treatment of the date, and each of them wrote the trail to the
18
+ neighbouring posts separately or not at all.
19
+
20
+ **The date is two props.** `date` is the words a reader sees and `dateTime` is the
21
+ machine value on the `time` element, and they are kept apart because parsing a
22
+ formatted date back into an ISO one is a guess about a locale. A wrong date in a
23
+ `datetime` attribute is a wrong date in a feed reader and in a search result, and
24
+ neither reader can see the string that was wrong.
25
+
26
+ ## Guidelines
27
+
28
+ ### When to use
29
+
30
+ Use BlogPostPage as the body of the route that renders one post, handed the
31
+ frontmatter your content pipeline produced.
32
+
33
+ Use it for every post. A blog whose posts each have their own wrapper is a blog
34
+ whose posts drift apart within a year.
35
+
36
+ ### When not to use
37
+
38
+ Do not use it for a documentation page. Documentation is a set of short pages
39
+ about one thing; a post is a dated argument about one thing, and a trail to the
40
+ post before it is not a trail through a manual.
41
+
42
+ Do not use it for a landing page. `MarketingPage` is that composition.
43
+
44
+ ### Best practices
45
+
46
+ - Pass `dateTime` in `YYYY-MM-DD`. It is the value a feed reader and a search
47
+ engine order by, and it is the one that is wrong silently.
48
+ - The body is `children`, held by `Prose`. Your pipeline owns the document; this
49
+ owns the measure and the rhythm. Do not parse it into an array of paragraphs to
50
+ hand over - that is a second representation of something your pipeline already
51
+ has.
52
+ - Render only the halves of the trail you have. A previous link on the oldest post
53
+ is a link to the first post a reader has already read, and a next link on the
54
+ newest is a promise the site cannot keep.
55
+ - `trailLabel` and `trailLabels` are required alongside a neighbour. They are words
56
+ a reader reads and hears, and a Page that ships no copy ships no reader-facing
57
+ copy either: a site that files posts newest-last says the opposite pair.
58
+ - Use `footer` for what comes after the trail: related posts, a newsletter form, a
59
+ closing call to action. It is a slot because every one of those is a Block, and a
60
+ Page that took a Block per prop would be a Page whose interface lists the whole
61
+ catalogue.
62
+ - Pass `indexLink` with the words and the route your archive lives at. The page
63
+ ships neither.
64
+
65
+ ### Content guidelines
66
+
67
+ - Write the standfirst as one or two sentences a reader can stop at. It is what an
68
+ index, a feed and a search result show.
69
+ - Keep tags to nouns. A tag is a category a reader browses, not a sentence.
70
+ - Keep the title to one line at the largest viewport unless the post is the only
71
+ thing on the page.
72
+
73
+ **A post: the title, the standfirst, both dates, tags, the body and the trail.**
74
+
75
+ ```tsx
76
+ import { BlogPostPage } from '@nanisoft/prism-ui/pages/blog-post-page'
77
+
78
+ /** A post: the title, the standfirst, both dates, tags, the body and the trail. */
79
+ export default function BlogPostPageDemo() {
80
+ return (
81
+ <BlogPostPage
82
+ title="Every minute, the whole chain"
83
+ description="What live full-chain capture at index scale actually takes: the spike, then production, then Kubernetes."
84
+ date="21 September 2026"
85
+ dateTime="2026-09-21"
86
+ author="NaniSoft"
87
+ readingTime="6 min read"
88
+ tags={[{ label: 'market-data' }, { label: 'collector' }]}
89
+ trailLabels={{ previous: 'Previous', next: 'Next' }}
90
+ trailLabel="More posts"
91
+ previous={{ title: 'One feed to research them all', href: '#one-feed' }}
92
+ next={{ title: 'Multi-agent research, mapped', href: '#multi-agent' }}
93
+ indexLink={{ title: 'All posts', href: '#all' }}
94
+ >
95
+ <p>
96
+ The claim sounds like a feature. It is a measurement with a timestamp on
97
+ it, and it started as a suspicion that it was impossible.
98
+ </p>
99
+ <h2>The assumption that was backwards</h2>
100
+ <p>
101
+ Going in, full-chain-per-minute looked like a streaming problem. It was a
102
+ counting problem, and the count had to fit in a minute.
103
+ </p>
104
+ <ul>
105
+ <li>Every strike, call and put.</li>
106
+ <li>Open interest, implied volatility and the Greeks.</li>
107
+ <li>Index futures, index spot and the volatility index.</li>
108
+ </ul>
109
+ </BlogPostPage>
110
+ )
111
+ }
112
+ ```
113
+
114
+ ## Props
115
+
116
+ **`title`** `string` · required · default: — — The post's title. It is the page's `h1`, so the page has exactly one and it is the post's name.
117
+ **`description`** `string` · optional · default: — — The standfirst: one or two sentences under the title that say what the post is about. It is a prop rather than the first paragraph of the body because a reader deciding whether to read needs it before the body starts, and because it is what an index, a feed and a search result show.
118
+ **`date`** `string` · required · default: — — The publication date, already formatted for display, and the machine value for the `time` element's `dateTime`. Both are props, and the machine one is required: `dateTime` is what a reader's browser, a feed reader and a search engine use to order posts, and a formatted string is none of those.
119
+ **`dateTime`** `string` · required · default: — — The date in a machine-readable form, `YYYY-MM-DD`, carried on the `time` element. Required, and deliberately not derived from `date`: parsing a formatted date back into an ISO one is a guess about a locale, and a guess in a `datetime` attribute is a wrong date in a feed.
120
+ **`author`** `ReactNode` · optional · default: — — Who wrote the post. A name, or a link to an author page.
121
+ **`tags`** `readonly BlogPostTag[]` · optional · default: — — The tags on the post, in the order a reader should meet them. Omit them for a post with none: an empty row of badges is a row of nothing.
122
+ **`readingTime`** `string` · optional · default: — — How long the post takes to read, in the consumer's own words.
123
+ **`children`** `ReactNode` · required · default: — — The post's body. Any flow content: paragraphs, headings, lists, code, images, tables. `Prose` holds it to the reading measure and to the block rhythm.
124
+ **`previous`** `BlogPostNeighbour` · optional · default: — — The post before this one and the post after it, for the trail at the foot of the page. Omit the half there is no neighbour for; a "previous" link with no previous post is a link to nothing.
125
+ **`next`** `BlogPostNeighbour` · optional · default: —
126
+ **`trailLabels`** `BlogPostTrailLabels` · required · default: — — The two words the trail renders above the neighbouring posts' titles. Required whenever either neighbour is passed, because they are words a reader reads and a Page that ships no copy ships no reader-facing copy either.
127
+ **`trailLabel`** `string` · required · default: — — The accessible name of the trail between the neighbouring posts. It is required whenever either neighbour is passed, because it is a word a reader hears and a Page that ships no copy ships no reader-facing copy either. "More posts" is the Page's own phrasing and a site that files posts by release calls the same landmark something else.
128
+ **`indexLink`** `BlogPostNeighbour` · optional · default: — — The way back to the archive the post is filed in, rendered as one call to action under the trail. A prop rather than a hardcoded "back to the index", because the words and the route belong to the site that publishes the post and a Page that held either would be a Page making a claim about a consumer's own site.
129
+ **`footer`** `ReactNode` · optional · default: — — A slot for what comes after the trail: a related-posts grid, a newsletter form, a call to action. A slot rather than a prop because every one of those is a Block, and a Page that took a Block per prop would be a Page whose interface lists the whole catalogue.
130
+ **`className`** `string` · optional · default: — — Layout only, exactly as on every Component. Changing a Prism-owned visual property from here is prohibited.
131
+
132
+ ## Composition
133
+
134
+ **`exports`** `BlogPostPage` — the public runtime names this Page promises
135
+ **`source`** `packages/ui/src/pages/blog-post-page/index.tsx` — the file this Page is defined in
136
+ **`dependencies`** `Badge, CtaLink, Prose, Section` — the public Prism exports this item composes
@@ -0,0 +1,241 @@
1
+ # DashboardPage
2
+
3
+ A full dashboard screen composing the app shell, a page header, a KPI row and a data table.
4
+
5
+ ```tsx
6
+ import { DashboardPage } from '@nanisoft/prism-ui/pages/dashboard-page'
7
+ ```
8
+
9
+ ## Overview
10
+
11
+ DashboardPage is a complete application screen. It composes the shell, a page
12
+ header, a row of key figures and a data table, and it owns the sequence and the
13
+ spacing only. The frame comes from the shell, the screen's name from the header,
14
+ the summary from the stats and the working set from the table.
15
+
16
+ The page composes these blocks:
17
+
18
+ | Region | Block | Role |
19
+ | --- | --- | --- |
20
+ | Frame | AppShell01 | the navigation rail, the top bar and the mobile navigation sheet |
21
+ | Header | PageHeader01 | the trail, the page title and the page actions |
22
+ | Summary | Stats01 | the key figures above the table |
23
+ | Working set | DataTable01 | the toolbar, filters, selection and pagination |
24
+
25
+ Navigation, rows, columns, labels and every callback are props. The page fetches
26
+ nothing and imports no router, so the consumer owns the links and the data. The
27
+ table's search, page, filters and selection are controlled by the consumer, so
28
+ the same page can be driven by a server render or a client cache.
29
+
30
+ ## Guidelines
31
+
32
+ ### When to use
33
+
34
+ Use DashboardPage for the landing screen of an application, where a reader checks
35
+ the state of a workspace and acts on a collection. It works when the navigation
36
+ is stable across screens and the table is the reason the reader came.
37
+
38
+ ### When not to use
39
+
40
+ Do not use it for a single-purpose screen with no collection. A dashboard with an
41
+ empty table is a header and a stat row around nothing; compose the header and the
42
+ stats directly instead.
43
+
44
+ Do not use it for a read-only report. The table's toolbar promises search,
45
+ filters and selection; a screen that does not offer them should not render the
46
+ toolbar.
47
+
48
+ ### Best practices
49
+
50
+ - Pass the same navigation node to both the rail and the sheet. AppShell01 renders
51
+ it twice, so keep ids in the navigation unique or avoid ids there.
52
+ - Keep the page's own controls in the header, not the table toolbar. The header
53
+ action belongs to the screen; the toolbar belongs to the rows.
54
+ - Own the table state at the call site. Pass `page`, `searchValue`, `filterValues`
55
+ and `selectedIds`, and reset the page to one when the search or a filter
56
+ changes, so the view and the data never disagree.
57
+ - Give every row a stable `getRowId`. An index-based id loses the selection when
58
+ the page changes.
59
+ - Pass every string the table needs in `labels`. The page supplies none of them.
60
+
61
+ ### Content guidelines
62
+
63
+ - The title names the screen: "Overview", not "View overview".
64
+ - A stat label is a noun phrase in sentence case, and the figure keeps one unit.
65
+ - A table column header is a short noun and a row action is a verb phrase.
66
+
67
+ **The dashboard page, with the shell, a header, a KPI row and a driven table.**
68
+
69
+ ```tsx
70
+ 'use client'
71
+
72
+ import { useState } from 'react'
73
+
74
+ import { DashboardPage } from '@nanisoft/prism-ui/pages/dashboard-page'
75
+
76
+ const LINKS = [
77
+ { href: '#dashboard', label: 'Dashboard', current: true },
78
+ { href: '#projects', label: 'Projects', current: false },
79
+ { href: '#team', label: 'Team', current: false },
80
+ ]
81
+
82
+ const ACCOUNTS = [
83
+ { id: 'northwind', name: 'Northwind', plan: 'Team', status: 'Active', seats: '24' },
84
+ { id: 'contoso', name: 'Contoso', plan: 'Business', status: 'Active', seats: '86' },
85
+ { id: 'fabrikam', name: 'Fabrikam', plan: 'Team', status: 'Paused', seats: '12' },
86
+ { id: 'adventure', name: 'Adventure Works', plan: 'Starter', status: 'Active', seats: '4' },
87
+ { id: 'litware', name: 'Litware', plan: 'Business', status: 'Active', seats: '140' },
88
+ { id: 'proseware', name: 'Proseware', plan: 'Starter', status: 'Paused', seats: '7' },
89
+ ]
90
+
91
+ const PAGE_SIZE = 3
92
+
93
+ /** The dashboard page, with the shell, a header, a KPI row and a driven table. */
94
+ export default function DashboardPageDemo() {
95
+ const [query, setQuery] = useState('')
96
+ const [filters, setFilters] = useState<Record<string, string | undefined>>({})
97
+ const [selected, setSelected] = useState<string[]>([])
98
+ const [page, setPage] = useState(1)
99
+
100
+ const filtered = ACCOUNTS.filter((row) => {
101
+ const matchesQuery = row.name.toLowerCase().includes(query.toLowerCase())
102
+ const matchesStatus = !filters.status || row.status === filters.status
103
+ return matchesQuery && matchesStatus
104
+ })
105
+ const pageCount = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE))
106
+ const pageRows = filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE)
107
+
108
+ return (
109
+ <DashboardPage
110
+ shell={{
111
+ navigationLabel: 'Primary',
112
+ brand: <span className="text-sm font-semibold">Northwind</span>,
113
+ breadcrumbs: [{ label: 'Dashboard', href: '#dashboard' }, { label: 'Overview' }],
114
+ actions: (
115
+ <span className="bg-muted text-muted-foreground rounded-full px-3 py-1 text-xs font-medium">
116
+ AR
117
+ </span>
118
+ ),
119
+ navigation: (
120
+ <ul className="flex flex-col gap-1 text-sm">
121
+ {LINKS.map((link) => (
122
+ <li key={link.href}>
123
+ <a
124
+ href={link.href}
125
+ className={
126
+ link.current
127
+ ? 'bg-accent text-accent-foreground block rounded-md px-3 py-2 font-medium'
128
+ : 'text-muted-foreground hover:text-foreground block rounded-md px-3 py-2'
129
+ }
130
+ >
131
+ {link.label}
132
+ </a>
133
+ </li>
134
+ ))}
135
+ </ul>
136
+ ),
137
+ }}
138
+ header={{
139
+ breadcrumbs: [{ label: 'Dashboard', href: '#dashboard' }, { label: 'Overview' }],
140
+ title: 'Overview',
141
+ description: 'Accounts, usage and the people on this workspace.',
142
+ actions: [{ label: 'New account' }],
143
+ }}
144
+ stats={{
145
+ eyebrow: 'This month',
146
+ title: 'Delivery',
147
+ headingLevel: 'h2',
148
+ stats: [
149
+ { label: 'Active accounts', value: '1,284', delta: 12, hint: 'vs last month' },
150
+ { label: 'On track', value: '47.2%', delta: -3, hint: 'vs last month' },
151
+ { label: 'Open issues', value: '138' },
152
+ ],
153
+ }}
154
+ table={{
155
+ title: 'Accounts',
156
+ description: 'Every workspace you can manage.',
157
+ columns: [
158
+ { id: 'name', header: 'Account', label: 'Account', cell: (row) => String(row.name) },
159
+ { id: 'plan', header: 'Plan', label: 'Plan', cell: (row) => String(row.plan) },
160
+ { id: 'status', header: 'Status', label: 'Status', cell: (row) => String(row.status) },
161
+ {
162
+ id: 'seats',
163
+ header: 'Seats',
164
+ label: 'Seats',
165
+ className: 'text-right',
166
+ cell: (row) => String(row.seats),
167
+ },
168
+ ],
169
+ rows: pageRows,
170
+ getRowId: (row) => String(row.id),
171
+ selectable: true,
172
+ selectedIds: selected,
173
+ onSelectedIdsChange: setSelected,
174
+ searchValue: query,
175
+ onSearchChange: (value) => {
176
+ setQuery(value)
177
+ setPage(1)
178
+ },
179
+ filters: [
180
+ {
181
+ id: 'status',
182
+ label: 'Status',
183
+ options: [
184
+ { label: 'Active', value: 'Active' },
185
+ { label: 'Paused', value: 'Paused' },
186
+ ],
187
+ },
188
+ ],
189
+ filterValues: filters,
190
+ onFilterChange: (id, value) => {
191
+ setFilters((previous) => ({ ...previous, [id]: value }))
192
+ setPage(1)
193
+ },
194
+ page,
195
+ pageCount,
196
+ onPageChange: setPage,
197
+ rowActions: () => [
198
+ { label: 'View details' },
199
+ { label: 'Rename' },
200
+ { label: 'Delete', variant: 'destructive' },
201
+ ],
202
+ labels: {
203
+ search: 'Search accounts',
204
+ filters: 'Filters',
205
+ reset: 'Reset filters',
206
+ columns: 'Columns',
207
+ viewColumns: 'View',
208
+ selectAll: 'Select every account on this page',
209
+ selectRow: 'Select this account',
210
+ rowActions: 'Account actions',
211
+ previous: 'Previous',
212
+ next: 'Next',
213
+ page: (value) => `Go to page ${value}`,
214
+ selectedCount: (count) => `${count} selected`,
215
+ empty: 'No accounts match the current search and filters.',
216
+ },
217
+ }}
218
+ />
219
+ )
220
+ }
221
+ ```
222
+
223
+ ## Props
224
+
225
+ **`shell`** `Omit<ComponentProps<typeof AppShell01>, 'children'>` · required · default: — — The frame: the navigation rail, the top bar and the mobile navigation sheet. Navigation, actions and the rail slots are passed as nodes, so the page never imports a router.
226
+ **`header`** `ComponentProps<typeof PageHeader01>` · required · default: — — The page title band.
227
+ **`stats`** `ComponentProps<typeof Stats01>` · required · default: — — The key-figure row above the table.
228
+ **`table`** `ComponentProps<typeof DataTable01>` · required · default: — — The main table, with its rows, columns, labels and callbacks.
229
+
230
+ ## Composition
231
+
232
+ **`exports`** `DashboardPage` — the public runtime names this Page promises
233
+ **`source`** `packages/ui/src/pages/dashboard-page/index.tsx` — the file this Page is defined in
234
+ **`dependencies`** `AppShell01, DataTable01, PageHeader01, Stats01` — the public Prism exports this item composes
235
+
236
+ ## Blocks
237
+
238
+ - [AppShell01](https://prism.nanisoft.com/blocks/app-shell-01.md)
239
+ - [DataTable01](https://prism.nanisoft.com/blocks/data-table-01.md)
240
+ - [PageHeader01](https://prism.nanisoft.com/blocks/page-header-01.md)
241
+ - [Stats01](https://prism.nanisoft.com/blocks/stats-01.md)
@@ -0,0 +1,199 @@
1
+ # MarketingPage
2
+
3
+ A complete marketing page composing a hero, a feature grid, a KPI row, a plan comparison and a closing call to action.
4
+
5
+ ```tsx
6
+ import { MarketingPage } from '@nanisoft/prism-ui/pages/marketing-page'
7
+ ```
8
+
9
+ ## Overview
10
+
11
+ MarketingPage is a complete marketing screen. It composes five blocks in reading
12
+ order and passes each one its own content, so the page adds the sequence and
13
+ nothing else: an opening hero, a capability grid, a row of key figures, a plan
14
+ comparison and a closing call to action.
15
+
16
+ The page composes these blocks:
17
+
18
+ | Section | Block | Role |
19
+ | --- | --- | --- |
20
+ | Opening | Hero01 | the headline, the supporting line and one or two actions |
21
+ | Capabilities | FeatureGrid01 | a grid of short feature blurbs, with or without icons |
22
+ | Figures | Stats01 | two to four key numbers, each with an optional delta |
23
+ | Plans | Pricing01 | a plan comparison with one highlighted tier |
24
+ | Closing | Cta01 | the final call to action on a filled surface |
25
+
26
+ Every string, number, icon, price and action is a prop. The page ships no sample
27
+ copy and fetches nothing. It is a product-agnostic screen model, so a consumer
28
+ installs it, passes their own content and owns the links. It is not this site's
29
+ own landing route: that page is site-owned and composes the same blocks at the
30
+ call site.
31
+
32
+ ## Guidelines
33
+
34
+ ### When to use
35
+
36
+ Use MarketingPage for a public landing route whose job is to introduce a product
37
+ and move a reader to one next step. It works when the five sections form one
38
+ argument from the headline to the closing action.
39
+
40
+ ### When not to use
41
+
42
+ Do not use it inside an authenticated screen. A hero and a plan comparison carry
43
+ marketing weight that competes with the task an application screen exists to
44
+ support; compose the blocks you need directly instead.
45
+
46
+ Do not use it as a documentation page. Guides and item pages have their own
47
+ shell, and a marketing hero would sit above content that is already scoped.
48
+
49
+ Do not use it when the sequence is not the five sections. The page is a fixed
50
+ composition; a different order or a missing section is a page the consumer
51
+ builds from the blocks.
52
+
53
+ ### Best practices
54
+
55
+ - Pass every block its own props. The page does not merge or default them, so
56
+ omitting a section's content renders an empty section rather than a fallback.
57
+ - Keep the hero as the only `h1`. The page defaults `headingLevel` to `h1`; when
58
+ it is embedded under a heading the consumer already owns, pass one level
59
+ deeper and the rest of the outline follows.
60
+ - Put the primary action on the hero and the secondary action on the closing
61
+ band, not both in each. One decision per band keeps the page from reading as
62
+ two pitches.
63
+ - Keep the figures real. Stats01 renders exactly the numbers it is given, so the
64
+ page makes no claim the consumer did not make.
65
+
66
+ ### Content guidelines
67
+
68
+ - The hero headline states an outcome, not a feature name.
69
+ - The feature titles are the four or five things the product does; the bodies
70
+ are one sentence each.
71
+ - The closing line is a verb phrase and the action names what the reader gets.
72
+
73
+ **The marketing page, composed with placeholder sections.**
74
+
75
+ ```tsx
76
+ import { Blocks, Palette, ShieldCheck, Zap } from 'lucide-react'
77
+
78
+ import { MarketingPage } from '@nanisoft/prism-ui/pages/marketing-page'
79
+
80
+ /**
81
+ * The marketing page, composed with placeholder sections.
82
+ *
83
+ * The page owns the sequence; the demo owns the strings, the numbers and the
84
+ * feature icons. It renders at `h3` because the documentation page already owns
85
+ * an `h1`, which is the same reason a block demo nests its heading.
86
+ */
87
+ export default function MarketingPageDemo() {
88
+ return (
89
+ <MarketingPage
90
+ headingLevel="h3"
91
+ hero={{
92
+ eyebrow: 'Preview',
93
+ title: 'A headline that fits on two lines',
94
+ description:
95
+ 'One or two sentences of supporting copy, so the measure and the spacing around it can be judged.',
96
+ actions: [
97
+ { label: 'Primary action' },
98
+ { label: 'Secondary', variant: 'outline' },
99
+ ],
100
+ }}
101
+ features={{
102
+ eyebrow: 'Preview',
103
+ title: 'Section heading',
104
+ description: 'An optional line that explains what the set below it has in common.',
105
+ features: [
106
+ {
107
+ icon: Palette,
108
+ title: 'First item',
109
+ body: 'A short sentence describing the first thing in the set.',
110
+ },
111
+ {
112
+ icon: Blocks,
113
+ title: 'Second item',
114
+ body: 'A short sentence describing the second thing in the set.',
115
+ },
116
+ {
117
+ icon: Zap,
118
+ title: 'Third item',
119
+ body: 'A short sentence describing the third thing in the set.',
120
+ },
121
+ {
122
+ icon: ShieldCheck,
123
+ title: 'Fourth item',
124
+ body: 'A short sentence describing the fourth thing in the set.',
125
+ },
126
+ ],
127
+ }}
128
+ stats={{
129
+ eyebrow: 'Preview',
130
+ title: 'Section heading',
131
+ stats: [
132
+ { label: 'First metric', value: '1,284', delta: 12, hint: 'vs last month' },
133
+ { label: 'Second metric', value: '47.2%', delta: -3, hint: 'vs last month' },
134
+ { label: 'Third metric', value: '2.4d', delta: 0, hint: 'vs last quarter' },
135
+ { label: 'Fourth metric', value: '138' },
136
+ ],
137
+ }}
138
+ pricing={{
139
+ title: 'Section heading',
140
+ plans: [
141
+ {
142
+ name: 'First plan',
143
+ price: '$0',
144
+ period: ' / month',
145
+ body: 'One line on who it is for.',
146
+ features: ['First included line', 'Second included line', 'Third included line'],
147
+ cta: 'Choose',
148
+ },
149
+ {
150
+ name: 'Second plan',
151
+ price: '$24',
152
+ period: ' / month',
153
+ body: 'One line on who it is for.',
154
+ features: ['Everything in the first plan', 'One extra line', 'Another extra line'],
155
+ cta: 'Choose',
156
+ featured: true,
157
+ },
158
+ {
159
+ name: 'Third plan',
160
+ price: '$68',
161
+ period: ' / month',
162
+ body: 'One line on who it is for.',
163
+ features: ['Everything in the second plan', 'One extra line', 'Another extra line'],
164
+ cta: 'Choose',
165
+ },
166
+ ],
167
+ }}
168
+ cta={{
169
+ title: 'A closing line, two lines at most',
170
+ description: 'A sentence that says what happens next, then one action.',
171
+ action: { label: 'Do the thing', href: '/components/button' },
172
+ }}
173
+ />
174
+ )
175
+ }
176
+ ```
177
+
178
+ ## Props
179
+
180
+ **`hero`** `ComponentProps<typeof Hero01>` · required · default: — — The opening band: an optional eyebrow, the headline and one or two actions.
181
+ **`features`** `ComponentProps<typeof FeatureGrid01>` · required · default: — — The capability grid below the hero.
182
+ **`stats`** `ComponentProps<typeof Stats01>` · required · default: — — The key-figure row.
183
+ **`pricing`** `ComponentProps<typeof Pricing01>` · required · default: — — The plan comparison.
184
+ **`cta`** `ComponentProps<typeof Cta01>` · required · default: — — The closing call to action.
185
+ **`headingLevel`** `HeadingLevel` · optional · default: — — The heading level for the hero. Defaults to `h1`, because a page owns the top of the document outline. A consumer that embeds the page under a heading it already owns passes one level deeper.
186
+
187
+ ## Composition
188
+
189
+ **`exports`** `MarketingPage` — the public runtime names this Page promises
190
+ **`source`** `packages/ui/src/pages/marketing-page/index.tsx` — the file this Page is defined in
191
+ **`dependencies`** `Cta01, FeatureGrid01, Hero01, Pricing01, Stats01` — the public Prism exports this item composes
192
+
193
+ ## Blocks
194
+
195
+ - [Cta01](https://prism.nanisoft.com/blocks/cta-01.md)
196
+ - [FeatureGrid01](https://prism.nanisoft.com/blocks/feature-grid-01.md)
197
+ - [Hero01](https://prism.nanisoft.com/blocks/hero-01.md)
198
+ - [Pricing01](https://prism.nanisoft.com/blocks/pricing-01.md)
199
+ - [Stats01](https://prism.nanisoft.com/blocks/stats-01.md)