From 250a5b9ceaf95b6af2e22dab683e3b6c102b424c Mon Sep 17 00:00:00 2001 From: Hunter Johnston Date: Thu, 17 Aug 2023 20:34:06 -0400 Subject: [PATCH] initial api reference --- .prettierrc | 3 +- package.json | 2 +- pnpm-lock.yaml | 14 +- src/app.html | 2 +- src/components/api-section.svelte | 25 +++ .../api-tables/data-attrs-table.svelte | 36 +++++ src/components/api-tables/index.ts | 2 + src/components/api-tables/props-table.svelte | 40 +++++ src/components/index.ts | 3 + src/components/markdown/code.svelte | 16 ++ src/components/ui/table/table-cell.svelte | 2 +- src/components/ui/table/table-header.svelte | 2 +- src/components/ui/table/table-row.svelte | 8 +- src/components/ui/table/table.svelte | 2 +- src/content/api-reference/accordion.ts | 148 ++++++++++++++++++ src/content/api-reference/helpers.ts | 7 + src/content/api-reference/index.ts | 44 ++++++ src/content/components/accordion.md | 9 ++ src/content/introduction.md | 9 ++ .../docs/components/[name]/+page.svelte | 33 ++++ src/routes/docs/components/[name]/+page.ts | 13 ++ src/styles/app.postcss | 2 +- src/types/api.ts | 19 +++ src/types/index.ts | 1 + src/utils/docs.ts | 42 +++++ src/utils/index.ts | 2 + src/utils/markdown.ts | 11 ++ 27 files changed, 476 insertions(+), 21 deletions(-) create mode 100644 src/components/api-section.svelte create mode 100644 src/components/api-tables/data-attrs-table.svelte create mode 100644 src/components/api-tables/index.ts create mode 100644 src/components/api-tables/props-table.svelte create mode 100644 src/components/markdown/code.svelte create mode 100644 src/content/api-reference/accordion.ts create mode 100644 src/content/api-reference/helpers.ts create mode 100644 src/content/api-reference/index.ts create mode 100644 src/routes/docs/components/[name]/+page.svelte create mode 100644 src/routes/docs/components/[name]/+page.ts create mode 100644 src/types/api.ts create mode 100644 src/types/index.ts create mode 100644 src/utils/markdown.ts diff --git a/.prettierrc b/.prettierrc index d4a8d66ab..1f6ac73f8 100644 --- a/.prettierrc +++ b/.prettierrc @@ -13,7 +13,8 @@ { "files": "*.svelte", "options": { - "parser": "svelte" + "parser": "svelte", + "printWidth": 100 } }, { diff --git a/package.json b/package.json index a0b069058..0b5dc4993 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "types": "./dist/index.d.ts", "type": "module", "dependencies": { - "@melt-ui/svelte": "0.34.6", + "@melt-ui/svelte": "0.35.0", "@sveltejs/adapter-vercel": "^3.0.3", "nanoid": "^4.0.2", "shiki": "^0.14.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dac9025d4..660a0dfc5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,8 +6,8 @@ settings: dependencies: '@melt-ui/svelte': - specifier: 0.34.6 - version: 0.34.6(svelte@4.1.2) + specifier: 0.35.0 + version: 0.35.0(svelte@4.1.2) '@sveltejs/adapter-vercel': specifier: ^3.0.3 version: 3.0.3(@sveltejs/kit@1.22.4) @@ -30,7 +30,7 @@ devDependencies: version: 0.16.5(svelte@4.1.2) '@melt-ui/pp': specifier: ^0.1.2 - version: 0.1.2(@melt-ui/svelte@0.34.6)(svelte@4.1.2) + version: 0.1.2(@melt-ui/svelte@0.35.0)(svelte@4.1.2) '@playwright/test': specifier: ^1.28.1 version: 1.36.2 @@ -697,19 +697,19 @@ packages: - supports-color dev: false - /@melt-ui/pp@0.1.2(@melt-ui/svelte@0.34.6)(svelte@4.1.2): + /@melt-ui/pp@0.1.2(@melt-ui/svelte@0.35.0)(svelte@4.1.2): resolution: {integrity: sha512-GZeqp7UWLNZUC2dJpREnZrWMR88vy27WO7C3cIBz4KW3/CFD19FjNkd3VbSRfcRryrMkdnEs9nu2VUa8/0u58w==} engines: {pnpm: '>=8.6.3'} peerDependencies: '@melt-ui/svelte': '>= 0.29.0' svelte: ^3.55.0 || ^4.0.0 dependencies: - '@melt-ui/svelte': 0.34.6(svelte@4.1.2) + '@melt-ui/svelte': 0.35.0(svelte@4.1.2) svelte: 4.1.2 dev: true - /@melt-ui/svelte@0.34.6(svelte@4.1.2): - resolution: {integrity: sha512-uH+s8QL5vxvb9//tULFupGSBhWTh+TvRMbIVVEae8BVlIyQrvYD5G61hoq0CiJ+Oieqgti3wnckLkaz20aYDIg==} + /@melt-ui/svelte@0.35.0(svelte@4.1.2): + resolution: {integrity: sha512-cBS/RpcoS4cdHeNYko7MmZX8t/vNCSK0c1N1NYMGI+ioLSiwEqNYxotxJfj8OeaTfKwRJ0RYo6mxspKK4Jw6Jw==} peerDependencies: svelte: '>=3 <5' dependencies: diff --git a/src/app.html b/src/app.html index 9ea47fd62..5a8d6dab9 100644 --- a/src/app.html +++ b/src/app.html @@ -8,7 +8,7 @@ %sveltekit.head% - +
%sveltekit.body%
diff --git a/src/components/api-section.svelte b/src/components/api-section.svelte new file mode 100644 index 000000000..9b0045285 --- /dev/null +++ b/src/components/api-section.svelte @@ -0,0 +1,25 @@ + + +
+ {#each schemas as schema} +
+

{schema.title}

+

{schema.description}

+
+ {#if schema.props && schema.props.length} + + {/if} + {#if schema.dataAttributes && schema.dataAttributes.length} + + {/if} +
+
+ {/each} +
diff --git a/src/components/api-tables/data-attrs-table.svelte b/src/components/api-tables/data-attrs-table.svelte new file mode 100644 index 000000000..1678f778c --- /dev/null +++ b/src/components/api-tables/data-attrs-table.svelte @@ -0,0 +1,36 @@ + + + + + + Data Attribute + Value/Description + + + + {#if dataAttrs.length} + {#each dataAttrs as attr} + + + data-{attr.name} + + + {#if attr.value} + {attr.value} + {/if} +

+ {@html parseMarkdown(attr.description)} +

+
+
+ {/each} + {/if} +
+
diff --git a/src/components/api-tables/index.ts b/src/components/api-tables/index.ts new file mode 100644 index 000000000..7066892bc --- /dev/null +++ b/src/components/api-tables/index.ts @@ -0,0 +1,2 @@ +export { default as DataAttrsTable } from "./data-attrs-table.svelte"; +export { default as PropsTable } from "./props-table.svelte"; diff --git a/src/components/api-tables/props-table.svelte b/src/components/api-tables/props-table.svelte new file mode 100644 index 000000000..f4252d3b0 --- /dev/null +++ b/src/components/api-tables/props-table.svelte @@ -0,0 +1,40 @@ + + + + + + Prop + Default + Type/Description + + + + {#each props as prop} + + + {prop.name} + + + {#if prop.default} + {prop.default} + {:else} +

-

+ {/if} +
+ + {prop.type} +

+ {@html parseMarkdown(prop.description)} +

+
+
+ {/each} +
+
diff --git a/src/components/index.ts b/src/components/index.ts index 475caa514..bc79f5990 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -2,8 +2,11 @@ export * from "./light-switch"; export * from "./icons"; export * from "./page-header"; export * from "./navigation"; +export * from "./api-tables"; export { default as SiteHeader } from "./site-header.svelte"; export { default as TailwindIndicator } from "./tailwind-indicator.svelte"; export { default as SidebarNav } from "./navigation/sidebar-nav.svelte"; export { default as CopyCodeButton } from "./copy-code-button.svelte"; export { default as Steps } from "./steps.svelte"; +export { default as Code } from "./markdown/code.svelte"; +export { default as APISection } from "./api-section.svelte"; diff --git a/src/components/markdown/code.svelte b/src/components/markdown/code.svelte new file mode 100644 index 000000000..b8a09bd9c --- /dev/null +++ b/src/components/markdown/code.svelte @@ -0,0 +1,16 @@ + + + + + diff --git a/src/components/ui/table/table-cell.svelte b/src/components/ui/table/table-cell.svelte index ab15f75d6..9e326e035 100644 --- a/src/components/ui/table/table-cell.svelte +++ b/src/components/ui/table/table-cell.svelte @@ -10,7 +10,7 @@ [role=checkbox]]:translate-y-[2px]", + "px-2 py-4 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", className )} {...$$restProps} diff --git a/src/components/ui/table/table-header.svelte b/src/components/ui/table/table-header.svelte index 012ef212c..c9be778a7 100644 --- a/src/components/ui/table/table-header.svelte +++ b/src/components/ui/table/table-header.svelte @@ -8,6 +8,6 @@ export { className as class }; - + diff --git a/src/components/ui/table/table-row.svelte b/src/components/ui/table/table-row.svelte index 271a583ce..baeeb6ee5 100644 --- a/src/components/ui/table/table-row.svelte +++ b/src/components/ui/table/table-row.svelte @@ -10,12 +10,6 @@ export { className as class }; - + diff --git a/src/components/ui/table/table.svelte b/src/components/ui/table/table.svelte index 8a7af67f6..5dfc770c6 100644 --- a/src/components/ui/table/table.svelte +++ b/src/components/ui/table/table.svelte @@ -8,7 +8,7 @@ export { className as class }; -
+
diff --git a/src/content/api-reference/accordion.ts b/src/content/api-reference/accordion.ts new file mode 100644 index 000000000..2029f5e15 --- /dev/null +++ b/src/content/api-reference/accordion.ts @@ -0,0 +1,148 @@ +import type { APISchema } from "@/types"; +import { asChild } from "./helpers"; + +export const root: APISchema = { + title: "Root", + description: "The root accordion component used to set and manage the state of the accordion.", + props: [ + { + name: "multiple", + default: "false", + type: "boolean", + description: "Whether or not multiple accordion items can be active at the same time." + }, + { + name: "disabled", + default: "false", + type: "boolean", + description: "Whether or not the accordion is disabled." + }, + { + name: "value", + type: "string | undefined", + description: "The active accordion item value." + }, + { + name: "onValueChange", + type: "(value: string | undefined) => void", + description: "A callback function called when the active accordion item value changes." + }, + { + name: "value", + type: "string | undefined", + description: "The active accordion item value when `multiple` is true." + }, + { + name: "onValueChange", + type: "(value: string[]) => void", + description: + "A callback function called when the active accordion item value changes when `multiple` is true." + } + ], + dataAttributes: [ + { + name: "orientation", + value: "'horizontal' | 'vertical'", + description: "The orientation of the accordion." + }, + { + name: "melt-accordion", + value: "", + description: "Present on the root element." + } + ] +}; + +const item: APISchema = { + title: "Item", + description: "An accordion item.", + props: [ + asChild, + { + name: "value", + type: "string", + description: "The value of the accordion item." + }, + { + name: "disabled", + default: "false", + type: "boolean", + description: "Whether or not the accordion item is disabled." + } + ], + dataAttributes: [ + { + name: "state", + value: "'open' | 'closed'", + description: "The state of the accordion item." + }, + { + name: "disabled", + value: "", + description: "Present when the accordion item is disabled." + }, + { + name: "melt-accordion-item", + value: "", + description: "Present on the accordion item elements." + } + ] +}; + +const trigger: APISchema = { + title: "Trigger", + description: "The accordion item trigger, which opens and closes the accordion item.", + props: [asChild], + dataAttributes: [ + { + name: "state", + value: "'open' | 'closed'", + description: "The state of the accordion item." + }, + { + name: "disabled", + value: "", + description: "Present when the accordion item is disabled." + }, + { + name: "value", + value: "", + description: "The value of the accordion item." + }, + { + name: "melt-accordion-trigger", + value: "", + description: "Present on the accordion item elements." + } + ] +}; + +const content: APISchema = { + title: "Content", + description: "The accordion item content, which is displayed when the item is open.", + props: [asChild], + dataAttributes: [ + { + name: "state", + value: "'open' | 'closed'", + description: "The state of the accordion item." + }, + { + name: "disabled", + value: "", + description: "Present when the accordion item is disabled." + }, + { + name: "value", + value: "", + description: "The value of the accordion item." + }, + { + name: "melt-accordion-content", + value: "", + description: "Present on the accordion item elements." + } + ] +}; + +export const accordion = [root, item, trigger, content]; diff --git a/src/content/api-reference/helpers.ts b/src/content/api-reference/helpers.ts new file mode 100644 index 000000000..848619964 --- /dev/null +++ b/src/content/api-reference/helpers.ts @@ -0,0 +1,7 @@ +export const asChild = { + name: "asChild", + type: "boolean", + default: "false", + description: + "Whether to use [render delegation](/docs/render-delegation) with this component or not." +}; diff --git a/src/content/api-reference/index.ts b/src/content/api-reference/index.ts new file mode 100644 index 000000000..fc0ccf2cf --- /dev/null +++ b/src/content/api-reference/index.ts @@ -0,0 +1,44 @@ +import type { APISchema } from "@/types"; +import { accordion } from "./accordion"; + +export const bits = [ + "accordion", + "alert-dialog", + "aspect-ratio", + "avatar", + "button", + "checkbox", + "collapsible", + "context-menu", + "dialog", + "dropdown-menu", + "hover-card", + "label", + "menubar", + "popover", + "progress", + "radio-group", + "select", + "separator", + "slider", + "switch", + "tabs", + "toggle", + "tooltip" +] as const; + +export const bitsSet = new Set(bits); + +export function isBit(value: string): value is (typeof bits)[number] { + return bitsSet.has(value as (typeof bits)[number]); +} + +export type Bit = (typeof bits)[number]; + +export const apiSchemas: Record = { + accordion +}; + +export function getAPISchemas(bit: Bit): APISchema[] { + return apiSchemas[bit]; +} diff --git a/src/content/components/accordion.md b/src/content/components/accordion.md index a0d3f6059..3055277a7 100644 --- a/src/content/components/accordion.md +++ b/src/content/components/accordion.md @@ -3,6 +3,11 @@ title: Accordion description: Organizes content in collapsible sections, enabling users to expand or collapse them as needed. --- + + ## Structure ```svelte @@ -19,3 +24,7 @@ description: Organizes content in collapsible sections, enabling users to expand ``` + +## Component API + + diff --git a/src/content/introduction.md b/src/content/introduction.md index b8edcaa23..9dc5686cd 100644 --- a/src/content/introduction.md +++ b/src/content/introduction.md @@ -3,8 +3,17 @@ title: Introduction description: A collection of headless components for Svelte, built with Melt UI builders. --- + + Bits are headless component primitives that provide a simple interface for composing your own components. They have been thoughtfully designed to prioritize simplicity, customizability, and compatibility with any styling or UI framework you choose to work with. Under the hood, these components are powered by [Melt UI](https://melt-ui.com), which provides an even lower-level builder API for creating headless components. Bits take that API and wrap it in a more familiar component interface which allows us to handle some quality of life improvements for you. While our goal has been to keep the APIs as close to Melt's as possible, there are some slight differences, so be sure to check out the documentation for the Bit you're interested in to understand how it works. + +## Credits + +- [Melt UI](https://melt-ui.com) - The underlying builder API that powers Bits. +- [Radix UI](https://radix-ui.com) - The de-facto standard for headless APIs that we've strived to follow. diff --git a/src/routes/docs/components/[name]/+page.svelte b/src/routes/docs/components/[name]/+page.svelte new file mode 100644 index 000000000..e00f1740a --- /dev/null +++ b/src/routes/docs/components/[name]/+page.svelte @@ -0,0 +1,33 @@ + + +
+ + {tagline} + {doc.title} + {doc.description} + + +
diff --git a/src/routes/docs/components/[name]/+page.ts b/src/routes/docs/components/[name]/+page.ts new file mode 100644 index 000000000..32f4c3e55 --- /dev/null +++ b/src/routes/docs/components/[name]/+page.ts @@ -0,0 +1,13 @@ +import type { PageLoad } from "./$types"; +import { getComponentDoc } from "@/utils/docs"; + +export const load: PageLoad = async (event) => { + const { component, title, metadata, schemas } = await getComponentDoc(event.params.name); + + return { + component, + title, + metadata, + schemas + }; +}; diff --git a/src/styles/app.postcss b/src/styles/app.postcss index b798230ce..9759c906d 100644 --- a/src/styles/app.postcss +++ b/src/styles/app.postcss @@ -60,7 +60,7 @@ --primary: 0 0% 98%; --primary-foreground: 320 7% 16%; - --secondary: 320 3.7% 15.9%; + --secondary: 189 55% 56%; --secondary-foreground: 0 0% 98%; --accent: 320 3.7% 15.9%; diff --git a/src/types/api.ts b/src/types/api.ts new file mode 100644 index 000000000..d04143200 --- /dev/null +++ b/src/types/api.ts @@ -0,0 +1,19 @@ +export type PropSchema = { + name: string; + default?: string; + type: string; + description: string; +}; + +export type DataAttrSchema = { + name: string; + value: string; + description: string; +}; + +export type APISchema = { + title: string; + description: string; + props?: PropSchema[]; + dataAttributes?: DataAttrSchema[]; +}; diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 000000000..e4fa9cd3d --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1 @@ +export * from "./api.js"; diff --git a/src/utils/docs.ts b/src/utils/docs.ts index 18ccdd6cf..7559d2230 100644 --- a/src/utils/docs.ts +++ b/src/utils/docs.ts @@ -1,3 +1,5 @@ +import { bits, bitsSet, getAPISchemas, isBit } from "@/content/api-reference"; +import type { APISchema } from "@/types"; import { error, redirect } from "@sveltejs/kit"; export type FrontMatter = { @@ -18,6 +20,13 @@ type Doc = { title: string; }; +type ComponentDoc = { + component: DocFile["default"]; + metadata: DocFile["metadata"]; + title: string; + schemas: APISchema[]; +}; + export function slugFromPath(path: string) { return path.replace("/src/content/", "").replace(".md", ""); } @@ -48,3 +57,36 @@ export async function getDoc(slug: string): Promise { title: doc.metadata.title }; } + +export async function getComponentDoc(slug: string): Promise { + if (slug === "components") { + throw redirect(303, "/docs/components/accordion"); + } + + if (!isBit(slug)) { + throw error(404); + } + + const modules = import.meta.glob("/src/content/**/*.md"); + + let match: { path?: string; resolver?: DocResolver } = {}; + + for (const [path, resolver] of Object.entries(modules)) { + if (slugFromPath(path).replace("components/", "") === slug) { + match = { path, resolver: resolver as unknown as DocResolver }; + break; + } + } + + const doc = await match?.resolver?.(); + if (!doc || !doc.metadata) { + throw error(404); + } + + return { + component: doc.default, + metadata: doc.metadata, + title: doc.metadata.title, + schemas: getAPISchemas(slug) + }; +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 61abf905d..3c774e08b 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,3 +1,5 @@ export * from "./styles"; export * from "./copy-code"; export * from "./is"; +export * from "./markdown"; +export * from "./styles"; diff --git a/src/utils/markdown.ts b/src/utils/markdown.ts new file mode 100644 index 000000000..2c2d6f75f --- /dev/null +++ b/src/utils/markdown.ts @@ -0,0 +1,11 @@ +export function parseMarkdown(text: string) { + return text + .replace(/`([^`]+)`/g, "$1") + .replace(/\*\*([^*]+)\*\*/g, "$1") + .replace(/\*([^*]+)\*/g, "$1") + .replace(/_([^_]+)_/g, "$1") + .replace(/~~([^~]+)~~/g, "$1") + .replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1") + .replace(/>([^>]+)\n/g, "
$1
") + .replace(/\n/g, "
"); +}