Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6,200 changes: 6,200 additions & 0 deletions design/index.pen

Large diffs are not rendered by default.

4 changes: 0 additions & 4 deletions examples/playground/src/components/AdapterCalls.astro
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
<section class="calls-panel" id="adapter" aria-labelledby="adapter-title">
<div class="panel-bar">
<span class="bar-label" id="adapter-title">adapter calls</span>
<label class="offline-toggle">
<input type="checkbox" checked />
simulate offline
</label>
<span class="live-dot">live</span>
</div>
<ol class="call-log" id="call-log" aria-live="polite"></ol>
Expand Down
58 changes: 35 additions & 23 deletions examples/playground/src/components/DatabaseRows.astro
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
<section class="database-panel" id="database" aria-labelledby="database-title">
---
const fields = [
["hero.headline", "page", "hero", "headline"],
["hero.tagline", "page", "hero", "tagline"],
["hero.intro", "page", "hero", "intro"],
["shared.message", "page", "shared", "message"],
["cards[0].title", "cards", "cards-are-rows", "title"],
["cards[1].title", "cards", "ops-are-optimistic", "title"],
];
---

<section class="database-panel" aria-labelledby="database-title">
<div class="panel-bar">
<span class="bar-label" id="database-title">your database</span>
<span class="row-pill" id="database-count">0 rows</span>
<span class="row-pill">{fields.length} rows</span>
</div>
<div class="database-table" aria-live="polite">
{
fields.map(([key, collection, id, field]) => (
<div
class="database-row"
data-collection={collection}
data-id={id}
data-field={field}
>
<code>{key}</code>
<span class="database-row__value" data-placeholder />
</div>
))
}
</div>
<div class="database-table" id="database-table" aria-live="polite"></div>
</section>

<script>
import { ready } from "../lib/engine";

const table = document.getElementById("database-table")!;
const count = document.getElementById("database-count")!;

const fields = [
["hero.headline", "page", "hero", "headline"],
["hero.intro", "page", "hero", "intro"],
["shared.message", "page", "shared", "message"],
["cards[0].title", "cards", "cards-are-rows", "title"],
["cards[1].title", "cards", "ops-are-optimistic", "title"],
] as const;
const rows = document.querySelectorAll<HTMLElement>(".database-row");

