@tekki-forge/core 0.2.0 → 0.3.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/dist/app.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import type { AnyField } from "./fields.js";
2
- import type { DashboardWidget } from "./dashboard.js";
2
+ import type { DashboardConfig } from "./dashboard.js";
3
3
  import type { EntityIcon } from "./icons.js";
4
4
  import type { PluginDefinition } from "./plugin.js";
5
+ import type { SiteConfig } from "./site.js";
5
6
  /**
6
7
  * A permission rule lists who may perform an operation. Tokens:
7
8
  * - a role name, e.g. "admin", "manager", "member"
@@ -143,14 +144,15 @@ export interface MultiTenantConfig {
143
144
  enabled: boolean;
144
145
  model?: "organization";
145
146
  }
146
- export type UIStyle = "simple" | "shadow" | "neumorphism" | "glassmorphism" | "skeuomorphism" | "material" | "brutalist" | "editorial" | "aurora" | "terminal" | "clay";
147
+ export type UIStyle = "simple" | "shadow" | "neumorphism" | "glassmorphism" | "skeuomorphism" | "material" | "brutalist" | "editorial" | "aurora" | "terminal" | "clay" | "corporate" | "soft" | "luxury" | "retro" | "neon" | "swiss" | "nature" | "cyber" | "minimal" | "playful";
147
148
  /** Curated looks (style + colours + font + corners + mode + layout). Explicit theme keys override them. */
148
- export type ThemePreset = "midnight" | "sandstone" | "ocean" | "forest" | "sunset" | "graphite" | "candy" | "paper" | "phosphor";
149
+ export type ThemePreset = "midnight" | "sandstone" | "ocean" | "forest" | "sunset" | "graphite" | "candy" | "paper" | "phosphor" | "boardroom" | "sakura" | "noir" | "arcade" | "ember" | "atelier" | "matcha" | "circuit" | "lagoon" | "orchid" | "harbor" | "blueprint";
149
150
  /** Typeface pairing (headings + body), loaded from Google Fonts at runtime; "system" loads nothing. */
