@nanisoft/prism-llms 0.3.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.
- package/README.md +88 -0
- package/THIRD-PARTY-NOTICES.md +48 -0
- package/dist/data.json +3998 -375
- package/dist/demo-graph.d.ts +21 -12
- package/dist/demo-graph.js +37 -21
- package/dist/extractor.d.ts +24 -17
- package/dist/extractor.js +269 -153
- package/dist/index.d.ts +12 -7
- package/dist/index.js +9 -7
- package/dist/llms-full.txt +7988 -1989
- package/dist/llms.txt +97 -91
- package/dist/markdown.d.ts +69 -23
- package/dist/markdown.js +131 -72
- package/dist/md/blocks/app-shell-01.md +135 -0
- package/dist/md/blocks/auth-form-01.md +135 -0
- package/dist/md/blocks/cta-01.md +88 -0
- package/dist/md/blocks/data-table-01.md +196 -0
- package/dist/md/blocks/feature-grid-01.md +92 -0
- package/dist/md/blocks/hero-01.md +90 -0
- package/dist/md/blocks/instrument-panel-01.md +109 -0
- package/dist/md/blocks/logo-strip-01.md +97 -0
- package/dist/md/blocks/note-grid-01.md +102 -0
- package/dist/md/blocks/page-header-01.md +97 -0
- package/dist/md/blocks/pricing-01.md +91 -0
- package/dist/md/blocks/process-rail-01.md +106 -0
- package/dist/md/blocks/product-grid-01.md +113 -0
- package/dist/md/blocks/settings-panel-01.md +148 -0
- package/dist/md/blocks/site-footer.md +111 -0
- package/dist/md/blocks/site-header.md +119 -0
- package/dist/md/blocks/stack-grid-01.md +108 -0
- package/dist/md/blocks/stats-01.md +89 -0
- package/dist/md/blocks/status-ledger-01.md +121 -0
- package/dist/md/changelogs/prism-llms.md +206 -0
- package/dist/md/changelogs/prism-mcp-server.md +88 -0
- package/dist/md/changelogs/prism-tokens.md +126 -0
- package/dist/md/changelogs/prism-ui.md +429 -0
- package/dist/md/components/accordion.md +108 -0
- package/dist/md/components/alert.md +90 -7
- package/dist/md/components/avatar.md +89 -7
- package/dist/md/components/badge.md +74 -7
- package/dist/md/components/breadcrumb.md +84 -7
- package/dist/md/components/button.md +90 -7
- package/dist/md/components/card.md +90 -7
- package/dist/md/components/checkbox.md +101 -7
- package/dist/md/components/cta-link.md +98 -0
- package/dist/md/components/diagram.md +124 -0
- package/dist/md/components/dialog.md +128 -0
- package/dist/md/components/dropdown-menu.md +125 -0
- package/dist/md/components/fact-list.md +85 -0
- package/dist/md/components/field.md +116 -0
- package/dist/md/components/input.md +86 -7
- package/dist/md/components/kbd.md +77 -0
- package/dist/md/components/pagination.md +95 -7
- package/dist/md/components/popover.md +95 -7
- package/dist/md/components/product-mark.md +123 -0
- package/dist/md/components/product-switcher.md +88 -0
- package/dist/md/components/progress.md +88 -7
- package/dist/md/components/prose.md +91 -0
- package/dist/md/components/radio-group.md +111 -0
- package/dist/md/components/section.md +91 -0
- package/dist/md/components/select.md +143 -7
- package/dist/md/components/separator.md +77 -0
- package/dist/md/components/skeleton.md +71 -7
- package/dist/md/components/slider.md +90 -7
- package/dist/md/components/switch.md +104 -7
- package/dist/md/components/table.md +104 -7
- package/dist/md/components/tabs.md +105 -7
- package/dist/md/components/textarea.md +101 -0
- package/dist/md/components/tooltip.md +95 -7
- package/dist/md/components/typography.md +82 -7
- package/dist/md/content/about-content.md +36 -0
- package/dist/md/content/audience.md +43 -0
- package/dist/md/content/glossary.md +69 -0
- package/dist/md/content/product-vocabulary.md +53 -0
- package/dist/md/content/voice.md +52 -0
- package/dist/md/content/writing-mechanics.md +58 -0
- package/dist/md/foundation/colors.md +10 -0
- package/dist/md/foundation/iconography.md +14 -0
- package/dist/md/foundation/motion.md +10 -0
- package/dist/md/foundation/radii.md +9 -0
- package/dist/md/foundation/shadows.md +9 -0
- package/dist/md/foundation/spacings.md +8 -0
- package/dist/md/foundation/typography.md +9 -0
- package/dist/md/foundation/variables.md +9 -0
- package/dist/md/overview/architecture.md +76 -0
- package/dist/md/overview/brand.md +41 -0
- package/dist/md/overview/composition.md +77 -0
- package/dist/md/overview/quickstart.md +97 -0
- package/dist/md/overview/theming.md +89 -0
- package/dist/md/overview/using-llms.md +83 -0
- package/dist/md/pages/auth-page.md +149 -0
- package/dist/md/pages/blog-post-page.md +136 -0
- package/dist/md/pages/dashboard-page.md +241 -0
- package/dist/md/pages/marketing-page.md +199 -0
- package/dist/md/pages/not-found-page.md +105 -0
- package/dist/md/pages/settings-page.md +206 -0
- package/dist/prism-skill.md +50 -0
- package/dist/store.d.ts +203 -0
- package/dist/store.js +317 -0
- package/package.json +29 -26
- package/dist/demo-graph.d.ts.map +0 -1
- package/dist/demo-graph.js.map +0 -1
- package/dist/extractor.d.ts.map +0 -1
- package/dist/extractor.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/markdown.d.ts.map +0 -1
- package/dist/markdown.js.map +0 -1
- package/dist/md/blocks/component-demo.md +0 -19
- package/dist/md/blocks/page-header.md +0 -19
- package/dist/md/components/affix.md +0 -14
- package/dist/md/components/anchor.md +0 -14
- package/dist/md/components/auto-complete.md +0 -14
- package/dist/md/components/back-top.md +0 -14
- package/dist/md/components/border-beam.md +0 -14
- package/dist/md/components/calendar.md +0 -14
- package/dist/md/components/carousel.md +0 -14
- package/dist/md/components/cascader.md +0 -14
- package/dist/md/components/col.md +0 -14
- package/dist/md/components/collapse.md +0 -14
- package/dist/md/components/color-picker.md +0 -14
- package/dist/md/components/date-picker.md +0 -14
- package/dist/md/components/descriptions.md +0 -14
- package/dist/md/components/display-title.md +0 -20
- package/dist/md/components/divider.md +0 -14
- package/dist/md/components/drawer.md +0 -14
- package/dist/md/components/dropdown.md +0 -14
- package/dist/md/components/empty.md +0 -14
- package/dist/md/components/flex.md +0 -14
- package/dist/md/components/float-button.md +0 -14
- package/dist/md/components/form.md +0 -14
- package/dist/md/components/grid.md +0 -14
- package/dist/md/components/image.md +0 -14
- package/dist/md/components/input-number.md +0 -14
- package/dist/md/components/layout.md +0 -14
- package/dist/md/components/list.md +0 -14
- package/dist/md/components/listy.md +0 -14
- package/dist/md/components/masonry.md +0 -14
- package/dist/md/components/mentions.md +0 -14
- package/dist/md/components/menu.md +0 -14
- package/dist/md/components/modal.md +0 -14
- package/dist/md/components/popconfirm.md +0 -14
- package/dist/md/components/qrcode.md +0 -14
- package/dist/md/components/radio.md +0 -14
- package/dist/md/components/rate.md +0 -14
- package/dist/md/components/result.md +0 -14
- package/dist/md/components/row.md +0 -14
- package/dist/md/components/segmented.md +0 -14
- package/dist/md/components/space.md +0 -14
- package/dist/md/components/spin.md +0 -14
- package/dist/md/components/splitter.md +0 -14
- package/dist/md/components/statistic.md +0 -14
- package/dist/md/components/steps.md +0 -14
- package/dist/md/components/tag.md +0 -14
- package/dist/md/components/time-picker.md +0 -14
- package/dist/md/components/timeline.md +0 -14
- package/dist/md/components/tour.md +0 -14
- package/dist/md/components/transfer.md +0 -14
- package/dist/md/components/tree-select.md +0 -14
- package/dist/md/components/tree.md +0 -14
- package/dist/md/components/upload.md +0 -14
- package/dist/md/components/watermark.md +0 -14
- package/dist/md/docs/brand.md +0 -41
- package/dist/md/pages/blog-layout.md +0 -19
- package/dist/md/pages/docs-shell.md +0 -23
- package/dist/md/theme/blue-dark.md +0 -127
- package/dist/md/theme/blue-light.md +0 -129
- package/dist/md/theme/green-dark.md +0 -127
- package/dist/md/theme/green-light.md +0 -129
- package/dist/md/theme/lavender-dark.md +0 -127
- package/dist/md/theme/lavender-light.md +0 -129
- package/dist/md/theme/peach-dark.md +0 -127
- package/dist/md/theme/peach-light.md +0 -129
- package/dist/md/theme/rose-dark.md +0 -127
- 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)
|