@soloworks/smking-next 0.13.0 → 0.14.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/CHANGELOG.md +20 -0
- package/package.json +1 -1
- package/src/components/smking-cms.tsx +318 -14
- package/src/lib/smking-cms-styles.ts +569 -0
- package/src/types.ts +19 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @soloworks/smking-next
|
|
2
2
|
|
|
3
|
+
## 0.14.0 — 2026-05-23
|
|
4
|
+
|
|
5
|
+
**Single-source CSS distribution + theme mode toggle.** `<SmkingCms>` now ships its own polished default styles by default — no customer CSS work needed. Customers who already use Tailwind + `@tailwindcss/typography` can flip to a prose-utility markup variant via the `mode` prop or `SMKING_CMS_THEME` env var.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Mode A (`css`, default)** — `<SmkingCms>` emits a `<style data-smking="cms-styles">` block once per render carrying the canonical SDK stylesheet. Class hooks `.smk-cms` / `.smk-block--*` / `.smk-hero` / `.smk-nav` / `.smk-carousel`. Customer themes via CSS variables on `:root` (`--smk-accent`, `--smk-font-serif`, etc).
|
|
10
|
+
- **Mode B (`tailwind-prose`)** — opt-in via `<SmkingCms mode="tailwind-prose" ... />` or `SMKING_CMS_THEME=tailwind-prose` env var. Wraps the article in `<article class="prose dark:prose-invert">` and emits Tailwind utility classes on every block (`not-prose` resets around hero/nav/carousel/search). Requires the customer's `@tailwindcss/typography` plugin and a tailwind safelist (the `@soloworks/smking-wizard` 0.4+ installs both automatically).
|
|
11
|
+
- **`CmsThemeMode` type export** — discriminated string literal `'css' | 'tailwind-prose'` for typed prop usage.
|
|
12
|
+
- **`SMKING_CMS_STYLES` constant** at `@soloworks/smking-next/lib/smking-cms-styles` — auto-generated from `packages/shared/src/styles/cms-styles.css` via the monorepo `pnpm sync:cms-styles` script. Customers who want to ship the SDK stylesheet as a standalone CSS file (CDN / static asset) can copy this constant verbatim.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- `SmkingCms` props now accept `mode?: CmsThemeMode` alongside the existing `CmsParams`. No breaking change — omitted prop defaults to `'css'` (legacy behavior plus the new inline style block).
|
|
17
|
+
|
|
18
|
+
### Pairs with
|
|
19
|
+
|
|
20
|
+
- `smking/laravel` v0.15.0 — identical theme-mode toggle on the Laravel side. Both SDKs ship byte-identical canonical CSS in Mode A; Mode B markup also mirrors across both runtimes.
|
|
21
|
+
- `@soloworks/smking-wizard` v0.4.0 — install-time Tailwind detect + theme-mode-select TUI step. When Tailwind is detected the wizard offers Mode B and installs `@tailwindcss/typography` + safelist patches automatically.
|
|
22
|
+
|
|
3
23
|
## 0.13.0 — 2026-05-22
|
|
4
24
|
|
|
5
25
|
**Dynamic CMS blocks — `nav-recent-posts`, `nav-taxonomy-list`, `nav-search` now render with real publish-time data.**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soloworks/smking-next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "AI-native SEO (AEO) for Next.js — auto-inject JSON-LD, FAQ, AI summary, and SEO metadata so AI crawlers (ChatGPT, Perplexity, Google AI) can cite your pages.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/sillyleo/smking/tree/main/packages/smking-next",
|
|
@@ -1,13 +1,31 @@
|
|
|
1
1
|
import { getCmsPage } from "../lib/cms-client";
|
|
2
|
+
import { SMKING_CMS_STYLES } from "../lib/smking-cms-styles";
|
|
2
3
|
import { SMKING_ELEMENTS_IIFE } from "../lib/smking-elements-iife";
|
|
3
4
|
import type {
|
|
4
5
|
Block,
|
|
5
6
|
CmsParams,
|
|
7
|
+
CmsThemeMode,
|
|
6
8
|
ModuleHeader,
|
|
7
9
|
NavSearchIndexEntry,
|
|
8
10
|
NavSnapshotEntry,
|
|
9
11
|
} from "../types";
|
|
10
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Pick the active theme mode for this render. Precedence:
|
|
15
|
+
* 1. explicit `mode` prop (per-render override)
|
|
16
|
+
* 2. SMKING_CMS_THEME env var (per-deployment default)
|
|
17
|
+
* 3. 'css' (Mode A — the SDK ships its own styles)
|
|
18
|
+
*
|
|
19
|
+
* Wizard auto-detect writes the env var on install; customer can still
|
|
20
|
+
* flip per-page via the prop.
|
|
21
|
+
*/
|
|
22
|
+
function resolveThemeMode(modeProp?: CmsThemeMode): CmsThemeMode {
|
|
23
|
+
if (modeProp === "css" || modeProp === "tailwind-prose") return modeProp;
|
|
24
|
+
const env = process.env.SMKING_CMS_THEME;
|
|
25
|
+
if (env === "tailwind-prose") return "tailwind-prose";
|
|
26
|
+
return "css";
|
|
27
|
+
}
|
|
28
|
+
|
|
11
29
|
/**
|
|
12
30
|
* Server Component that renders a published smking CMS page.
|
|
13
31
|
*
|
|
@@ -41,7 +59,7 @@ import type {
|
|
|
41
59
|
* Emits `<title>` / `<meta>` / `og:*` / canonical inline; React 19
|
|
42
60
|
* hoists them into `<head>` automatically.
|
|
43
61
|
*/
|
|
44
|
-
export async function SmkingCms(props: CmsParams) {
|
|
62
|
+
export async function SmkingCms(props: CmsParams & { mode?: CmsThemeMode }) {
|
|
45
63
|
const data = await getCmsPage(props);
|
|
46
64
|
if (!data || data.status !== "ready" || !data.page) return null;
|
|
47
65
|
|
|
@@ -51,6 +69,7 @@ export async function SmkingCms(props: CmsParams) {
|
|
|
51
69
|
// also sets these still overrides ours where present.
|
|
52
70
|
const seo = data.seo;
|
|
53
71
|
const blocks = data.page.blocks ?? [];
|
|
72
|
+
const mode = resolveThemeMode(props.mode);
|
|
54
73
|
// Emit the Web Component registration `<script>` once if any block
|
|
55
74
|
// needs `<smking-carousel>` / `<smking-slide>`. The IIFE is
|
|
56
75
|
// self-guarded so duplicate emits are harmless; gating here keeps
|
|
@@ -60,12 +79,24 @@ export async function SmkingCms(props: CmsParams) {
|
|
|
60
79
|
// inline IIFE (no Web Component registration needed) so we don't gate
|
|
61
80
|
// on it here.
|
|
62
81
|
const needsSmkingElements = blocks.some((b) => b.component === "carousel");
|
|
63
|
-
// IIFE
|
|
64
|
-
//
|
|
65
|
-
//
|
|
82
|
+
// IIFE / CSS sources are hand-authored package constants (not user
|
|
83
|
+
// data) — passing them through __html here is the standard React
|
|
84
|
+
// server-side inline-script / inline-style emit pattern.
|
|
66
85
|
const elementsScript: { __html: string } = { __html: SMKING_ELEMENTS_IIFE };
|
|
86
|
+
const cmsStylesPayload: { __html: string } = { __html: SMKING_CMS_STYLES };
|
|
87
|
+
// Mode A (`css`) — emit the canonical SDK stylesheet once. Mode B
|
|
88
|
+
// (`tailwind-prose`) opts the customer's Tailwind + Typography
|
|
89
|
+
// plugin into rendering and skips this entirely.
|
|
90
|
+
const shouldEmitInlineStyles = mode === "css";
|
|
67
91
|
return (
|
|
68
92
|
<>
|
|
93
|
+
{shouldEmitInlineStyles && (
|
|
94
|
+
<style
|
|
95
|
+
data-smking="cms-styles"
|
|
96
|
+
// eslint-disable-next-line react/no-danger -- CSS source is a package constant generated from packages/shared/src/styles/cms-styles.css.
|
|
97
|
+
dangerouslySetInnerHTML={cmsStylesPayload}
|
|
98
|
+
/>
|
|
99
|
+
)}
|
|
69
100
|
{needsSmkingElements && (
|
|
70
101
|
<script
|
|
71
102
|
data-smking="elements"
|
|
@@ -98,16 +129,27 @@ export async function SmkingCms(props: CmsParams) {
|
|
|
98
129
|
<link rel="canonical" href={seo.canonicalUrl} data-smking="cms" />
|
|
99
130
|
)}
|
|
100
131
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
<h1
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
132
|
+
{mode === "tailwind-prose" ? (
|
|
133
|
+
<article
|
|
134
|
+
className="prose lg:prose-xl dark:prose-invert mx-auto"
|
|
135
|
+
data-smking="cms"
|
|
136
|
+
data-content-type={data.page.contentType}
|
|
137
|
+
>
|
|
138
|
+
{data.page.title && <h1>{data.page.title}</h1>}
|
|
139
|
+
{blocks.map((block) => renderBlockProse(block))}
|
|
140
|
+
</article>
|
|
141
|
+
) : (
|
|
142
|
+
<article
|
|
143
|
+
className="smk-cms"
|
|
144
|
+
data-smking="cms"
|
|
145
|
+
data-content-type={data.page.contentType}
|
|
146
|
+
>
|
|
147
|
+
{data.page.title && (
|
|
148
|
+
<h1 className="smk-cms__title">{data.page.title}</h1>
|
|
149
|
+
)}
|
|
150
|
+
{blocks.map((block) => renderBlock(block))}
|
|
151
|
+
</article>
|
|
152
|
+
)}
|
|
111
153
|
</>
|
|
112
154
|
);
|
|
113
155
|
}
|
|
@@ -436,3 +478,265 @@ function formatPublishedAt(iso: string): string {
|
|
|
436
478
|
return iso.slice(0, 10);
|
|
437
479
|
}
|
|
438
480
|
}
|
|
481
|
+
|
|
482
|
+
// ── Mode B (`tailwind-prose`) dispatcher ─────────────────────────────
|
|
483
|
+
//
|
|
484
|
+
// Mirror of `renderBlock` but emits Tailwind utility classes instead
|
|
485
|
+
// of `.smk-*` namespace. The outer `<article class="prose">` wrapper
|
|
486
|
+
// (set up in SmkingCms above) styles `article` blocks automatically;
|
|
487
|
+
// structured blocks (hero/nav/carousel/search) sit inside `not-prose`
|
|
488
|
+
// resets and re-apply Tailwind utility styling.
|
|
489
|
+
//
|
|
490
|
+
// Required Tailwind safelist (the wizard adds these on install):
|
|
491
|
+
// prose / prose-invert / lg:prose-xl / dark:prose-invert / not-prose
|
|
492
|
+
// mx-auto / my-* / mt-* / mb-* / px-* / py-* / pt-* / pb-* / p-*
|
|
493
|
+
// text-* / font-* / tracking-* / leading-*
|
|
494
|
+
// bg-* / border / border-* / rounded-*
|
|
495
|
+
// grid / flex / gap-* / items-* / justify-* / space-y-*
|
|
496
|
+
// w-* / h-* / aspect-*
|
|
497
|
+
function renderBlockProse(block: Block) {
|
|
498
|
+
switch (block.component) {
|
|
499
|
+
case "article": {
|
|
500
|
+
const articleHtml: { __html: string } = { __html: block.props.html };
|
|
501
|
+
// Article HTML inherits `prose` from the outer wrapper.
|
|
502
|
+
return (
|
|
503
|
+
<div
|
|
504
|
+
key={block.id}
|
|
505
|
+
data-block-id={block.id}
|
|
506
|
+
// eslint-disable-next-line react/no-danger -- same trust boundary as renderBlock article case.
|
|
507
|
+
dangerouslySetInnerHTML={articleHtml}
|
|
508
|
+
/>
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
case "hero":
|
|
512
|
+
return (
|
|
513
|
+
<section
|
|
514
|
+
key={block.id}
|
|
515
|
+
className="not-prose my-12 text-center bg-zinc-50 dark:bg-zinc-900 py-16 px-6 rounded-lg"
|
|
516
|
+
data-block-id={block.id}
|
|
517
|
+
>
|
|
518
|
+
{block.props.image && (
|
|
519
|
+
<img
|
|
520
|
+
src={block.props.image.url}
|
|
521
|
+
alt={block.props.image.alt}
|
|
522
|
+
className="mx-auto mb-8 rounded-lg max-w-full h-auto"
|
|
523
|
+
loading="lazy"
|
|
524
|
+
/>
|
|
525
|
+
)}
|
|
526
|
+
{block.props.title && (
|
|
527
|
+
<h2 className="text-4xl md:text-5xl font-serif font-extrabold tracking-tight leading-tight mb-4">
|
|
528
|
+
{block.props.title}
|
|
529
|
+
</h2>
|
|
530
|
+
)}
|
|
531
|
+
{block.props.subtitle && (
|
|
532
|
+
<p className="text-sm text-zinc-600 dark:text-zinc-400 mt-6">
|
|
533
|
+
{block.props.subtitle}
|
|
534
|
+
</p>
|
|
535
|
+
)}
|
|
536
|
+
{block.props.cta && (
|
|
537
|
+
<a
|
|
538
|
+
className="inline-block mt-8 px-6 py-3 bg-zinc-900 dark:bg-zinc-100 text-zinc-100 dark:text-zinc-900 rounded font-medium"
|
|
539
|
+
href={block.props.cta.href}
|
|
540
|
+
>
|
|
541
|
+
{block.props.cta.label}
|
|
542
|
+
</a>
|
|
543
|
+
)}
|
|
544
|
+
</section>
|
|
545
|
+
);
|
|
546
|
+
case "nav-recent-posts":
|
|
547
|
+
case "nav-taxonomy-list":
|
|
548
|
+
return renderNavListProse(block);
|
|
549
|
+
case "nav-search":
|
|
550
|
+
return renderNavSearchProse(block);
|
|
551
|
+
case "carousel": {
|
|
552
|
+
const aspectClass = (() => {
|
|
553
|
+
switch (block.props.aspectRatio) {
|
|
554
|
+
case "4:3":
|
|
555
|
+
return "aspect-[4/3]";
|
|
556
|
+
case "1:1":
|
|
557
|
+
return "aspect-square";
|
|
558
|
+
case "3:4":
|
|
559
|
+
return "aspect-[3/4]";
|
|
560
|
+
case "9:16":
|
|
561
|
+
return "aspect-[9/16]";
|
|
562
|
+
default:
|
|
563
|
+
return "aspect-video";
|
|
564
|
+
}
|
|
565
|
+
})();
|
|
566
|
+
return (
|
|
567
|
+
<section
|
|
568
|
+
key={block.id}
|
|
569
|
+
className="not-prose my-8"
|
|
570
|
+
data-block-id={block.id}
|
|
571
|
+
>
|
|
572
|
+
<div className="grid grid-flow-col auto-cols-[80%] md:auto-cols-[60%] gap-4 overflow-x-auto snap-x snap-mandatory pb-2 -mx-6 px-6">
|
|
573
|
+
{block.props.slides.map((slide, i) => (
|
|
574
|
+
<div
|
|
575
|
+
key={i}
|
|
576
|
+
className="snap-start bg-zinc-100 dark:bg-zinc-900 rounded-lg overflow-hidden"
|
|
577
|
+
>
|
|
578
|
+
{slide.imageUrl && (
|
|
579
|
+
<img
|
|
580
|
+
src={slide.imageUrl}
|
|
581
|
+
alt=""
|
|
582
|
+
className={`w-full ${aspectClass} object-cover block`}
|
|
583
|
+
loading="lazy"
|
|
584
|
+
/>
|
|
585
|
+
)}
|
|
586
|
+
{slide.caption && (
|
|
587
|
+
<div className="px-4 py-3 text-sm text-zinc-500 dark:text-zinc-400">
|
|
588
|
+
{slide.caption}
|
|
589
|
+
</div>
|
|
590
|
+
)}
|
|
591
|
+
</div>
|
|
592
|
+
))}
|
|
593
|
+
</div>
|
|
594
|
+
</section>
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
default:
|
|
598
|
+
// Unknown component — render nothing in Mode B.
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function renderNavListProse(
|
|
604
|
+
block: Extract<
|
|
605
|
+
Block,
|
|
606
|
+
{ component: "nav-recent-posts" | "nav-taxonomy-list" }
|
|
607
|
+
>,
|
|
608
|
+
) {
|
|
609
|
+
const snapshot: NavSnapshotEntry[] = block.props.snapshot ?? [];
|
|
610
|
+
const layout = block.props.layout ?? "list";
|
|
611
|
+
const heading = (block.props as ModuleHeader).heading;
|
|
612
|
+
const viewAll = (block.props as ModuleHeader).viewAll;
|
|
613
|
+
|
|
614
|
+
const listClass =
|
|
615
|
+
layout === "grid"
|
|
616
|
+
? "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 list-none p-0 m-0"
|
|
617
|
+
: layout === "carousel"
|
|
618
|
+
? "grid grid-flow-col auto-cols-[70%] md:auto-cols-[40%] gap-4 list-none p-0 m-0 overflow-x-auto snap-x snap-mandatory"
|
|
619
|
+
: "flex flex-col list-none p-0 m-0";
|
|
620
|
+
|
|
621
|
+
return (
|
|
622
|
+
<section
|
|
623
|
+
key={block.id}
|
|
624
|
+
className="not-prose my-10 border-t border-zinc-200 dark:border-zinc-800 pt-8"
|
|
625
|
+
data-block-id={block.id}
|
|
626
|
+
>
|
|
627
|
+
{heading && (
|
|
628
|
+
<h2 className="text-xs font-bold uppercase tracking-widest text-zinc-500 dark:text-zinc-400 mb-6">
|
|
629
|
+
{heading}
|
|
630
|
+
</h2>
|
|
631
|
+
)}
|
|
632
|
+
{snapshot.length === 0 ? (
|
|
633
|
+
<p className="text-zinc-400 italic py-4">No posts yet.</p>
|
|
634
|
+
) : (
|
|
635
|
+
<ul className={listClass}>
|
|
636
|
+
{snapshot.map((entry) => {
|
|
637
|
+
const itemClass =
|
|
638
|
+
layout === "grid"
|
|
639
|
+
? "group"
|
|
640
|
+
: layout === "carousel"
|
|
641
|
+
? "snap-start bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-lg overflow-hidden"
|
|
642
|
+
: "border-t border-zinc-200 dark:border-zinc-800 first:border-t-0";
|
|
643
|
+
const linkClass =
|
|
644
|
+
layout === "grid"
|
|
645
|
+
? "flex flex-col bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-lg overflow-hidden no-underline text-inherit hover:-translate-y-0.5 hover:shadow-lg transition"
|
|
646
|
+
: layout === "carousel"
|
|
647
|
+
? "flex flex-col no-underline text-inherit"
|
|
648
|
+
: "flex gap-5 items-center py-5 no-underline text-inherit hover:opacity-80 transition";
|
|
649
|
+
const thumbClass =
|
|
650
|
+
layout === "list"
|
|
651
|
+
? "w-24 h-24 object-cover rounded shrink-0 bg-zinc-100 dark:bg-zinc-900"
|
|
652
|
+
: "w-full aspect-[16/10] object-cover bg-zinc-100 dark:bg-zinc-900";
|
|
653
|
+
const metaClass = layout === "list" ? "flex-1 min-w-0" : "p-4";
|
|
654
|
+
return (
|
|
655
|
+
<li key={entry.slug} className={itemClass}>
|
|
656
|
+
<a href={`/${entry.slug}`} className={linkClass}>
|
|
657
|
+
{entry.featuredImageUrl && (
|
|
658
|
+
<img
|
|
659
|
+
src={entry.featuredImageUrl}
|
|
660
|
+
alt=""
|
|
661
|
+
className={thumbClass}
|
|
662
|
+
loading="lazy"
|
|
663
|
+
/>
|
|
664
|
+
)}
|
|
665
|
+
<div className={metaClass}>
|
|
666
|
+
<h3 className="text-lg font-serif font-bold leading-snug mb-2 tracking-tight">
|
|
667
|
+
{entry.title || entry.slug}
|
|
668
|
+
</h3>
|
|
669
|
+
{entry.excerpt && (
|
|
670
|
+
<p className="text-sm text-zinc-500 dark:text-zinc-400 line-clamp-2 leading-relaxed">
|
|
671
|
+
{entry.excerpt}
|
|
672
|
+
</p>
|
|
673
|
+
)}
|
|
674
|
+
{entry.publishedAt && (
|
|
675
|
+
<time
|
|
676
|
+
className="block text-xs text-zinc-400 mt-2 uppercase tracking-wider"
|
|
677
|
+
dateTime={entry.publishedAt}
|
|
678
|
+
>
|
|
679
|
+
{formatPublishedAt(entry.publishedAt)}
|
|
680
|
+
</time>
|
|
681
|
+
)}
|
|
682
|
+
</div>
|
|
683
|
+
</a>
|
|
684
|
+
</li>
|
|
685
|
+
);
|
|
686
|
+
})}
|
|
687
|
+
</ul>
|
|
688
|
+
)}
|
|
689
|
+
{viewAll && (
|
|
690
|
+
<a
|
|
691
|
+
className="inline-flex items-center gap-1 mt-6 text-sm font-semibold text-zinc-900 dark:text-zinc-100 no-underline hover:underline"
|
|
692
|
+
href={viewAll.href}
|
|
693
|
+
>
|
|
694
|
+
{viewAll.label} <span aria-hidden>→</span>
|
|
695
|
+
</a>
|
|
696
|
+
)}
|
|
697
|
+
</section>
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function renderNavSearchProse(
|
|
702
|
+
block: Extract<Block, { component: "nav-search" }>,
|
|
703
|
+
) {
|
|
704
|
+
const index: NavSearchIndexEntry[] = block.props.index ?? [];
|
|
705
|
+
const placeholder = block.props.placeholder || "Search…";
|
|
706
|
+
const indexJson = JSON.stringify(index).replace(/</g, "\\u003c");
|
|
707
|
+
const scriptSrc =
|
|
708
|
+
"(function(){var s=document.currentScript;var r=s&&s.previousElementSibling;if(!r)return;var i=r.querySelector('[data-smk-search-input]');var u=r.querySelector('[data-smk-search-results]');var e=r.querySelector('[data-smk-search-empty]');var idx=" +
|
|
709
|
+
indexJson +
|
|
710
|
+
";function up(){var q=(i.value||'').trim().toLowerCase();u.textContent='';if(!q){u.hidden=true;e.hidden=true;return}var m=idx.filter(function(x){return((x.title||'')+' '+(x.excerpt||'')+' '+(x.slug||'')).toLowerCase().indexOf(q)>=0}).slice(0,20);if(m.length===0){u.hidden=true;e.hidden=false;return}e.hidden=true;u.hidden=false;m.forEach(function(x){var li=document.createElement('li');li.className='border-t border-zinc-200 dark:border-zinc-800 first:border-t-0';var a=document.createElement('a');a.href='/'+x.slug;a.className='block px-4 py-3 text-zinc-900 dark:text-zinc-100 no-underline hover:bg-zinc-50 dark:hover:bg-zinc-900';a.textContent=x.title||x.slug;li.appendChild(a);u.appendChild(li)})}i.addEventListener('input',up)})();";
|
|
711
|
+
|
|
712
|
+
return (
|
|
713
|
+
<section
|
|
714
|
+
key={block.id}
|
|
715
|
+
className="not-prose my-8 border-t border-zinc-200 dark:border-zinc-800 pt-6"
|
|
716
|
+
data-block-id={block.id}
|
|
717
|
+
>
|
|
718
|
+
<div className="relative">
|
|
719
|
+
<input
|
|
720
|
+
type="search"
|
|
721
|
+
data-smk-search-input
|
|
722
|
+
placeholder={placeholder}
|
|
723
|
+
className="w-full px-4 py-3 border border-zinc-200 dark:border-zinc-800 rounded-lg bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 text-base focus:outline-none focus:border-zinc-900 dark:focus:border-zinc-100 focus:ring-4 focus:ring-zinc-900/10 dark:focus:ring-zinc-100/10 transition"
|
|
724
|
+
/>
|
|
725
|
+
<ul
|
|
726
|
+
data-smk-search-results
|
|
727
|
+
hidden
|
|
728
|
+
className="list-none p-0 mt-3 border border-zinc-200 dark:border-zinc-800 rounded-lg bg-white dark:bg-zinc-900 overflow-hidden shadow-lg"
|
|
729
|
+
/>
|
|
730
|
+
<p
|
|
731
|
+
data-smk-search-empty
|
|
732
|
+
hidden
|
|
733
|
+
className="text-zinc-400 pt-4 text-sm"
|
|
734
|
+
>
|
|
735
|
+
No matches.
|
|
736
|
+
</p>
|
|
737
|
+
</div>
|
|
738
|
+
{/* eslint-disable-next-line react/no-danger -- inline IIFE; index JSON-escaped against </script> smuggling. */}
|
|
739
|
+
<script dangerouslySetInnerHTML={{ __html: scriptSrc }} />
|
|
740
|
+
</section>
|
|
741
|
+
);
|
|
742
|
+
}
|
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTO-GENERATED - do not edit by hand.
|
|
3
|
+
* Source: packages/shared/src/styles/cms-styles.css
|
|
4
|
+
* Sync: pnpm sync:cms-styles
|
|
5
|
+
* Hash: bf93aa1f4b52
|
|
6
|
+
*
|
|
7
|
+
* Inlined by <SmkingCms> (smking-cms.tsx) inside a
|
|
8
|
+
* <style data-smking="cms-styles"> ... </style>
|
|
9
|
+
* tag exactly once per page render. Mode A only; Mode B
|
|
10
|
+
* ("tailwind-prose") skips this entirely.
|
|
11
|
+
*/
|
|
12
|
+
export const SMKING_CMS_STYLES = `/*
|
|
13
|
+
* smking CMS — canonical block styles (Mode A "css")
|
|
14
|
+
*
|
|
15
|
+
* Single source of truth for all customer-facing CMS render styling.
|
|
16
|
+
* Consumed at build time by \`scripts/sync-cms-styles.mjs\` which emits:
|
|
17
|
+
* - packages/smking-next/src/lib/smking-cms-styles.ts (TS const)
|
|
18
|
+
* - packages/smking-laravel/resources/views/cms-styles.blade.php (Blade)
|
|
19
|
+
*
|
|
20
|
+
* The dashboard imports the same file directly via Next.js workspace
|
|
21
|
+
* alias so what the editor preview shows = what the customer site renders
|
|
22
|
+
* (WYSIWYG strict per boss original spec).
|
|
23
|
+
*
|
|
24
|
+
* Mode B (\`tailwind-prose\`) customers do NOT load this file — their
|
|
25
|
+
* Tailwind + @tailwindcss/typography plugin replaces all styles below.
|
|
26
|
+
*
|
|
27
|
+
* Namespacing rules (cross-platform parity):
|
|
28
|
+
* - All selectors are scoped to \`.smk-cms\` or \`.smk-block--*\` — no
|
|
29
|
+
* global element rules (\`h1 {}\` \`p {}\`) so SDK styles never leak
|
|
30
|
+
* into customer's other page sections.
|
|
31
|
+
* - Wrapped in \`@layer smking-cms\` so customer CSS authoring with
|
|
32
|
+
* Tailwind \`@layer base|components|utilities\` has predictable order.
|
|
33
|
+
* - Tokens on \`.smk-cms\` (CSS variables) — customer overrides via
|
|
34
|
+
* \`:root { --smk-accent: …; }\` or per-instance.
|
|
35
|
+
*
|
|
36
|
+
* Edit invariants:
|
|
37
|
+
* 1. Never write a global element selector.
|
|
38
|
+
* 2. Always declare variants via \`[data-*]\` attribute selectors — the
|
|
39
|
+
* SDK markup emits the attributes; CSS reads them. No hard-coded
|
|
40
|
+
* ratios / layouts / colors.
|
|
41
|
+
* 3. Test against Mode A's class hooks: smk-cms / smk-block--{type} /
|
|
42
|
+
* smk-hero / smk-nav / smk-nav-search / smk-carousel.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
@layer smking-cms {
|
|
46
|
+
/* ============================================================
|
|
47
|
+
Design tokens — customer overrides on :root or .smk-cms
|
|
48
|
+
============================================================ */
|
|
49
|
+
.smk-cms {
|
|
50
|
+
--smk-fg: #0a0a0a;
|
|
51
|
+
--smk-fg-muted: #525252;
|
|
52
|
+
--smk-fg-subtle: #a3a3a3;
|
|
53
|
+
--smk-bg: #fff;
|
|
54
|
+
--smk-bg-soft: #fafafa;
|
|
55
|
+
--smk-border: #e5e5e5;
|
|
56
|
+
--smk-border-strong: #d4d4d4;
|
|
57
|
+
--smk-accent: #0a0a0a;
|
|
58
|
+
--smk-accent-fg: #fff;
|
|
59
|
+
--smk-radius-sm: 6px;
|
|
60
|
+
--smk-radius-md: 10px;
|
|
61
|
+
--smk-radius-lg: 16px;
|
|
62
|
+
--smk-section-gap: 4rem;
|
|
63
|
+
--smk-font-serif: ui-serif, Georgia, "Iowan Old Style", "Apple Garamond",
|
|
64
|
+
"Times New Roman", serif;
|
|
65
|
+
--smk-font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
66
|
+
"PingFang TC", "Noto Sans CJK TC", "Microsoft JhengHei", sans-serif;
|
|
67
|
+
}
|
|
68
|
+
@media (prefers-color-scheme: dark) {
|
|
69
|
+
.smk-cms {
|
|
70
|
+
--smk-fg: #f5f5f5;
|
|
71
|
+
--smk-fg-muted: #a3a3a3;
|
|
72
|
+
--smk-fg-subtle: #525252;
|
|
73
|
+
--smk-bg: #0a0a0a;
|
|
74
|
+
--smk-bg-soft: #171717;
|
|
75
|
+
--smk-border: #262626;
|
|
76
|
+
--smk-border-strong: #404040;
|
|
77
|
+
--smk-accent: #f5f5f5;
|
|
78
|
+
--smk-accent-fg: #0a0a0a;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* ============================================================
|
|
83
|
+
Article wrapper
|
|
84
|
+
============================================================ */
|
|
85
|
+
.smk-cms {
|
|
86
|
+
color: var(--smk-fg);
|
|
87
|
+
font-family: var(--smk-font-sans);
|
|
88
|
+
line-height: 1.7;
|
|
89
|
+
font-size: 17px;
|
|
90
|
+
max-width: 42rem;
|
|
91
|
+
margin: 0 auto;
|
|
92
|
+
padding: 3rem 1.5rem;
|
|
93
|
+
}
|
|
94
|
+
.smk-cms__title {
|
|
95
|
+
font-family: var(--smk-font-serif);
|
|
96
|
+
font-size: clamp(2rem, 5vw, 3rem);
|
|
97
|
+
font-weight: 700;
|
|
98
|
+
line-height: 1.1;
|
|
99
|
+
letter-spacing: -0.02em;
|
|
100
|
+
margin: 0 0 3rem;
|
|
101
|
+
}
|
|
102
|
+
.smk-cms > .smk-block + .smk-block {
|
|
103
|
+
margin-top: 2.5rem;
|
|
104
|
+
}
|
|
105
|
+
/* Hero edges out to wrapper bounds; other blocks stay readable column. */
|
|
106
|
+
.smk-cms > .smk-block--hero {
|
|
107
|
+
margin-left: -1.5rem;
|
|
108
|
+
margin-right: -1.5rem;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* ============================================================
|
|
112
|
+
Article — Plate-rendered HTML
|
|
113
|
+
SDK emits: \`<div class="smk-block smk-block--article">{html}</div>\`
|
|
114
|
+
The inner HTML comes from Plate's serializeHtml on the SaaS side,
|
|
115
|
+
so customer SDK ships zero editor deps. Below selectors style the
|
|
116
|
+
emitted h1/h2/p/ul/blockquote etc inside the article wrapper.
|
|
117
|
+
============================================================ */
|
|
118
|
+
.smk-block--article p {
|
|
119
|
+
margin: 0 0 1.25em;
|
|
120
|
+
}
|
|
121
|
+
.smk-block--article p:last-child {
|
|
122
|
+
margin-bottom: 0;
|
|
123
|
+
}
|
|
124
|
+
.smk-block--article h1,
|
|
125
|
+
.smk-block--article h2,
|
|
126
|
+
.smk-block--article h3 {
|
|
127
|
+
font-family: var(--smk-font-serif);
|
|
128
|
+
font-weight: 700;
|
|
129
|
+
letter-spacing: -0.015em;
|
|
130
|
+
line-height: 1.2;
|
|
131
|
+
}
|
|
132
|
+
.smk-block--article h1 {
|
|
133
|
+
font-size: 2rem;
|
|
134
|
+
margin: 2em 0 0.5em;
|
|
135
|
+
}
|
|
136
|
+
.smk-block--article h2 {
|
|
137
|
+
font-size: 1.6rem;
|
|
138
|
+
margin: 1.8em 0 0.5em;
|
|
139
|
+
}
|
|
140
|
+
.smk-block--article h3 {
|
|
141
|
+
font-size: 1.25rem;
|
|
142
|
+
margin: 1.5em 0 0.5em;
|
|
143
|
+
}
|
|
144
|
+
.smk-block--article h1:first-child,
|
|
145
|
+
.smk-block--article h2:first-child,
|
|
146
|
+
.smk-block--article h3:first-child {
|
|
147
|
+
margin-top: 0;
|
|
148
|
+
}
|
|
149
|
+
.smk-block--article ul,
|
|
150
|
+
.smk-block--article ol {
|
|
151
|
+
margin: 0 0 1.25em;
|
|
152
|
+
padding-left: 1.75em;
|
|
153
|
+
}
|
|
154
|
+
.smk-block--article li {
|
|
155
|
+
margin: 0.4em 0;
|
|
156
|
+
}
|
|
157
|
+
.smk-block--article li::marker {
|
|
158
|
+
color: var(--smk-fg-subtle);
|
|
159
|
+
}
|
|
160
|
+
.smk-block--article a {
|
|
161
|
+
color: inherit;
|
|
162
|
+
text-decoration: underline;
|
|
163
|
+
text-decoration-color: var(--smk-border-strong);
|
|
164
|
+
text-underline-offset: 3px;
|
|
165
|
+
transition: text-decoration-color 0.15s;
|
|
166
|
+
}
|
|
167
|
+
.smk-block--article a:hover {
|
|
168
|
+
text-decoration-color: currentColor;
|
|
169
|
+
}
|
|
170
|
+
.smk-block--article blockquote {
|
|
171
|
+
margin: 1.5em 0;
|
|
172
|
+
padding: 0.25em 0 0.25em 1.25rem;
|
|
173
|
+
border-left: 3px solid var(--smk-border-strong);
|
|
174
|
+
color: var(--smk-fg-muted);
|
|
175
|
+
font-style: italic;
|
|
176
|
+
}
|
|
177
|
+
.smk-block--article code {
|
|
178
|
+
background: var(--smk-bg-soft);
|
|
179
|
+
padding: 0.15em 0.4em;
|
|
180
|
+
border-radius: 4px;
|
|
181
|
+
font-size: 0.9em;
|
|
182
|
+
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
183
|
+
}
|
|
184
|
+
.smk-block--article pre {
|
|
185
|
+
background: var(--smk-bg-soft);
|
|
186
|
+
padding: 1rem;
|
|
187
|
+
border-radius: var(--smk-radius-sm);
|
|
188
|
+
overflow-x: auto;
|
|
189
|
+
margin: 1.5em 0;
|
|
190
|
+
}
|
|
191
|
+
.smk-block--article pre code {
|
|
192
|
+
background: transparent;
|
|
193
|
+
padding: 0;
|
|
194
|
+
}
|
|
195
|
+
.smk-block--article img {
|
|
196
|
+
max-width: 100%;
|
|
197
|
+
height: auto;
|
|
198
|
+
border-radius: var(--smk-radius-sm);
|
|
199
|
+
margin: 1.5em 0;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* ============================================================
|
|
203
|
+
Hero — magazine cover banner
|
|
204
|
+
SDK emits: \`<section class="smk-block smk-block--hero smk-hero">\`
|
|
205
|
+
============================================================ */
|
|
206
|
+
.smk-hero {
|
|
207
|
+
text-align: center;
|
|
208
|
+
padding: 5rem 1.5rem 4rem;
|
|
209
|
+
background: var(--smk-bg-soft);
|
|
210
|
+
border-radius: 0;
|
|
211
|
+
margin-bottom: 3rem;
|
|
212
|
+
position: relative;
|
|
213
|
+
}
|
|
214
|
+
.smk-hero__image {
|
|
215
|
+
max-width: 100%;
|
|
216
|
+
height: auto;
|
|
217
|
+
margin: 0 auto 2rem;
|
|
218
|
+
border-radius: var(--smk-radius-md);
|
|
219
|
+
display: block;
|
|
220
|
+
}
|
|
221
|
+
.smk-hero__title {
|
|
222
|
+
font-family: var(--smk-font-serif);
|
|
223
|
+
font-size: clamp(2.25rem, 6vw, 4rem);
|
|
224
|
+
font-weight: 800;
|
|
225
|
+
line-height: 1.05;
|
|
226
|
+
letter-spacing: -0.025em;
|
|
227
|
+
margin: 0 0 1rem;
|
|
228
|
+
color: var(--smk-fg);
|
|
229
|
+
max-width: 32rem;
|
|
230
|
+
margin-inline: auto;
|
|
231
|
+
}
|
|
232
|
+
.smk-hero__subtitle {
|
|
233
|
+
color: var(--smk-fg-muted);
|
|
234
|
+
font-size: 0.95rem;
|
|
235
|
+
margin: 1.5rem auto 0;
|
|
236
|
+
max-width: 28rem;
|
|
237
|
+
letter-spacing: 0.02em;
|
|
238
|
+
}
|
|
239
|
+
.smk-hero__cta {
|
|
240
|
+
display: inline-block;
|
|
241
|
+
padding: 0.75rem 1.5rem;
|
|
242
|
+
margin-top: 2rem;
|
|
243
|
+
background: var(--smk-accent);
|
|
244
|
+
color: var(--smk-accent-fg);
|
|
245
|
+
border-radius: var(--smk-radius-sm);
|
|
246
|
+
text-decoration: none;
|
|
247
|
+
font-weight: 500;
|
|
248
|
+
font-size: 0.95rem;
|
|
249
|
+
transition: opacity 0.15s;
|
|
250
|
+
}
|
|
251
|
+
.smk-hero__cta:hover {
|
|
252
|
+
opacity: 0.85;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* ============================================================
|
|
256
|
+
Nav — recent-posts / taxonomy-list
|
|
257
|
+
SDK emits: \`<section class="smk-block smk-block--nav-{type} smk-nav {sub-class}" data-layout="list|grid|carousel">\`
|
|
258
|
+
============================================================ */
|
|
259
|
+
.smk-nav {
|
|
260
|
+
padding: 2.5rem 0;
|
|
261
|
+
border-top: 1px solid var(--smk-border);
|
|
262
|
+
}
|
|
263
|
+
.smk-nav__heading {
|
|
264
|
+
font-size: 0.75rem;
|
|
265
|
+
font-weight: 700;
|
|
266
|
+
text-transform: uppercase;
|
|
267
|
+
letter-spacing: 0.12em;
|
|
268
|
+
color: var(--smk-fg-muted);
|
|
269
|
+
margin: 0 0 1.5rem;
|
|
270
|
+
}
|
|
271
|
+
.smk-nav__list {
|
|
272
|
+
list-style: none;
|
|
273
|
+
padding: 0;
|
|
274
|
+
margin: 0;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/* List layout (data-layout="list") */
|
|
278
|
+
.smk-nav[data-layout="list"] .smk-nav__list {
|
|
279
|
+
display: flex;
|
|
280
|
+
flex-direction: column;
|
|
281
|
+
}
|
|
282
|
+
.smk-nav[data-layout="list"] .smk-nav__item + .smk-nav__item {
|
|
283
|
+
border-top: 1px solid var(--smk-border);
|
|
284
|
+
}
|
|
285
|
+
.smk-nav[data-layout="list"] .smk-nav__link {
|
|
286
|
+
display: flex;
|
|
287
|
+
gap: 1.25rem;
|
|
288
|
+
align-items: center;
|
|
289
|
+
padding: 1.25rem 0;
|
|
290
|
+
color: inherit;
|
|
291
|
+
text-decoration: none;
|
|
292
|
+
transition: opacity 0.15s;
|
|
293
|
+
}
|
|
294
|
+
.smk-nav[data-layout="list"] .smk-nav__thumb {
|
|
295
|
+
width: 6rem;
|
|
296
|
+
height: 6rem;
|
|
297
|
+
object-fit: cover;
|
|
298
|
+
border-radius: var(--smk-radius-sm);
|
|
299
|
+
flex-shrink: 0;
|
|
300
|
+
background: var(--smk-bg-soft);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/* Grid layout (data-layout="grid") */
|
|
304
|
+
.smk-nav[data-layout="grid"] .smk-nav__list {
|
|
305
|
+
display: grid;
|
|
306
|
+
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
|
307
|
+
gap: 1.25rem;
|
|
308
|
+
}
|
|
309
|
+
.smk-nav[data-layout="grid"] .smk-nav__link {
|
|
310
|
+
display: flex;
|
|
311
|
+
flex-direction: column;
|
|
312
|
+
color: inherit;
|
|
313
|
+
text-decoration: none;
|
|
314
|
+
background: var(--smk-bg);
|
|
315
|
+
border: 1px solid var(--smk-border);
|
|
316
|
+
border-radius: var(--smk-radius-md);
|
|
317
|
+
overflow: hidden;
|
|
318
|
+
transition: transform 0.15s, box-shadow 0.15s, border-color 0.15s;
|
|
319
|
+
}
|
|
320
|
+
.smk-nav[data-layout="grid"] .smk-nav__link:hover {
|
|
321
|
+
transform: translateY(-2px);
|
|
322
|
+
border-color: var(--smk-border-strong);
|
|
323
|
+
box-shadow: 0 8px 24px -8px rgb(0 0 0 / 0.12);
|
|
324
|
+
}
|
|
325
|
+
.smk-nav[data-layout="grid"] .smk-nav__thumb {
|
|
326
|
+
width: 100%;
|
|
327
|
+
aspect-ratio: 16 / 10;
|
|
328
|
+
object-fit: cover;
|
|
329
|
+
background: var(--smk-bg-soft);
|
|
330
|
+
}
|
|
331
|
+
.smk-nav[data-layout="grid"] .smk-nav__meta {
|
|
332
|
+
padding: 1rem;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/* Carousel layout (data-layout="carousel") — horizontal scroll-snap */
|
|
336
|
+
.smk-nav[data-layout="carousel"] .smk-nav__list {
|
|
337
|
+
display: grid;
|
|
338
|
+
grid-auto-flow: column;
|
|
339
|
+
grid-auto-columns: 70%;
|
|
340
|
+
gap: 1rem;
|
|
341
|
+
overflow-x: auto;
|
|
342
|
+
scroll-snap-type: x mandatory;
|
|
343
|
+
padding-bottom: 0.5rem;
|
|
344
|
+
}
|
|
345
|
+
@media (min-width: 640px) {
|
|
346
|
+
.smk-nav[data-layout="carousel"] .smk-nav__list {
|
|
347
|
+
grid-auto-columns: 40%;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
.smk-nav[data-layout="carousel"] .smk-nav__item {
|
|
351
|
+
scroll-snap-align: start;
|
|
352
|
+
background: var(--smk-bg);
|
|
353
|
+
border: 1px solid var(--smk-border);
|
|
354
|
+
border-radius: var(--smk-radius-md);
|
|
355
|
+
overflow: hidden;
|
|
356
|
+
}
|
|
357
|
+
.smk-nav[data-layout="carousel"] .smk-nav__link {
|
|
358
|
+
display: flex;
|
|
359
|
+
flex-direction: column;
|
|
360
|
+
color: inherit;
|
|
361
|
+
text-decoration: none;
|
|
362
|
+
}
|
|
363
|
+
.smk-nav[data-layout="carousel"] .smk-nav__thumb {
|
|
364
|
+
width: 100%;
|
|
365
|
+
aspect-ratio: 16 / 10;
|
|
366
|
+
object-fit: cover;
|
|
367
|
+
background: var(--smk-bg-soft);
|
|
368
|
+
}
|
|
369
|
+
.smk-nav[data-layout="carousel"] .smk-nav__meta {
|
|
370
|
+
padding: 1rem;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/* Shared internals */
|
|
374
|
+
.smk-nav__link:hover {
|
|
375
|
+
opacity: 0.85;
|
|
376
|
+
}
|
|
377
|
+
.smk-nav[data-layout="grid"] .smk-nav__link:hover,
|
|
378
|
+
.smk-nav[data-layout="carousel"] .smk-nav__link:hover {
|
|
379
|
+
opacity: 1;
|
|
380
|
+
}
|
|
381
|
+
.smk-nav__meta {
|
|
382
|
+
flex: 1;
|
|
383
|
+
min-width: 0;
|
|
384
|
+
}
|
|
385
|
+
.smk-nav__title {
|
|
386
|
+
font-family: var(--smk-font-serif);
|
|
387
|
+
font-size: 1.15rem;
|
|
388
|
+
font-weight: 700;
|
|
389
|
+
line-height: 1.3;
|
|
390
|
+
margin: 0 0 0.5rem;
|
|
391
|
+
letter-spacing: -0.01em;
|
|
392
|
+
}
|
|
393
|
+
.smk-nav__excerpt {
|
|
394
|
+
font-size: 0.9rem;
|
|
395
|
+
color: var(--smk-fg-muted);
|
|
396
|
+
margin: 0;
|
|
397
|
+
line-height: 1.55;
|
|
398
|
+
display: -webkit-box;
|
|
399
|
+
-webkit-line-clamp: 2;
|
|
400
|
+
-webkit-box-orient: vertical;
|
|
401
|
+
overflow: hidden;
|
|
402
|
+
}
|
|
403
|
+
.smk-nav__date {
|
|
404
|
+
display: block;
|
|
405
|
+
font-size: 0.75rem;
|
|
406
|
+
color: var(--smk-fg-subtle);
|
|
407
|
+
margin-top: 0.6rem;
|
|
408
|
+
letter-spacing: 0.04em;
|
|
409
|
+
text-transform: uppercase;
|
|
410
|
+
}
|
|
411
|
+
.smk-nav__empty {
|
|
412
|
+
color: var(--smk-fg-subtle);
|
|
413
|
+
font-style: italic;
|
|
414
|
+
padding: 1rem 0;
|
|
415
|
+
}
|
|
416
|
+
.smk-nav__view-all {
|
|
417
|
+
display: inline-flex;
|
|
418
|
+
align-items: center;
|
|
419
|
+
gap: 0.4rem;
|
|
420
|
+
margin-top: 1.5rem;
|
|
421
|
+
color: var(--smk-accent);
|
|
422
|
+
font-size: 0.875rem;
|
|
423
|
+
font-weight: 600;
|
|
424
|
+
text-decoration: none;
|
|
425
|
+
}
|
|
426
|
+
.smk-nav__view-all::after {
|
|
427
|
+
content: "→";
|
|
428
|
+
transition: transform 0.15s;
|
|
429
|
+
}
|
|
430
|
+
.smk-nav__view-all:hover::after {
|
|
431
|
+
transform: translateX(3px);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/* ============================================================
|
|
435
|
+
Nav-search — inline filter input
|
|
436
|
+
SDK emits: \`<section class="smk-block smk-block--nav-search smk-nav-search">\`
|
|
437
|
+
with \`data-smk-search-input\` / \`data-smk-search-results\` /
|
|
438
|
+
\`data-smk-search-empty\` attributes the IIFE wires up.
|
|
439
|
+
============================================================ */
|
|
440
|
+
.smk-nav-search {
|
|
441
|
+
padding: 2rem 0;
|
|
442
|
+
border-top: 1px solid var(--smk-border);
|
|
443
|
+
}
|
|
444
|
+
.smk-nav-search__shell {
|
|
445
|
+
position: relative;
|
|
446
|
+
}
|
|
447
|
+
.smk-nav-search__input {
|
|
448
|
+
width: 100%;
|
|
449
|
+
padding: 0.875rem 1.125rem;
|
|
450
|
+
border: 1px solid var(--smk-border);
|
|
451
|
+
border-radius: var(--smk-radius-md);
|
|
452
|
+
background: var(--smk-bg);
|
|
453
|
+
color: var(--smk-fg);
|
|
454
|
+
font-size: 1rem;
|
|
455
|
+
font-family: inherit;
|
|
456
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
457
|
+
}
|
|
458
|
+
.smk-nav-search__input::placeholder {
|
|
459
|
+
color: var(--smk-fg-subtle);
|
|
460
|
+
}
|
|
461
|
+
.smk-nav-search__input:focus {
|
|
462
|
+
outline: none;
|
|
463
|
+
border-color: var(--smk-fg);
|
|
464
|
+
box-shadow: 0 0 0 4px color-mix(in srgb, var(--smk-fg) 8%, transparent);
|
|
465
|
+
}
|
|
466
|
+
.smk-nav-search__list {
|
|
467
|
+
list-style: none;
|
|
468
|
+
padding: 0;
|
|
469
|
+
margin: 0.75rem 0 0;
|
|
470
|
+
border: 1px solid var(--smk-border);
|
|
471
|
+
border-radius: var(--smk-radius-md);
|
|
472
|
+
background: var(--smk-bg);
|
|
473
|
+
overflow: hidden;
|
|
474
|
+
box-shadow: 0 4px 12px -4px rgb(0 0 0 / 0.1);
|
|
475
|
+
}
|
|
476
|
+
.smk-nav-search__result + .smk-nav-search__result {
|
|
477
|
+
border-top: 1px solid var(--smk-border);
|
|
478
|
+
}
|
|
479
|
+
.smk-nav-search__result a {
|
|
480
|
+
display: block;
|
|
481
|
+
padding: 0.875rem 1.125rem;
|
|
482
|
+
color: var(--smk-fg);
|
|
483
|
+
text-decoration: none;
|
|
484
|
+
transition: background 0.1s;
|
|
485
|
+
}
|
|
486
|
+
.smk-nav-search__result a:hover {
|
|
487
|
+
background: var(--smk-bg-soft);
|
|
488
|
+
}
|
|
489
|
+
.smk-nav-search__empty {
|
|
490
|
+
color: var(--smk-fg-subtle);
|
|
491
|
+
padding: 1rem 0 0;
|
|
492
|
+
font-size: 0.9rem;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/* ============================================================
|
|
496
|
+
Carousel — \`data-aspect\` token-driven aspect-ratio
|
|
497
|
+
SDK emits: \`<section class="smk-block smk-block--carousel smk-carousel"
|
|
498
|
+
data-aspect="16:9|4:3|1:1|3:4|9:16">\`
|
|
499
|
+
5 aspect ratio variants — picked by the editor's settings popover,
|
|
500
|
+
written into props.aspectRatio, surfaced as data-aspect attr by
|
|
501
|
+
SDK render. All variant CSS lives below — image / slide both consume
|
|
502
|
+
the same --smk-carousel-aspect token.
|
|
503
|
+
============================================================ */
|
|
504
|
+
.smk-carousel {
|
|
505
|
+
margin: 2.5rem 0;
|
|
506
|
+
--smk-carousel-aspect: 16 / 9;
|
|
507
|
+
}
|
|
508
|
+
.smk-carousel[data-aspect="4:3"] {
|
|
509
|
+
--smk-carousel-aspect: 4 / 3;
|
|
510
|
+
}
|
|
511
|
+
.smk-carousel[data-aspect="1:1"] {
|
|
512
|
+
--smk-carousel-aspect: 1 / 1;
|
|
513
|
+
}
|
|
514
|
+
.smk-carousel[data-aspect="3:4"] {
|
|
515
|
+
--smk-carousel-aspect: 3 / 4;
|
|
516
|
+
}
|
|
517
|
+
.smk-carousel[data-aspect="9:16"] {
|
|
518
|
+
--smk-carousel-aspect: 9 / 16;
|
|
519
|
+
}
|
|
520
|
+
.smk-carousel__viewport {
|
|
521
|
+
display: grid;
|
|
522
|
+
grid-auto-flow: column;
|
|
523
|
+
grid-auto-columns: 80%;
|
|
524
|
+
gap: 1rem;
|
|
525
|
+
overflow-x: auto;
|
|
526
|
+
scroll-snap-type: x mandatory;
|
|
527
|
+
-webkit-overflow-scrolling: touch;
|
|
528
|
+
padding-bottom: 0.5rem;
|
|
529
|
+
margin-inline: -1.5rem;
|
|
530
|
+
padding-inline: 1.5rem;
|
|
531
|
+
}
|
|
532
|
+
@media (min-width: 640px) {
|
|
533
|
+
.smk-carousel__viewport {
|
|
534
|
+
grid-auto-columns: 60%;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
.smk-carousel__viewport::-webkit-scrollbar {
|
|
538
|
+
height: 6px;
|
|
539
|
+
}
|
|
540
|
+
.smk-carousel__viewport::-webkit-scrollbar-thumb {
|
|
541
|
+
background: var(--smk-border-strong);
|
|
542
|
+
border-radius: 3px;
|
|
543
|
+
}
|
|
544
|
+
.smk-carousel__slide {
|
|
545
|
+
scroll-snap-align: start;
|
|
546
|
+
background: var(--smk-bg-soft);
|
|
547
|
+
border-radius: var(--smk-radius-md);
|
|
548
|
+
overflow: hidden;
|
|
549
|
+
}
|
|
550
|
+
.smk-carousel__image {
|
|
551
|
+
width: 100%;
|
|
552
|
+
aspect-ratio: var(--smk-carousel-aspect);
|
|
553
|
+
object-fit: cover;
|
|
554
|
+
display: block;
|
|
555
|
+
}
|
|
556
|
+
.smk-carousel__caption {
|
|
557
|
+
padding: 0.75rem 1rem;
|
|
558
|
+
font-size: 0.875rem;
|
|
559
|
+
color: var(--smk-fg-muted);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/* ============================================================
|
|
563
|
+
Unknown block — forward-compat fallback
|
|
564
|
+
============================================================ */
|
|
565
|
+
.smk-block--unknown {
|
|
566
|
+
display: none;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
`;
|
package/src/types.ts
CHANGED
|
@@ -58,6 +58,25 @@ export type CmsStatus = "ready" | "pending" | "not_found";
|
|
|
58
58
|
|
|
59
59
|
export type CmsContentType = "article" | "landing" | "listing";
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Theme mode (v0.14+) — decides how `<SmkingCms>` ships styling.
|
|
63
|
+
*
|
|
64
|
+
* - `'css'` (Mode A, default) — SDK inlines the canonical
|
|
65
|
+
* `<style data-smking="cms-styles">` once per render plus emits
|
|
66
|
+
* `.smk-*` class hooks on every block. Zero dependency on Tailwind.
|
|
67
|
+
* Customers theme via CSS variable overrides (`:root { --smk-accent: … }`).
|
|
68
|
+
*
|
|
69
|
+
* - `'tailwind-prose'` (Mode B) — SDK skips the inline stylesheet,
|
|
70
|
+
* wraps the article in `<article class="prose dark:prose-invert">`
|
|
71
|
+
* and emits Tailwind utility classes on each block. Customer must
|
|
72
|
+
* have `@tailwindcss/typography` plugin wired up and the required
|
|
73
|
+
* classes safelisted (the wizard does this on install).
|
|
74
|
+
*
|
|
75
|
+
* Resolution order: explicit `<SmkingCms mode>` prop → `SMKING_CMS_THEME`
|
|
76
|
+
* env var → 'css'.
|
|
77
|
+
*/
|
|
78
|
+
export type CmsThemeMode = "css" | "tailwind-prose";
|
|
79
|
+
|
|
61
80
|
/**
|
|
62
81
|
* v2 substrate block primitives. Mirrors `apps/web/src/features/cms/types.ts`
|
|
63
82
|
* on the SaaS side (kept in sync manually — when the v1 block catalog
|