@ciderpress/ui 1.0.0-rc.10 → 1.0.0-rc.11

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 (80) hide show
  1. package/dist/head/css/loader-apple.css +1 -1
  2. package/dist/head/css/loader-dots.css +1 -1
  3. package/dist/head/css/themes/amber.css +1 -1
  4. package/dist/head/css/themes/arcade.css +1 -1
  5. package/dist/head/css/themes/grannysmith.css +1 -1
  6. package/dist/head/css/themes/honeycrisp.css +1 -1
  7. package/dist/head/css/themes/midnight.css +1 -1
  8. package/dist/head/css/themes/mulled.css +1 -1
  9. package/dist/node.mjs +408 -15
  10. package/dist/theme/components/home/cta.css +15 -0
  11. package/dist/theme/components/home/cta.tsx +10 -2
  12. package/dist/theme/components/home/feature-card.css +1 -1
  13. package/dist/theme/components/home/feature-card.tsx +3 -2
  14. package/dist/theme/components/home/feature.tsx +34 -31
  15. package/dist/theme/components/home/hero-demo.css +11 -0
  16. package/dist/theme/components/home/home-visual.tsx +226 -0
  17. package/dist/theme/components/home/layout.tsx +350 -171
  18. package/dist/theme/components/home/split.css +46 -19
  19. package/dist/theme/components/home/split.tsx +28 -8
  20. package/dist/theme/components/home/tabs.css +299 -0
  21. package/dist/theme/components/home/tabs.tsx +351 -0
  22. package/dist/theme/components/home/trust-strip.css +41 -0
  23. package/dist/theme/components/home/trust-strip.tsx +168 -11
  24. package/dist/theme/components/home/workspaces.tsx +171 -69
  25. package/dist/theme/components/workspaces/card.css +4 -0
  26. package/dist/theme/components/workspaces/card.tsx +3 -3
  27. package/dist/theme/components/workspaces/grid.tsx +18 -6
  28. package/dist/theme/index.tsx +5 -0
  29. package/dist/theme/lib/rich-text-parse.ts +740 -0
  30. package/dist/theme/lib/rich-text.test.tsx +342 -0
  31. package/dist/theme/lib/rich-text.tsx +86 -0
  32. package/dist/theme/styles/overrides/fonts.css +7 -4
  33. package/dist/theme/styles/overrides/home.css +11 -2
  34. package/dist/theme/styles/overrides/rspress.css +10 -5
  35. package/dist/theme/styles/overrides/tokens.css +41 -23
  36. package/dist/theme/styles/rich-text.css +106 -0
  37. package/dist/theme/styles/themes/amber.css +32 -2
  38. package/dist/theme/styles/themes/arcade.css +16 -1
  39. package/dist/theme/styles/themes/grannysmith.css +32 -2
  40. package/dist/theme/styles/themes/honeycrisp.css +37 -7
  41. package/dist/theme/styles/themes/midnight.css +16 -1
  42. package/dist/theme/styles/themes/mulled.css +54 -9
  43. package/package.json +3 -3
  44. package/src/theme/components/home/cta.css +15 -0
  45. package/src/theme/components/home/cta.tsx +10 -2
  46. package/src/theme/components/home/feature-card.css +1 -1
  47. package/src/theme/components/home/feature-card.tsx +3 -2
  48. package/src/theme/components/home/feature.tsx +34 -31
  49. package/src/theme/components/home/hero-demo.css +11 -0
  50. package/src/theme/components/home/home-visual.tsx +226 -0
  51. package/src/theme/components/home/layout.tsx +350 -171
  52. package/src/theme/components/home/split.css +46 -19
  53. package/src/theme/components/home/split.tsx +28 -8
  54. package/src/theme/components/home/tabs.css +299 -0
  55. package/src/theme/components/home/tabs.tsx +351 -0
  56. package/src/theme/components/home/trust-strip.css +41 -0
  57. package/src/theme/components/home/trust-strip.tsx +168 -11
  58. package/src/theme/components/home/workspaces.tsx +171 -69
  59. package/src/theme/components/workspaces/card.css +4 -0
  60. package/src/theme/components/workspaces/card.tsx +3 -3
  61. package/src/theme/components/workspaces/grid.tsx +18 -6
  62. package/src/theme/index.tsx +5 -0
  63. package/src/theme/lib/rich-text-parse.ts +740 -0
  64. package/src/theme/lib/rich-text.test.tsx +342 -0
  65. package/src/theme/lib/rich-text.tsx +86 -0
  66. package/src/theme/styles/overrides/fonts.css +7 -4
  67. package/src/theme/styles/overrides/home.css +11 -2
  68. package/src/theme/styles/overrides/rspress.css +10 -5
  69. package/src/theme/styles/overrides/tokens.css +41 -23
  70. package/src/theme/styles/rich-text.css +106 -0
  71. package/src/theme/styles/themes/amber.css +32 -2
  72. package/src/theme/styles/themes/arcade.css +16 -1
  73. package/src/theme/styles/themes/grannysmith.css +32 -2
  74. package/src/theme/styles/themes/honeycrisp.css +37 -7
  75. package/src/theme/styles/themes/midnight.css +16 -1
  76. package/src/theme/styles/themes/mulled.css +54 -9
  77. package/dist/theme/components/home/hero-demo-custom.tsx +0 -116
  78. package/dist/theme/components/home/split-visual-custom.tsx +0 -26
  79. package/src/theme/components/home/hero-demo-custom.tsx +0 -116
  80. package/src/theme/components/home/split-visual-custom.tsx +0 -26
