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
8 changes: 8 additions & 0 deletions docs-app/app/docs-support/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Real ES module backing the `docs-support` bare specifier that live demo
// fences import from. Runtime `.md` docs resolve that specifier through
// setupKolay's dynamic `modules` map (see routes/application.ts); build-time
// `.gjs.md` docs go through real imports instead, resolved via the
// `resolve.alias` entry in vite.config.mjs pointing at this file.
export { default as ThemeSupport } from './theme-support';
export { default as ThemeSwitcher } from './theme-switcher';
export { default as didInsert } from '@ember/render-modifiers/modifiers/did-insert';
34 changes: 30 additions & 4 deletions docs-app/app/docs-support/rehype-shadow-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,24 @@ function forEachRawNode(node: unknown, visit: (node: RawNode) => void) {
* Rather than teaching repl-sdk's compiler about shadow roots, this plugin
* renames each live demo's placeholder tag to `<carbon-shadow-demo>` (a
* custom element registered in `shadow-demo-element.ts`), which performs the
* actual `attachShadow` + style-import once the demo's rendered element is
* appended into it. Fences marked `no-shadow` are left as plain `<div>`s.
* actual `attachShadow` once the demo's rendered element is appended into
* it. Fences marked `no-shadow` are left as plain `<div>`s.
*
* Kolay has since grown a generic `wrapDemos` rehype plugin (`kolay/wrap-demos`,
* merged in universal-ember/kolay#361, unreleased as of kolay 5.4.0 - it ships
* in the pending 6.0.0). It is *not* a drop-in replacement for this: it wraps
* the placeholder in a component invocation (`<Shadowed>{placeholder}</Shadowed>`)
* rather than renaming it, which puts the placeholder inside the wrapper's
* shadow root. repl-sdk grafts each compiled demo in afterwards via
* `element.querySelector('#<placeholderId>')` (see repl-sdk's
* `src/compilers/markdown.js` / `src/compilers/ember/gmd.js`), and that lookup
* does not cross a shadow boundary - so a shadow-DOM wrapper would trip its
* "Could not find placeholder / target element" assertion. Renaming the
* placeholder itself, as below, keeps it in the light DOM and is why this
* works. `wrapDemos` is still useful for non-isolating chrome (borders,
* labels) once kolay 6 lands.
*/
export function rehypeShadowDemo() {
export function rehypeShadowDemo({ forBuildTimeInjection = false } = {}) {
return (tree: unknown, file: VFileLike) => {
const liveCode = file.data?.liveCode ?? [];
const shadowed = new Set(
Expand All @@ -71,7 +85,19 @@ export function rehypeShadowDemo() {

if (!id || !shadowed.has(id)) return;

node.value = `<carbon-shadow-demo id="${id}" class="${className}"></carbon-shadow-demo>`;
// kolay's build-time `.gjs.md` compiler (gjs-md.js's
// rehypeInjectComponentInvocation) finds this node by its `id` and
// injects the demo's compiled component invocation by string-replacing
// the node's *literal* `</div>` - it doesn't re-run the placeholder
// regex, so it doesn't know about `<carbon-shadow-demo>`. An inner
// `<div>` gives it something to match, and CarbonShadowDemo re-parents
// any child into the shadow root regardless of its tag, so the extra
// wrapper is otherwise inert. Runtime `.md` grafts by `id` directly
// onto `<carbon-shadow-demo>` (see shadow-demo-element.ts), so it does
// not need this and keeps the original two-attribute form.
node.value = forBuildTimeInjection
? `<carbon-shadow-demo id="${id}" class="${className}"><div></div></carbon-shadow-demo>`
: `<carbon-shadow-demo id="${id}" class="${className}"></carbon-shadow-demo>`;
});
};
}
14 changes: 10 additions & 4 deletions docs-app/app/docs-support/shadow-demo-element.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
const STYLESHEET_SELECTOR = 'link[rel="stylesheet"]';
const TAG_NAME = 'carbon-shadow-demo';

/**
* Style-isolates a live glimdown demo in its own shadow root, importing the
* app's stylesheets (mirroring ember-primitives' `<Shadowed includeStyles>`)
* so the isolated demo still picks up Carbon's styling.
* Style-isolates a live glimdown demo in its own shadow root.
*
* Nothing is imported into the shadow root on our behalf: this used to copy
* the document's `<link rel="stylesheet">` tags in as `@import`s (mirroring
* ember-primitives' `<Shadowed includeStyles>`), but that was dropped again
* because the demos that need Carbon's CSS already inline it themselves --
* they render `<ThemeSupport />` (see `theme-support.gts`), which emits a
* `<style>` with the Carbon stylesheets *inside* the demo, and therefore
* inside this shadow root. Theme tokens defined on `:root` reach here too,
* since custom properties inherit through the shadow boundary.
*
* repl-sdk's gmd compiler renders each demo by `appendChild`-ing it into the
* placeholder element some time after this element connects (see
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,21 @@
# Notification

```gjs live preview
import { Button, Notification } from 'carbon-components-ember/components';
import { ThemeSupport, setOwner } from 'docs-support';
import Component from '@glimmer/component';
import { service } from '@ember/service';
import { Button, Notification } from 'carbon-components-ember/components';
import { ThemeSupport } from 'docs-support';

class Context {
class NotificationsDemo extends Component {
@service('carbon.notifications') notifications;

constructor() {
setOwner(this);
}

get documentBody() {
return document.body;
}

showNotification = (type) => {
showNotification = () => {
this.notifications.info({
caption: 'test',
});
}
}

const context = new Context();
};

<template>
<template>
<ThemeSupport />
<Notification
@type='success'
Expand Down Expand Up @@ -55,17 +45,20 @@ const context = new Context();
<Notification @type='warning' @caption='warning' />
<br />
<br />
<Button @type='primary' @onClick={{context.showNotification}}>
<Button @type='primary' @onClick={{this.showNotification}}>
Notify
</Button>

<div style="position: absolute; top:0; right: 0">
{{#each context.notifications.queue as |n|}}
{{#each this.notifications.queue as |n|}}
<Notification @notification={{n}} />
<div style="margin: 2px"></div>
{{/each}}
</div>
</template>
</div>
</template>
}

<template><NotificationsDemo /></template>
```
## API Reference

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,14 @@ Pagination supports `xs`, `sm`, `md` and `lg` sizes via the `@size` argument.
import { Pagination } from 'carbon-components-ember/components';
import { ThemeSupport } from 'docs-support';

const noop = () => null;

<template>
<ThemeSupport />
<Pagination @size='xs' @length={{100}} @onPageChanged={{() => {}}} />
<Pagination @size='sm' @length={{100}} @onPageChanged={{() => {}}} />
<Pagination @size='md' @length={{100}} @onPageChanged={{() => {}}} />
<Pagination @size='lg' @length={{100}} @onPageChanged={{() => {}}} />
<Pagination @size='xs' @length={{100}} @onPageChanged={{noop}} />
<Pagination @size='sm' @length={{100}} @onPageChanged={{noop}} />
<Pagination @size='md' @length={{100}} @onPageChanged={{noop}} />
<Pagination @size='lg' @length={{100}} @onPageChanged={{noop}} />
</template>
```

Expand Down
74 changes: 74 additions & 0 deletions docs-app/vite.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ import { kolay } from "kolay/vite";
import { transformAsync } from '@babel/core';
import { defineConfig } from "vite";
import { resolve, dirname, basename, join } from "path";
import rehypeShiki from "@shikijs/rehype";

import { rehypeShadowDemo } from "./app/docs-support/rehype-shadow-demo";

// Components invocable at the top level of a build-time `.gjs.md` doc (e.g.
// `<ThemeSwitcher />` above the first heading), mirroring the `topLevelScope`
// passed to `setupKolay` for runtime `.md` docs in routes/application.ts.
// Bare imports here must be real, resolvable module specifiers -- unlike
// `setupKolay`'s `modules` map, there's no dynamic runtime lookup at build
// time.
const kolayScope = `
import ThemeSwitcher from 'docs-app/docs-support/theme-switcher';
import { APIDocs, ComponentSignature, ModifierSignature } from 'docs-app/routes/api-docs';
import { Callout } from '@universal-ember/docs-support';
`;

function astroturf() {

Expand Down Expand Up @@ -81,6 +96,26 @@ export default defineConfig((/* { mode } */) => {
"ember-source",
"@ember/test-waiters",
],
alias: [
// Backs the `docs-support` bare specifier that live demo fences
// import `ThemeSupport`/`didInsert`/etc. from. Runtime `.md` docs
// resolve it dynamically via setupKolay's `modules` map instead (see
// routes/application.ts); this alias is only exercised by build-time
// `.gjs.md` docs.
{ find: "docs-support", replacement: resolve("./app/docs-support/index.ts") },
// Demo fences conventionally import from these two extension-less
// specifiers, but the addon's package.json `exports` only maps
// `./*` to `dist/*.js` -- there is no `dist/components.js` or
// `dist/helpers.js`, only `dist/components/index.js` and
// `dist/helpers/index.js`. Runtime `.md` docs never hit real module
// resolution for these (setupKolay's `modules` map short-circuits
// it with the `/index` forms), so build-time `.gjs.md` docs need
// the same specifiers aliased to their real, resolvable form.
// Anchored `RegExp`s so already-correct `/index` (or `/icon`, etc.)
// specifiers elsewhere in the app don't also match and recurse.
{ find: /^carbon-components-ember\/components$/, replacement: "carbon-components-ember/components/index" },
{ find: /^carbon-components-ember\/helpers$/, replacement: "carbon-components-ember/helpers/index" },
],
},
plugins: [
// Runs a classic ember-cli prebuild so @embroider/core's resolver has
Expand All @@ -98,6 +133,45 @@ export default defineConfig((/* { mode } */) => {
// that group, so there is no separate top-level `src` option.
groups: [],
packages: ["carbon-components-ember"],
// Applies to build-time `.gjs.md` docs only; runtime `.md` docs get
// their own copies of these via setupKolay in routes/application.ts.
scope: kolayScope,
rehypePlugins: [
[rehypeShadowDemo, { forBuildTimeInjection: true }],
// Code-fence syntax highlighting, mirroring the rehypeShiki setup
// in routes/application.ts for runtime `.md` docs. Build time runs
// in Node, so (unlike the runtime highlighter) there's no need to
// hand-roll getHighlighterCore/loadWasm -- the package's default
// export creates its own highlighter from the bundled langs/themes.
[
rehypeShiki,
{
langs: [
"javascript",
"typescript",
"bash",
"css",
"diff",
"html",
"glimmer-js",
"glimmer-ts",
"handlebars",
"jsonc",
"markdown",
],
// Theme chosen by the `--shiki-{light,dark}{,-bg}` CSS
// variables this emits (defaultColor: false) -- mapped to
// `color`/`background-color` by @universal-ember/docs-support's
// prebuilt site-css/shiki.css (already loaded globally, so no
// separate include is needed here), same as runtime `.md` docs.
defaultColor: false,
themes: {
light: "github-light",
dark: "github-dark",
},
},
],
],
}),
babel({
babelHelpers: "runtime",
Expand Down
Loading