150
- export type ThemeFont = "inter" | "geist" | "plex" | "grotesk" | "serif" | "rounded" | "mono" | "system";
151
+ export type ThemeFont = "inter" | "geist" | "plex" | "grotesk" | "serif" | "rounded" | "mono" | "system" | "elegant" | "pixel" | "tech" | "playful";
151
152
  export interface ThemeConfig {
152
153
  /**
153
- * Start from a curated look: midnight, sandstone, ocean, forest, sunset, graphite, candy, paper, phosphor.
154
+ * Start from a curated look: midnight, sandstone, ocean, forest, sunset, graphite, candy, paper, phosphor,
155
+ * boardroom, sakura, noir, arcade, ember, atelier, matcha, circuit, lagoon, orchid, harbor, blueprint.
154
156
  * Any other key set here overrides the preset's value.
155
157
  */
156
158
  preset?: ThemePreset;
@@ -167,6 +169,16 @@ export interface ThemeConfig {
167
169
  * - aurora soft animated gradient backdrop from your colours, glassy cards
168
170
  * - terminal dark-first, monospace, phosphor accents
169
171
  * - clay claymorphism: puffy rounded surfaces with inner highlights
172
+ * - corporate sober and dense, enterprise blue-grey, crisp tables
173
+ * - soft pastel, rounded and airy, gentle shadows
174
+ * - luxury deep tones, gold accents, serif display, thin rules (dark by default)
175
+ * - retro 80s/90s desktop: bevelled windows, pixel headings, readable body
176
+ * - neon dark, glowing accents and crisp borders (glow off for reduced motion / forced colours)
177
+ * - swiss strict grid, big type, red accent, no shadows
178
+ * - nature earthy tones, organic shapes, paper texture
179
+ * - cyber angled corners, HUD accents, dark-first
180
+ * - minimal almost no chrome: whitespace and hairlines
181
+ * - playful bold colours, hand-drawn edges, bouncy hover
170
182
  */
171
183
  style?: UIStyle;
172
184
  colors?: {
@@ -182,7 +194,8 @@ export interface ThemeConfig {
182
194
  radius?: "none" | "small" | "medium" | "large" | "full";
183
195
  /**
184
196
  * Typeface pairing: inter, geist, plex (IBM Plex), grotesk (Space Grotesk headings), serif (Fraunces headings),
185
- * rounded (Nunito), mono (JetBrains Mono), system (no web font). Default: the style's own, else system.
197
+ * rounded (Nunito), mono (JetBrains Mono), elegant (Cormorant Garamond headings), pixel (Silkscreen headings),
198
+ * tech (Chakra Petch headings), playful (Fredoka + Nunito), system (no web font). Default: the style's own, else system.
186
199
  */
187
200
  font?: ThemeFont;
188
201
  /** Navigation: a sidebar (default), a top bar, or a compact icon rail (tooltips on hover/focus, expandable with its toggle). */
@@ -193,6 +206,20 @@ export interface ThemeConfig {
193
206
  logo?: string;
194
207
  /** Short line shown on the sign-in pages' brand panel. */
195
208
  tagline?: string;
209
+ /** Page background made from the theme colours (CSS only, static under reduced motion). Default: plain (the style's own). */
210
+ background?: "plain" | "gradient" | "mesh" | "dots" | "grid" | "noise";
211
+ /** Card treatment. Default: the style's own. */
212
+ cards?: "flat" | "bordered" | "elevated" | "glass";
213
+ /** Button shape and fill. Default: the style's own. */
214
+ buttons?: "solid" | "soft" | "outline" | "pill";
215
+ /** Page and card transitions: none, subtle (default) or lively. Always none under prefers-reduced-motion. */
216
+ motion?: "none" | "subtle" | "lively";
217
+ /** Sidebar surface: light (the regular surface), dark, or brand (the primary colour). Default: the style's own. */
218
+ sidebar?: "light" | "dark" | "brand";
219
+ /** Navigation icons: outline (default) or duotone (tinted fill). */
220
+ iconStyle?: "outline" | "duotone";
221
+ /** Colours of charts, exposed as --forge-chart-1 … --forge-chart-8: theme (default), vivid, pastel or mono. */
222
+ chartPalette?: "theme" | "vivid" | "pastel" | "mono";
196
223
  }
197
224
  export interface NotificationRule {
198
225
  /**
@@ -236,16 +263,12 @@ export interface AppDefinition {
236
263
  };
237
264
  /** Client SDKs to generate. Default: TypeScript only (packages/sdk). */
238
265
  sdk?: SdkConfig;
239
- admin?: {
240
- enabled?: boolean;
241
- };
266
+ admin?: AdminConfig;
242
267
  theme?: ThemeConfig;
243
268
  /** Plugins such as storage(), jobs(), webhooks(), payments(). */
244
269
  plugins?: readonly PluginDefinition[];
245
- /** Widgets on the home page (metric, chart, list). Default: a count per entity. */
246
- dashboard?: {
247
- widgets: readonly DashboardWidget[];
248
- };
270
+ /** Widgets on the home page (metric, chart, list), optionally in tabs. Default: a count per entity. */
271
+ dashboard?: DashboardConfig;
249
272
  /** In-app (and optional email) notifications triggered by record events. */
250
273
  notifications?: readonly NotificationRule[];
251
274
  /**
@@ -261,6 +284,26 @@ export interface AppDefinition {
261
284
  * `true` builds one from the app's name, description, tagline and entities; an object overrides any part.
262
285
  */
263
286
  landing?: boolean | LandingConfig;
287
+ /**
288
+ * Feature flags with their default value, e.g. `{ newEditor: false }`. Global admins switch them at
289
+ * runtime in Administration → System settings; read them with `useFeatureFlag("newEditor")` in the UI
290
+ * and `ctx.use<FeatureFlags>("featureFlags")?.enabled("newEditor")` in extensions.
291
+ */
292
+ featureFlags?: Record<string, boolean>;
293
+ /**
294
+ * A public website: pages made of sections (hero, features, pricing, FAQ, blog…), a header menu and a footer.
295
+ * Seeded into the database and editable in the admin (Content); signed-out visitors of `/` see the home page.
296
+ */
297
+ site?: SiteConfig;
298
+ }
299
+ export interface AdminConfig {
300
+ /** The administration area (/admin) and its API. Default: true. */
301
+ enabled?: boolean;
302
+ /**
303
+ * Built-in support inbox: a "Help & support" form in the app (tickets with a message thread) answered
304
+ * by global admins in Administration → Support. Default: true.
305
+ */
306
+ support?: boolean;
264
307
  }
265
308
  export interface LandingFeature {
266
309
  title: string;
package/dist/app.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AA4DA,MAAM,UAAU,MAAM,CAAC,aAAgD,EAAE;IACvE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,UAAU,EAAE,CAAC;AAC9C,CAAC;AAqED,MAAM,UAAU,MAAM,CAAC,UAA6C;IAClE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,UAAU,EAAE,CAAC;AAC9C,CAAC;AAiMD,MAAM,UAAU,SAAS,CAAC,UAAyB;IACjD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,UAAU,EAAE,CAAC;AAC3C,CAAC"}
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AA6DA,MAAM,UAAU,MAAM,CAAC,aAAgD,EAAE;IACvE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,UAAU,EAAE,CAAC;AAC9C,CAAC;AAqED,MAAM,UAAU,MAAM,CAAC,UAA6C;IAClE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,UAAU,EAAE,CAAC;AAC9C,CAAC;AA+QD,MAAM,UAAU,SAAS,CAAC,UAAyB;IACjD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,UAAU,EAAE,CAAC;AAC3C,CAAC"}
@@ -4,6 +4,16 @@
4
4
  */
5
5
  export type WidgetFilter = Record<string, string | number | boolean | readonly (string | number)[]>;
6
6
  export type Aggregate = "count" | "sum" | "avg" | "min" | "max";
7
+ /** Date buckets of a chart axis. */
8
+ export type DateBucket = "day" | "week" | "month" | "year";
9
+ /** Calendar periods (weeks start on Monday). */
10
+ export type Period = "week" | "month" | "quarter" | "year";
11
+ /**
12
+ * How numbers are written. Default: like the field (money keeps its currency, percentage fields show "%").
13
+ * "compact" = 12.5K, "currency" = money (the field's currency, else `currency`), "percent" = a fraction as % (0.25 → 25%).
14
+ */
15
+ export type NumberFormat = "number" | "compact" | "currency" | "percent";
16
+ export type ChartKind = "bar" | "line" | "area" | "donut" | "pie" | "stackedBar" | "funnel" | "heatmap" | "table";
7
17
  interface WidgetBase {
8
18
  title: string;
9
19
  entity: string;
@@ -16,34 +26,102 @@ interface WidgetBase {
16
26
  */
17
27
  visibleTo?: readonly string[];
18
28
  }
19
- export interface MetricWidget extends WidgetBase {
29
+ /** One side of a ratio metric. */
30
+ export interface MetricSource {
31
+ entity: string;
32
+ where?: WidgetFilter;
33
+ aggregate?: Aggregate;
34
+ field?: string;
35
+ /** Date or datetime field that places records in a period (`period`, `compare`). Default "createdAt". */
36
+ dateField?: string;
37
+ }
38
+ export interface MetricWidget extends Omit<WidgetBase, "entity"> {
20
39
  readonly type: "metric";
40
+ /** Required, except for ratio metrics (each side names its own entity). */
41
+ entity?: string;
21
42
  aggregate?: Aggregate;
22
43
  /** Numeric field for sum/avg/min/max. */
23
44
  field?: string;
24
45
  /**
25
46
  * Show the change versus the previous period ("+12% vs previous month") with a sparkline of the last 12 periods.
26
47
  * Periods are rolling windows ending now (month = the last month, compared with the month before).
27
- * Default "month" when the entity has a date to compare on, false when `where` already uses relative dates ($today, $startOfMonth…).
48
+ * Default "month" when the entity has a date to compare on, false when `where` already uses relative dates ($today, $startOfMonth…) or `period` is set.
28
49
  */
29
- compare?: "week" | "month" | "quarter" | "year" | false;
50
+ compare?: Period | false;
30
51
  /** Date or datetime field that places records in a period. Default "createdAt". */
31
52
  dateField?: string;
32
- /** Which direction is good news (colours the change). Default "up-is-good". */
53
+ /** Which direction is good news (colours the change and the goal). Default "up-is-good". */
33
54
  trend?: "up-is-good" | "down-is-good";
55
+ /**
56
+ * Count only the current calendar period (this week/month/quarter/year, in the dashboard's timezone), the whole
57
+ * period: records dated later in it (upcoming bookings, due dates) count too. Without `period` the value is all-time.
58
+ */
59
+ period?: Period;
60
+ /** A target: shows progress towards it (e.g. 50000 with period "month" = "50K this month"). */
61
+ goal?: number;
62
+ /**
63
+ * A ratio of two aggregates, e.g. a conversion rate: { numerator: { entity: "Deal", where: { stage: "won" } }, denominator: { entity: "Lead" } }.
64
+ * Both sides use the viewer's permissions; the widget is hidden from viewers who can't read either side. Shown as a percentage by default.
65
+ */
66
+ ratio?: {
67
+ numerator: MetricSource;
68
+ denominator: MetricSource;
69
+ };
70
+ format?: NumberFormat;
71
+ /** ISO currency code for `format: "currency"` when the field is not money. */
72
+ currency?: string;
34
73
  }
35
- export interface ChartWidget extends WidgetBase {
74
+ /** One line/bar series of a multi-series chart; each series can come from another entity. */
75
+ export interface ChartSeries {
76
+ entity: string;
77
+ /** Legend label. Default: the entity's plural label. */
78
+ label?: string;
79
+ where?: WidgetFilter;
80
+ aggregate?: Aggregate;
81
+ field?: string;
82
+ /** Date axis (`groupBy: { by }`): the date field that places this series' records. Default: the chart's groupBy field, else "createdAt". */
83
+ dateField?: string;
84
+ /** Category axis (`groupBy: "stage"`): the field this series groups by. Default: the chart's groupBy. */
85
+ groupBy?: string;
86
+ /** Value axis. Default: left, or right when the unit differs from the first series (count vs money). */
87
+ axis?: "left" | "right";
88
+ }
89
+ export interface ChartWidget extends Omit<WidgetBase, "entity"> {
36
90
  readonly type: "chart";
37
- chart: "bar" | "line" | "donut";
38
- /** Enum, boolean or many-to-one relation field — or a date field bucketed by day/week/month/year. */
39
- groupBy: string | {
40
- field: string;
41
- by: "day" | "week" | "month" | "year";
91
+ chart: ChartKind;
92
+ /** Required unless `series` is set. */
93
+ entity?: string;
94
+ /**
95
+ * Enum, boolean or many-to-one relation field — or a date field bucketed by day/week/month/year.
96
+ * Heatmaps: `{ field, by: "hour" }` (day of week × hour of day) or `{ field, by: "day" }` (one cell per day, a column per week).
97
+ * Multi-series date axis: `{ by: "month" }` (each series names its `dateField`).
98
+ */
99
+ groupBy?: string | {
100
+ field?: string;
101
+ by: DateBucket | "hour";
42
102
  };
103
+ /** A second enum, boolean or many-to-one field: one series per value (stackedBar, or grouped bar/line/area/table). */
104
+ stackBy?: string;
105
+ /** Several series (possibly from several entities) on a shared date axis or a shared category axis. */
106
+ series?: readonly ChartSeries[];
43
107
  aggregate?: Aggregate;
44
108
  field?: string;
45
109
  /** Max categories shown (the rest are grouped as "Other"). Default 8. */
46
110
  limit?: number;
111
+ /** Funnel: the enum values to show, in order. Default: every value, in declaration order. */
112
+ stages?: readonly string[];
113
+ /** Funnel: count records at a stage or any later one ("reached this stage"). Default false. */
114
+ cumulative?: boolean;
115
+ /** Plot height: sm 160px, md 220px (default), lg 320px. */
116
+ height?: "sm" | "md" | "lg";
117
+ /** Series/segment colours in order: hex ("#2563eb") or theme tokens ("primary", "accent", "success", "info", "danger", "chart-1"…"chart-8"). Default: the theme's chart palette. */
118
+ colors?: readonly string[];
119
+ /** Show the legend. Default: when there are several series (and for donut/pie). */
120
+ legend?: boolean;
121
+ /** Stack the series of a bar/area chart. Default: true for stackedBar, false otherwise. */
122
+ stacked?: boolean;
123
+ format?: NumberFormat;
124
+ currency?: string;
47
125
  }
48
126
  export interface ListWidget extends WidgetBase {
49
127
  readonly type: "list";
@@ -53,6 +131,19 @@ export interface ListWidget extends WidgetBase {
53
131
  columns?: readonly string[];
54
132
  }
55
133
  export type DashboardWidget = MetricWidget | ChartWidget | ListWidget;
134
+ /** A dashboard tab; `visibleTo` gives roles their own dashboards. */
135
+ export interface DashboardTab {
136
+ title: string;
137
+ widgets: readonly DashboardWidget[];
138
+ visibleTo?: readonly string[];
139
+ }
140
+ export interface DashboardConfig {
141
+ /** The widgets (one page). Use `tabs` instead for several pages. */
142
+ widgets?: readonly DashboardWidget[];
143
+ tabs?: readonly DashboardTab[];
144
+ /** IANA timezone for date buckets, heatmaps and goal periods (e.g. "Europe/Paris"). Default "UTC". */
145
+ timezone?: string;
146
+ }
56
147
  export declare const metric: (w: Omit<MetricWidget, "type">) => MetricWidget;
57
148
  export declare const chart: (w: Omit<ChartWidget, "type">) => ChartWidget;
58
149
  export declare const list: (w: Omit<ListWidget, "type">) => ListWidget;
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"AA0DA,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAA6B,EAAgB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;AAClG,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,CAA4B,EAAe,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;AAC9F,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,CAA2B,EAAc,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC"}
1
+ {"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"AAsJA,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAA6B,EAAgB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;AAClG,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,CAA4B,EAAe,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;AAC9F,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,CAA2B,EAAc,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC"}
package/dist/index.d.ts CHANGED
@@ -3,3 +3,4 @@ export * from "./app.js";
3
3
  export * from "./plugin.js";
4
4
  export * from "./dashboard.js";
5
5
  export * from "./icons.js";
6
+ export * from "./site.js";
package/dist/index.js CHANGED
@@ -3,4 +3,5 @@ export * from "./app.js";
3
3
  export * from "./plugin.js";
4
4
  export * from "./dashboard.js";
5
5
  export * from "./icons.js";
6
+ export * from "./site.js";
6
7
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC"}
package/dist/site.d.ts ADDED
@@ -0,0 +1,350 @@
1
+ /**
2
+ * The public website (`site` in forge.config.ts): pages made of sections from a built-in library, a header menu,
3
+ * a footer and a blog. Pages are seeded into the database and stay editable in the admin (Content).
4
+ */
5
+ /** A same-site path ("/pricing", "/sign-up", "#plans"), an http(s) URL, mailto: or tel:. */
6
+ export type Href = string;
7
+ /** An image: an http(s) URL, a file in apps/web/public ("/hero.png") or an upload ("/api/files/<id>", storage plugin). */
8
+ export type ImageUrl = string;
9
+ export interface SiteLink {
10
+ label: string;
11
+ href: Href;
12
+ }
13
+ /** Heading shown above most sections. */
14
+ export interface SectionHeading {
15
+ /** Small line above the title. */
16
+ eyebrow?: string;
17
+ title?: string;
18
+ subtitle?: string;
19
+ }
20
+ export type PostKind = "post" | "guide" | "webinar" | "case-study";
21
+ export type DataFilter = Record<string, string | number | boolean | readonly (string | number)[]>;
22
+ export interface HeroProps {
23
+ eyebrow?: string;
24
+ title: string;
25
+ subtitle?: string;
26
+ primary?: SiteLink;
27
+ secondary?: SiteLink;
28
+ /** Shown by the "split" variant (and behind the "minimal" one when set). */
29
+ image?: ImageUrl;
30
+ imageAlt?: string;
31
+ }
32
+ export interface FeatureItem {
33
+ title: string;
34
+ description?: string;
35
+ /** zap, shield, chart, sparkles, lock, globe, clock, bell, search, heart, rocket, users, calendar… */
36
+ icon?: string;
37
+ /** Shown by the "alternating" and "bento" variants. */
38
+ image?: ImageUrl;
39
+ imageAlt?: string;
40
+ href?: Href;
41
+ }
42
+ export interface FeaturesProps extends SectionHeading {
43
+ items: readonly FeatureItem[];
44
+ }
45
+ export interface LogosProps extends SectionHeading {
46
+ items: readonly {
47
+ name: string;
48
+ image?: ImageUrl;
49
+ href?: Href;
50
+ }[];
51
+ }
52
+ export interface TestimonialsProps extends SectionHeading {
53
+ items: readonly {
54
+ quote: string;
55
+ author: string;
56
+ role?: string;
57
+ avatar?: ImageUrl;
58
+ }[];
59
+ }
60
+ /**
61
+ * A number: static (`value: "99.9%"`) or live — `entity` (+ `aggregate`, `field`, `where`), computed like a
62
+ * dashboard metric. Live numbers need an entity whose read rule includes "public" (and no organization scope).
63
+ */
64
+ export interface StatItem {
65
+ label: string;
66
+ value?: string;
67
+ entity?: string;
68
+ aggregate?: "count" | "sum" | "avg" | "min" | "max";
69
+ field?: string;
70
+ where?: DataFilter;
71
+ prefix?: string;
72
+ suffix?: string;
73
+ }
74
+ export interface StatsProps extends SectionHeading {
75
+ items: readonly StatItem[];
76
+ }
77
+ export interface StepsProps extends SectionHeading {
78
+ items: readonly {
79
+ title: string;
80
+ description?: string;
81
+ }[];
82
+ }
83
+ export interface PricingPlan {
84
+ name: string;
85
+ price: string;
86
+ /** e.g. "/ month" */
87
+ period?: string;
88
+ description?: string;
89
+ features?: readonly string[];
90
+ cta?: SiteLink;
91
+ /** Emphasized card ("Most popular"). */
92
+ highlighted?: boolean;
93
+ }
94
+ export interface PricingProps extends SectionHeading {
95
+ /** "payments": the plans of the payments plugin (name, price, description, features). Default: "static" when `plans` is set. */
96
+ source?: "static" | "payments";
97
+ plans?: readonly PricingPlan[];
98
+ }
99
+ export interface FaqProps extends SectionHeading {
100
+ /** Answers are markdown. */
101
+ items: readonly {
102
+ question: string;
103
+ answer: string;
104
+ }[];
105
+ }
106
+ export interface CtaProps {
107
+ title: string;
108
+ subtitle?: string;
109
+ primary?: SiteLink;
110
+ secondary?: SiteLink;
111
+ }
112
+ export interface ComparisonProps extends SectionHeading {
113
+ /** Column headings, e.g. ["Acme", "Spreadsheets", "Others"]. */
114
+ columns: readonly string[];
115
+ /** One value per column: true/false shows a check or a dash, text is shown as is. */
116
+ rows: readonly {
117
+ feature: string;
118
+ values: readonly (string | boolean)[];
119
+ }[];
120
+ }
121
+ export interface IntegrationsProps extends SectionHeading {
122
+ items: readonly {
123
+ name: string;
124
+ description?: string;
125
+ image?: ImageUrl;
126
+ href?: Href;
127
+ }[];
128
+ }
129
+ export interface TeamProps extends SectionHeading {
130
+ items: readonly {
131
+ name: string;
132
+ role?: string;
133
+ bio?: string;
134
+ avatar?: ImageUrl;
135
+ href?: Href;
136
+ }[];
137
+ }
138
+ export interface GalleryProps extends SectionHeading {
139
+ items: readonly {
140
+ image: ImageUrl;
141
+ alt: string;
142
+ caption?: string;
143
+ }[];
144
+ }
145
+ /** A form whose messages are stored (built-in ContactMessage model) and notified to admins. */
146
+ export interface ContactProps extends SectionHeading {
147
+ /** Shown beside the form by the "split" variant. */
148
+ email?: string;
149
+ phone?: string;
150
+ address?: string;
151
+ /** Ask for a company name too. */
152
+ company?: boolean;
153
+ buttonLabel?: string;
154
+ successMessage?: string;
155
+ }
156
+ /** Email capture stored in the built-in NewsletterSubscriber model. */
157
+ export interface NewsletterProps extends SectionHeading {
158
+ buttonLabel?: string;
159
+ successMessage?: string;
160
+ }
161
+ export interface RichTextProps {
162
+ /** Markdown (sanitized: no raw HTML). */
163
+ body: string;
164
+ }
165
+ export interface ImageTextProps {
166
+ eyebrow?: string;
167
+ title?: string;
168
+ /** Markdown. */
169
+ body?: string;
170
+ image: ImageUrl;
171
+ imageAlt?: string;
172
+ cta?: SiteLink;
173
+ }
174
+ export interface VideoProps extends SectionHeading {
175
+ /** A YouTube or Vimeo link, or a video file (.mp4/.webm). */
176
+ url: string;
177
+ caption?: string;
178
+ }
179
+ export interface EmbedProps extends SectionHeading {
180
+ /** https URL whose host is allowed (YouTube, Vimeo, Google Maps/Docs/Forms, Calendly, Typeform, Loom, Figma… or site.embedHosts). */
181
+ url: string;
182
+ /** Accessible name of the frame. */
183
+ frameTitle: string;
184
+ /** Height in pixels (200–1200). Default 480. */
185
+ height?: number;
186
+ }
187
+ export interface BlogListProps extends SectionHeading {
188
+ /** Which posts: "post" (blog), "guide", "webinar" or "case-study". Default "post". */
189
+ kind?: PostKind;
190
+ /** Default 6. */
191
+ limit?: number;
192
+ tag?: string;
193
+ }
194
+ export interface SpacerProps {
195
+ }
196
+ export interface DividerProps {
197
+ }
198
+ /** Records of an entity whose read rule includes "public" (e.g. a product catalog or a job board). */
199
+ export interface EntityListProps extends SectionHeading {
200
+ entity: string;
201
+ /** Fields shown (default: the display field and the first list columns). */
202
+ fields?: readonly string[];
203
+ /** List-query filter, e.g. { status: "open" }. */
204
+ filter?: DataFilter;
205
+ /** e.g. "-createdAt" */
206
+ sort?: string;
207
+ /** Default 12, at most 48. */
208
+ limit?: number;
209
+ }
210
+ export interface SectionPropsByType {
211
+ hero: HeroProps;
212
+ features: FeaturesProps;
213
+ logos: LogosProps;
214
+ testimonials: TestimonialsProps;
215
+ stats: StatsProps;
216
+ steps: StepsProps;
217
+ pricing: PricingProps;
218
+ faq: FaqProps;
219
+ cta: CtaProps;
220
+ comparison: ComparisonProps;
221
+ integrations: IntegrationsProps;
222
+ team: TeamProps;
223
+ gallery: GalleryProps;
224
+ contact: ContactProps;
225
+ newsletter: NewsletterProps;
226
+ richText: RichTextProps;
227
+ imageText: ImageTextProps;
228
+ video: VideoProps;
229
+ embed: EmbedProps;
230
+ blogList: BlogListProps;
231
+ spacer: SpacerProps;
232
+ divider: DividerProps;
233
+ entityList: EntityListProps;
234
+ entityGrid: EntityListProps;
235
+ }
236
+ export interface SectionVariantsByType {
237
+ hero: "centered" | "split" | "brand" | "minimal";
238
+ features: "grid" | "alternating" | "bento";
239
+ logos: "strip" | "grid";
240
+ testimonials: "cards" | "single";
241
+ stats: "row" | "cards";
242
+ steps: "numbered" | "timeline";
243
+ pricing: "cards" | "compact";
244
+ faq: "accordion" | "columns";
245
+ cta: "banner" | "split" | "minimal";
246
+ comparison: "table" | "cards";
247
+ integrations: "grid" | "list";
248
+ team: "cards" | "compact";
249
+ gallery: "grid" | "masonry";
250
+ contact: "form" | "split";
251
+ newsletter: "inline" | "card";
252
+ richText: "default" | "narrow";
253
+ imageText: "left" | "right";
254
+ video: "default" | "wide";
255
+ embed: "default" | "wide";
256
+ blogList: "grid" | "list" | "featured";
257
+ spacer: "small" | "medium" | "large";
258
+ divider: "line" | "dots";
259
+ entityList: "table" | "list";
260
+ entityGrid: "cards" | "compact";
261
+ }
262
+ export type SectionType = keyof SectionPropsByType;
263
+ export interface SectionOptions<T extends SectionType> {
264
+ variant?: SectionVariantsByType[T];
265
+ /** Stable key (letters, digits, dashes) so admin edits survive reordering in the config. Default: the type (+ "-2", "-3"…). */
266
+ id?: string;
267
+ /** Kept but not shown. */
268
+ hidden?: boolean;
269
+ }
270
+ export interface SectionDefinition {
271
+ readonly __forge: "section";
272
+ type: string;
273
+ variant?: string;
274
+ id?: string;
275
+ hidden?: boolean;
276
+ props: Record<string, unknown>;
277
+ }
278
+ /**
279
+ * A section of a page: `section("hero", { variant: "split", title: "…", image: "/hero.png" })`.
280
+ * Props are typed per section type; see SectionPropsByType.
281
+ */
282
+ export declare function section<T extends SectionType>(type: T, props?: SectionPropsByType[T] & SectionOptions<T>): SectionDefinition;
283
+ /** Ready-made pages with sensible sections and placeholder copy from the app's name, description and entities. */
284
+ export type PageTemplate = "home" | "features" | "how-it-works" | "integrations" | "product-tour" | "use-cases" | "industries" | "pricing" | "customers" | "blog" | "guides" | "webinars" | "docs" | "about" | "team" | "careers" | "contact" | "security" | "privacy" | "compliance" | "login" | "signup" | "blank";
285
+ export interface PageSeo {
286
+ /** Default: the page title. */
287
+ title?: string;
288
+ description?: string;
289
+ /** Open Graph image. */
290
+ image?: ImageUrl;
291
+ }
292
+ export interface PageDefinition {
293
+ readonly __forge: "page";
294
+ /** Fills the title, path, SEO and sections you leave out. */
295
+ template?: PageTemplate;
296
+ title?: string;
297
+ /** "/", "/pricing", "/product/features" (lowercase, at most 3 segments). */
298
+ path?: string;
299
+ seo?: PageSeo;
300
+ sections?: readonly SectionDefinition[];
301
+ /** "draft" pages are only visible to site editors. Default "published". */
302
+ status?: "published" | "draft";
303
+ /**
304
+ * Place in the automatic header menu: `false` hides it, `{ group: "Resources" }` puts it in a dropdown,
305
+ * `{ label }` renames the entry, `{ footerOnly: true }` lists it only in the footer (legal pages).
306
+ * Default: grouped by the first path segment ("/product/x" → "Product").
307
+ */
308
+ nav?: false | {
309
+ group?: string;
310
+ label?: string;
311
+ footerOnly?: boolean;
312
+ };
313
+ /** Make the page a link to another address (the login and signup templates point at /sign-in and /sign-up). */
314
+ redirect?: Href;
315
+ }
316
+ export declare function page(definition?: Omit<PageDefinition, "__forge">): PageDefinition;
317
+ /** A header menu entry: a link (`href` or `page`, a key of site.pages) or a dropdown group (`items`). */
318
+ export interface SiteNavItem {
319
+ label?: string;
320
+ href?: Href;
321
+ page?: string;
322
+ description?: string;
323
+ items?: readonly SiteNavItem[];
324
+ }
325
+ export interface SiteFooter {
326
+ columns?: readonly {
327
+ title: string;
328
+ links: readonly SiteNavItem[];
329
+ }[];
330
+ /** Line under the columns, e.g. "Made in Dakar". */
331
+ note?: string;
332
+ /** Social profile links shown as icons. */
333
+ social?: readonly {
334
+ network: "x" | "github" | "linkedin" | "youtube" | "instagram" | "facebook" | "mastodon" | "discord";
335
+ href: string;
336
+ }[];
337
+ }
338
+ export interface SiteConfig {
339
+ pages: Record<string, PageDefinition>;
340
+ /** Header menu. Default: built from the pages (groups by first path segment or `nav.group`). */
341
+ nav?: readonly SiteNavItem[];
342
+ /** Footer columns. Default: the menu's groups and pages. */
343
+ footer?: SiteFooter;
344
+ /** Base path of each post kind (detail pages are `<path>/<slug>`, feed `<path>/rss.xml`). Default: the page listing that kind. */
345
+ blog?: Partial<Record<PostKind, string>>;
346
+ /** Extra hosts the `embed` section may show (besides YouTube, Vimeo, Google, Calendly, Typeform, Loom, Figma…). */
347
+ embedHosts?: readonly string[];
348
+ /** Roles (global user roles) that may edit the site in the admin. Default ["admin"]. */
349
+ editors?: readonly string[];
350
+ }
package/dist/site.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The public website (`site` in forge.config.ts): pages made of sections from a built-in library, a header menu,
3
+ * a footer and a blog. Pages are seeded into the database and stay editable in the admin (Content).
4
+ */
5
+ /**
6
+ * A section of a page: `section("hero", { variant: "split", title: "…", image: "/hero.png" })`.
7
+ * Props are typed per section type; see SectionPropsByType.
8
+ */
9
+ export function section(type, props) {
10
+ const { variant, id, hidden, ...rest } = (props ?? {});
11
+ return { __forge: "section", type, ...(variant !== undefined ? { variant } : {}), ...(id !== undefined ? { id } : {}), ...(hidden !== undefined ? { hidden } : {}), props: rest };
12
+ }
13
+ export function page(definition = {}) {
14
+ return { __forge: "page", ...definition };
15
+ }
16
+ //# sourceMappingURL=site.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"site.js","sourceRoot":"","sources":["../src/site.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAkRH;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAwB,IAAO,EAAE,KAAiD;IACvG,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,CAAC,KAAK,IAAI,EAAE,CAAkF,CAAC;IACxI,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACpL,CAAC;AAyDD,MAAM,UAAU,IAAI,CAAC,aAA8C,EAAE;IACnE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC;AAC5C,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekki-forge/core",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Forge schema DSL: defineApp, entity and field builders",
5
5
  "keywords": [
6
6
  "code-generator",