@@ -1,69 +1,181 @@
1
- import type { HomeShowcaseConfig } from '@ciderpress/config'
2
- import { useFrontmatter } from '@rspress/core/runtime'
1
+ import type { TruncateConfig } from '@ciderpress/config'
3
2
  import { match, P } from 'massaman/match'
4
3
  import type React from 'react'
5
4
 
6
5
  import { useCiderpress } from '../../hooks/use-ciderpress'
7
- import type { WorkspaceGroupData } from '../../hooks/use-ciderpress'
6
+ import type { WorkspaceCardData, WorkspaceGroupData } from '../../hooks/use-ciderpress'
7
+ import { renderRichText } from '../../lib/rich-text.tsx'
8
8
  import { WorkspaceCard } from '../workspaces/card'
9
9
  import { WorkspaceGrid } from '../workspaces/grid'
10
10
 
11
+ /**
12
+ * A single card compiled from `showcase.source` — the same shape as a
13
+ * workspace card, minus the grouping.
14
+ */
15
+ export type ShowcaseCard = WorkspaceCardData
16
+
11
17
  interface FrontmatterWorkspacesHeading {
12
18
  readonly label?: string
13
19
  readonly title?: string
14
- readonly subtitle?: string
20
+ readonly body?: string
21
+ }
22
+
23
+ interface HomeWorkspacesProps {
24
+ readonly heading?: FrontmatterWorkspacesHeading
25
+ /**
26
+ * Cards compiled from an explicit `showcase.source` path list. When
27
+ * present they render as one ungrouped grid; otherwise the grouped
28
+ * workspace data from the theme config is used.
29
+ */
30
+ readonly cards?: readonly ShowcaseCard[]
31
+ readonly columns?: 1 | 2 | 3 | 4
32
+ readonly truncate?: TruncateConfig
15
33
  }
16
34
 
35
+ const DEFAULT_COLUMNS: 1 | 2 | 3 | 4 = 2
36
+
17
37
  /**
18
- * Smart orchestrator that reads workspace data from themeConfig
19
- * and renders workspace groups with the correct card component per type.
20
- *
21
- * Optionally renders a top-level heading above the workspace groups
22
- * when `home.showcase.heading` is configured. The sync engine writes
23
- * it into frontmatter as `workspacesHeading`.
38
+ * Showcase grid block. Renders either the cards compiled from an
39
+ * explicit `showcase.source` path list (one ungrouped grid) or the
40
+ * grouped workspace data serialized from the repo at build time. Grid
41
+ * layout — heading, columns, truncation — comes from props.
24
42
  *
25
- * @returns React element with workspace groups or null
43
+ * @param props - Optional heading, cards, column count, and truncation limits.
44
+ * @returns React element with the showcase grid, or null when empty.
26
45
  */