const shorten = (value: unknown) => {
const text = value == null ? "" : String(value);
Expand All @@ -28,17 +44,13 @@
void ready.then((engine) => {
const render = () => {
const { items } = engine.getSnapshot();
table.replaceChildren();
for (const [key, collection, id, field] of fields) {
const row = items[collection]?.find((item) => item.id === id);
const item = document.createElement("div");
item.className = "database-row";
item.innerHTML = `<code></code><span></span>`;
item.querySelector("code")!.textContent = key;
item.querySelector("span")!.textContent = shorten(row?.[field]);
table.append(item);
for (const row of rows) {
const { collection, id, field } = row.dataset;
const item = items[collection!]?.find((entry) => entry.id === id);
const value = row.querySelector<HTMLElement>(".database-row__value")!;
value.textContent = shorten(item?.[field!]);
value.removeAttribute("data-placeholder");
}
count.textContent = `${fields.length} rows`;
};
engine.subscribe(render);
render();
Expand Down
107 changes: 107 additions & 0 deletions examples/playground/src/components/SiteHeader.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
import { Check, Moon, Pencil, Sun, Undo2 } from "@lucide/astro";
---

<header class="site-header" id="top">
<div class="site-header__inner">
<a class="site-brand" href="/">
<img src="/logo.svg" alt="" aria-hidden="true" width="24" height="24" />
<span>better-content</span>
</a>

<nav class="site-nav" aria-label="Primary">
<a href="https://better-content-docs.vercel.app" target="_blank" rel="noreferrer">Docs</a>
<a href="#adapters">Adapters</a>
<a href="https://github.com/Invalid8/better-content" target="_blank" rel="noreferrer">GitHub</a>
</nav>

<div class="site-actions">
<button class="icon-button" id="theme-toggle" type="button" title="Switch theme">
<Sun class="theme-icon theme-icon--light" aria-hidden="true" size={15} stroke-width={1.8} />
<Moon class="theme-icon theme-icon--dark" aria-hidden="true" size={15} stroke-width={1.8} />
<span class="sr-only">Switch theme</span>
</button>

<button
class="button button--quiet when-editing"
id="discard-all"
type="button"
aria-label="Discard unsaved changes"
>
<Undo2 aria-hidden="true" size={14} stroke-width={1.9} />
<span class="button__label">Discard</span>
</button>

<button class="button button--ink when-reading" id="toggle-edit" type="button" aria-pressed="false" aria-label="Turn on edit mode">
<Pencil aria-hidden="true" size={14} stroke-width={1.9} />
<span class="button__label">Edit mode</span>
<kbd><span class="kbd-mod">⌘</span>E</kbd>
</button>

<button class="button button--mark when-editing" id="save-all" type="button" aria-label="Save all changes">
<Check aria-hidden="true" size={14} stroke-width={2.2} />
<span class="button__label">Save all</span>
<kbd><span class="kbd-mod">⌘</span>S</kbd>
</button>
</div>
</div>
</header>

<p class="sr-only" id="engine-status" role="status" aria-live="polite"></p>

<script>
import { ready } from "../lib/engine";
import { editMode } from "../lib/editMode";
import { statusStore } from "../lib/status";

const editButton = document.getElementById("toggle-edit") as HTMLButtonElement | null;
const saveButton = document.getElementById("save-all") as HTMLButtonElement | null;
const discardButton = document.getElementById("discard-all") as HTMLButtonElement | null;
const themeButton = document.getElementById("theme-toggle") as HTMLButtonElement | null;
const status = document.getElementById("engine-status");

editButton?.addEventListener("click", editMode.toggle);
editMode.subscribe(() => {
editButton?.setAttribute("aria-pressed", String(editMode.get()));
});

const THEME_KEY = "bc-theme";
const applyTheme = (theme: string) => {
document.documentElement.dataset.theme = theme;
localStorage.setItem(THEME_KEY, theme);
};
themeButton?.addEventListener("click", () => {
applyTheme(document.documentElement.dataset.theme === "dark" ? "light" : "dark");
});

statusStore.subscribe(() => {
if (status) status.textContent = statusStore.get();
});

void ready.then((engine) => {
const render = () => {
const { hasUnsavedChanges, saving } = engine.getSnapshot();
if (saveButton) saveButton.disabled = !hasUnsavedChanges || saving;
if (discardButton) discardButton.disabled = !hasUnsavedChanges || saving;
};

saveButton?.addEventListener("click", () => void engine.saveAll());
discardButton?.addEventListener("click", () => location.reload());

window.addEventListener("keydown", (event) => {
if (!(event.metaKey || event.ctrlKey)) return;
const key = event.key.toLowerCase();
if (key === "e") {
event.preventDefault();
editMode.toggle();
}
if (key === "s" && editMode.get()) {
event.preventDefault();
void engine.saveAll();
}
});

engine.subscribe(render);
render();
});
</script>
89 changes: 0 additions & 89 deletions examples/playground/src/components/Toolbar.astro

This file was deleted.

63 changes: 63 additions & 0 deletions examples/playground/src/components/react/HeroIsland.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
CmsAuthProvider,
ContentEditSpan,
PageProvider,
} from "better-content/react";
import { editMode } from "../../lib/editMode";
import { useLiveEngine } from "./useLiveEngine";

function HeroContent() {
return (
<>
<h1 className="hero-title">
<ContentEditSpan
as="span"
className="hero-line"
collection="page"
itemId="hero"
fieldKey="headline"
/>
<ContentEditSpan
as="span"
className="hero-line"
collection="page"
itemId="hero"
fieldKey="tagline"
/>
</h1>
<ContentEditSpan
as="p"
className="hero-deck"
collection="page"
itemId="hero"
fieldKey="intro"
/>
</>
);
}

export default function HeroIsland() {
const { engine, isEditing } = useLiveEngine();

if (!engine) {
return (
<div className="hero-skeleton" aria-hidden="true">
<span className="hero-skeleton__title" />
<span className="hero-skeleton__title" />
<span className="hero-skeleton__deck" />
<span className="hero-skeleton__deck" />
<span className="hero-skeleton__deck" />
</div>
);
}

return (
<CmsAuthProvider
value={{ isAdmin: false, isEditing, toggleEdit: editMode.toggle }}
>
<PageProvider engine={engine}>
<HeroContent />
</PageProvider>
</CmsAuthProvider>
);
}
32 changes: 18 additions & 14 deletions examples/playground/src/components/react/NoteCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,25 @@ export default function NoteCard() {
const { hasUnsavedChanges } = usePageContext();

return (
<article className="note-card" data-framework="react">
<header>
<h3>React</h3>
<code>&lt;ContentEditSpan /&gt;</code>
<article className="island" data-framework="react">
<header className="island__bar">
<span className="island__dot"></span>
<span className="island__name">React</span>
<code className="island__hook">&lt;ContentEditSpan /&gt;</code>
<span className="island__state" data-dirty={hasUnsavedChanges || undefined}>
{hasUnsavedChanges ? "unsaved" : "synced"}
</span>
</header>
<ContentEditSpan
as="p"
className="note-text"
collection="page"
itemId="shared"
fieldKey="message"
/>
<footer>
unsaved changes: <strong>{hasUnsavedChanges ? "yes" : "no"}</strong>
</footer>
<div className="island__body">
<p className="island__path">page / shared.message</p>
<ContentEditSpan
as="p"
className="island__text"
collection="page"
itemId="shared"
fieldKey="message"
/>
</div>
</article>
);
}
Loading