27
- export function HomeWorkspaces(): React.ReactElement | null {
28
- const { workspaces, home } = useCiderpress()
29
- const gridConfig = home && home.showcase
30
- const { frontmatter } = useFrontmatter()
31
- const heading = (frontmatter as Record<string, unknown>).workspacesHeading as
32
- | FrontmatterWorkspacesHeading
33
- | undefined
46
+ export function HomeWorkspaces(props: HomeWorkspacesProps): React.ReactElement | null {
47
+ const { workspaces } = useCiderpress()
48
+ const { heading, cards, columns, truncate } = props
49
+ const resolvedColumns = match(columns)
50
+ .with(P.nonNullable, (c) => c)
51
+ .otherwise(() => DEFAULT_COLUMNS)
34
52
 
35
- return match(workspaces)
53
+ // A `cards` key means the block declared an explicit `showcase.source`
54
+ // — an empty array is "that source resolved to nothing", not "fall back
55
+ // to the workspace deck". Only an absent key takes the fallback.
56
+ const body = match(cards)
36
57
  .with(
37
- P.when((w): w is readonly WorkspaceGroupData[] => Array.isArray(w) && w.length > 0),
38
- (groups) => (
39
- <div className="cp-workspace-section">
40
- <hr className="cp-divider" />
41
- {match(heading)
42
- .with(undefined, () => null)
43
- .otherwise((h) => (
44
- <div className="cp-feature-section-head">
45
- {match(h.label)
46
- .with(undefined, () => null)
47
- .otherwise((e) => (
48
- <div className="cp-feature-section-head__eyebrow">{e}</div>
49
- ))}
50
- {match(h.title)
51
- .with(undefined, () => null)
52
- .otherwise((t) => (
53
- <h2 className="cp-feature-section-head__title">{t}</h2>
54
- ))}
55
- {match(h.subtitle)
56
- .with(undefined, () => null)
57
- .otherwise((s) => (
58
- <p className="cp-feature-section-head__sub">{s}</p>
59
- ))}
60
- </div>
61
- ))}
62
- {groups.map((group) => renderGroup(group, gridConfig))}
63
- </div>
64
- )
58
+ P.when((c): c is readonly ShowcaseCard[] => Array.isArray(c)),
59
+ (list) =>
60
+ match(list.length === 0)
61
+ .with(true, () => null)
62
+ .otherwise(() => [renderCardGrid(list, resolvedColumns, truncate)])
63
+ )
64
+ .otherwise(() =>
65
+ match(workspaces)
66
+ .with(
67
+ P.when((w): w is readonly WorkspaceGroupData[] => Array.isArray(w) && w.length > 0),
68
+ (groups) => groups.map((group, i) => renderGroup(group, resolvedColumns, truncate, i))
69
+ )
70
+ .otherwise(() => null)
65
71
  )
66
- .otherwise(() => null)
72
+
73
+ return match(body)
74
+ .with(null, () => null)
75
+ .otherwise((content) => (
76
+ <div className="cp-workspace-section">
77
+ {/* The rule separates this band from the heading above it, so a
78
+ block with no heading of its own would render it as an orphan
79
+ line floating over the cards. */}
80
+ {match(renderHeading(heading))
81
+ .with(P.nullish, () => null)
82
+ .otherwise((head) => (
83
+ <>
84
+ <hr className="cp-divider" />
85
+ {head}
86
+ </>
87
+ ))}
88
+ {content}
89
+ </div>
90
+ ))
91
+ }
92
+
93
+ /**
94
+ * Render the block heading, or nothing when every field is absent.
95
+ *
96
+ * @private
97
+ * @param heading - Flat heading trio from the block
98
+ * @returns Heading element, or null
99
+ */
100
+ function renderHeading(
101
+ heading: FrontmatterWorkspacesHeading | undefined
102
+ ): React.ReactElement | null {
103
+ if (heading === undefined) {
104
+ return null
105
+ }
106
+ const { label, title, body } = heading
107
+ const hasContent = [label, title, body].some((value) => typeof value === 'string')
108
+ if (!hasContent) {
109
+ return null
110
+ }
111
+ return (
112
+ <div className="cp-feature-section-head">
113
+ {match(label)
114
+ .with(P.string, (e) => (
115
+ <div className="cp-feature-section-head__eyebrow">{renderRichText(e)}</div>
116
+ ))
117
+ .otherwise(() => null)}
118
+ {match(title)
119
+ .with(P.string, (t) => (
120
+ <h2 className="cp-feature-section-head__title">{renderRichText(t)}</h2>
121
+ ))
122
+ .otherwise(() => null)}
123
+ {match(body)
124
+ .with(P.string, (s) => <p className="cp-feature-section-head__sub">{renderRichText(s)}</p>)
125
+ .otherwise(() => null)}
126
+ </div>
127
+ )
128
+ }
129
+
130
+ /**
131
+ * Render an ungrouped grid of cards compiled from `showcase.source`.
132
+ *
133
+ * @private
134
+ * @param cards - Resolved showcase cards
135
+ * @param columns - Column count for the grid
136
+ * @param truncate - Optional line-clamp limits for card text
137
+ * @returns Ungrouped workspace grid element
138
+ */
139
+ function renderCardGrid(
140
+ cards: readonly ShowcaseCard[],
141
+ columns: 1 | 2 | 3 | 4,
142
+ truncate: TruncateConfig | undefined
143
+ ): React.ReactElement {
144
+ return (
145
+ <WorkspaceGrid key="showcase-source" columns={columns}>
146
+ {cards.map((card, i) => renderCard(card, i, truncate))}
147
+ </WorkspaceGrid>
148
+ )
149
+ }
150
+
151
+ /**
152
+ * Render a single showcase / workspace card.
153
+ *
154
+ * @private
155
+ * @param card - Card data
156
+ * @param index - Array index for key generation
157
+ * @param truncate - Optional line-clamp limits for card text
158
+ * @returns Workspace card element
159
+ */
160
+ function renderCard(
161
+ card: WorkspaceCardData,
162
+ index: number,
163
+ truncate: TruncateConfig | undefined
164
+ ): React.ReactElement {
165
+ return (
166
+ <WorkspaceCard
167
+ key={`${card.title}-${index}`}
168
+ title={card.title}
169
+ href={card.href}
170
+ icon={card.icon}
171
+ scope={card.scope}
172
+ description={card.description}
173
+ tags={card.tags}
174
+ badge={card.badge}
175
+ titleLines={truncate && truncate.title}
176
+ descriptionLines={truncate && truncate.description}
177
+ />
178
+ )
67
179
  }
68
180
 
69
181
  /**
@@ -71,37 +183,27 @@ export function HomeWorkspaces(): React.ReactElement | null {
71
183
  *
72
184
  * @private
73
185
  * @param group - Workspace group data with heading, description, and cards
74
- * @param gridConfig - Optional grid layout config for columns and truncation
186
+ * @param columns - Column count for the grid
187
+ * @param truncate - Optional line-clamp limits for card text
188
+ * @param index - Position in the group list, to disambiguate the key
75
189
  * @returns Workspace grid element
76
190
  */
77
191
  function renderGroup(
78
192
  group: WorkspaceGroupData,
79
- gridConfig: HomeShowcaseConfig | undefined
193
+ columns: 1 | 2 | 3 | 4,
194
+ truncate: TruncateConfig | undefined,
195
+ index: number
80
196
  ): React.ReactElement {
81
- const titleLines = gridConfig && gridConfig.truncate && gridConfig.truncate.title
82
- const descLines = gridConfig && gridConfig.truncate && gridConfig.truncate.description
83
-
84
197
  return (
198
+ // Heading alone is not unique — two configured workspace groups may
199
+ // share a title, which collided into one React key.
85
200
  <WorkspaceGrid
86
- key={group.heading}
201
+ key={`${group.heading}-${index}`}
87
202
  heading={group.heading}
88
203
  description={group.description}
89
- columns={gridConfig && gridConfig.columns}
204
+ columns={columns}
90
205
  >
91
- {group.cards.map((card, i) => (
92
- <WorkspaceCard
93
- key={`${card.title}-${i}`}
94
- title={card.title}
95
- href={card.href}
96
- icon={card.icon}
97
- scope={card.scope}
98
- description={card.description}
99
- tags={card.tags}
100
- badge={card.badge}
101
- titleLines={titleLines}
102
- descriptionLines={descLines}
103
- />
104
- ))}
206
+ {group.cards.map((card, i) => renderCard(card, i, truncate))}
105
207
  </WorkspaceGrid>
106
208
  )
107
209
  }
@@ -69,6 +69,10 @@
69
69
  font-size: 15px;
70
70
  font-weight: 700;
71
71
  color: var(--cp-c-text-1);
72
+ /* Presentational lowercase. Done here rather than in JS because the
73
+ title may carry inline markup — lowercasing the source would also
74
+ lowercase link targets and class names inside it. */
75
+ text-transform: lowercase;
72
76
  }
73
77
 
74
78
  .cp-workspace-card__desc {
@@ -1,6 +1,7 @@
1
1
  import { match, P } from 'massaman/match'
2
2
  import type React from 'react'
3
3
 
4
+ import { renderRichText } from '../../lib/rich-text.tsx'
4
5
  import { Card } from '../shared/card'
5
6
  import { CardIcon } from '../shared/card-icon'
6
7
  import type { CardIconInput } from '../shared/resolve-card-icon'
@@ -65,7 +66,6 @@ export function WorkspaceCard({
65
66
  titleLines,
66
67
  descriptionLines,
67
68
  }: WorkspaceCardProps): React.ReactElement {
68
- const name = title.toLowerCase()
69
69
  const resolved = resolveCardIcon(icon)
70
70
 
71
71
  const iconEl = match(resolved)
@@ -93,7 +93,7 @@ export function WorkspaceCard({
93
93
  className={clampClass('cp-workspace-card__desc', descriptionLines)}
94
94
  style={clampStyle(descriptionLines)}
95
95
  >
96
- {d}
96
+ {renderRichText(d)}
97
97
  </span>
98
98
  ))
99
99
  .otherwise(() => null)
@@ -122,7 +122,7 @@ export function WorkspaceCard({
122
122
  className={clampClass('cp-workspace-card__name', titleLines)}
123
123
  style={clampStyle(titleLines)}
124
124
  >
125
- {name}
125
+ {renderRichText(title)}
126
126
  </span>
127
127
  </div>
128
128
  </div>
@@ -1,16 +1,21 @@
1
+ import { match, P } from 'massaman/match'
1
2
  import type React from 'react'
2
3
 
4
+ import { renderRichText } from '../../lib/rich-text.tsx'
5
+
3
6
  import './card.css'
4
7
 
5
8
  export interface WorkspaceGridProps {
6
9
  /**
7
- * Section heading (e.g. "Apps", "Packages").
10
+ * Section heading (e.g. "Apps", "Packages"). Omitted for an ungrouped
11
+ * grid, such as a showcase block with an explicit `source` list.
8
12
  */
9
- readonly heading: string
13
+ readonly heading?: string
10
14
  /**
11
- * Brief description rendered below the heading.
15
+ * Brief description rendered below the heading. Omitted for an
16
+ * ungrouped grid.
12
17
  */
13
- readonly description: string
18
+ readonly description?: string
14
19
  /**
15
20
  * Number of grid columns.
16
21
  */
@@ -38,8 +43,15 @@ export function WorkspaceGrid({
38
43
 
39
44
  return (
40
45
  <>
41
- <h2>{heading}</h2>
42
- <p className="cp-workspace-section__desc">{description}</p>
46
+ {/* Rich text, matching the card titles and descriptions this grid
47
+ wraps — a group heading was the one string in the block that
48
+ showed its `**markers**` literally. */}
49
+ {match(heading)
50
+ .with(P.string, (h) => <h2>{renderRichText(h)}</h2>)
51
+ .otherwise(() => null)}
52
+ {match(description)
53
+ .with(P.string, (d) => <p className="cp-workspace-section__desc">{renderRichText(d)}</p>)
54
+ .otherwise(() => null)}
43
55
  <div className="cp-workspace-grid" style={style}>
44
56
  {children}
45
57
  </div>
@@ -23,6 +23,7 @@ import './styles/layers.css'
23
23
  import './styles/overrides/fonts.css'
24
24
  import './styles/overrides/tokens.css'
25
25
  import './styles/overrides/rspress.css'
26
+ import './styles/rich-text.css'
26
27
  // Theme color palettes — scoped via [data-cp-theme][data-cp-variant] selectors.
27
28
  // `mulled.css` ships first because it is `DEFAULT_THEME_NAME` — its rules emit
28
29
  // the `:root { ... }` FOUC fallback that paints before JS hydrates the
@@ -106,6 +107,10 @@ export { TrustStrip } from './components/home/trust-strip'
106
107
  export type { TrustStripProps } from './components/home/trust-strip'
107
108
  export { HomeSplit } from './components/home/split'
108
109
  export type { SplitProps, SplitAction } from './components/home/split'
110
+ export { HomeTabs } from './components/home/tabs'
111
+ export { RichText, renderRichText } from './lib/rich-text.tsx'
112
+ export { hasAccentMarker, toPlainText } from './lib/rich-text-parse.ts'
113
+ export type { HomeTabsProps, HomeTabEntry, TabsAction } from './components/home/tabs'
109
114
  export { CTA } from './components/home/cta'
110
115
  export type { CTAProps } from './components/home/cta'
111
116
  export { WorkspaceCard } from './components/workspaces/card'