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
107 changes: 64 additions & 43 deletions website/app/components/doc/code-group/index.gts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import Component from '@glimmer/component';
import { registerDestructor } from '@ember/destroyable';
import { CodeBlock } from 'ember-shiki';
import { tracked } from '@glimmer/tracking';
import { notEq } from 'ember-truth-helpers';
import { modifier } from 'ember-modifier';
import { service } from '@ember/service';

Expand All @@ -22,7 +21,8 @@ import DynamicTemplate from 'website/components/dynamic-template';

interface DocCodeGroupSignature {
Args: {
filename?: string;
classicComponentId?: string;
gtsComponentId?: string;
hbsSnippet?: string;
jsSnippet?: string;
gtsSnippet?: string;
Expand All @@ -43,8 +43,8 @@ const CODE_GROUP_LANGUAGE_STORAGE_KEY = 'hds-doc-code-group-language';
const CODE_GROUP_LANGUAGE_CHANGE_EVENT = 'hds-doc-code-group-language-change';

// Helper to undo code escaping for display
const unescapeCode = (code: string) => {
return code.replace(/\\n/g, '\n');
const unescapeCode = (code?: string) => {
return code ? code.replace(/\\n/g, '\n') : '';
};

export default class DocCodeGroup extends Component<DocCodeGroupSignature> {
Expand Down Expand Up @@ -119,59 +119,80 @@ export default class DocCodeGroup extends Component<DocCodeGroupSignature> {
}
}

// NOTE: the dynamic template requires a component to render a preview. If there is not a component (ex. only a sass/yaml/bash snippet), we hide the preview by default.
get hidePreview() {
// TODO: refactor dynamic template to support gts components: https://hashicorp.atlassian.net/browse/HDS-5833
return this.args.hbsSnippet === '' || this.args.hidePreview === 'true';
}

get hbsSnippet() {
const { hbsSnippet } = this.args;
return hbsSnippet ? unescapeCode(hbsSnippet) : '';
}
// NOTE: the dynamic template requires an Ember component to render a preview. If there is not a component (ex. only a sass/yaml/bash snippet), we hide the preview by default.
get showPreview() {
if (this.args.hidePreview === 'true') {
Comment thread
didoo marked this conversation as resolved.
return false;
}

get gtsSnippet() {
const { gtsSnippet } = this.args;
return gtsSnippet ? unescapeCode(gtsSnippet) : '';
}
const hasHbsPreview =
!!this.args.hbsSnippet && !!this.args.classicComponentId;
const hasGtsPreview = !!this.args.gtsSnippet && !!this.args.gtsComponentId;

get jsSnippet() {
const { jsSnippet } = this.args;
return jsSnippet ? unescapeCode(jsSnippet) : '';
return hasHbsPreview || hasGtsPreview;
}

get compactGtsSnippet() {
const { compactGtsSnippet } = this.args;
return compactGtsSnippet ? unescapeCode(compactGtsSnippet) : '';
}
get preview() {
if (
this.currentView === 'gts' &&
this.args.gtsSnippet &&
this.args.gtsComponentId
) {
return {
componentId: this.args.gtsComponentId,
templateString: undefined,
};
}

get customSnippet() {
const { customSnippet } = this.args;
return customSnippet ? unescapeCode(customSnippet) : '';
if (
(this.currentView === 'hbs' || this.currentView === 'js') &&
this.args.hbsSnippet &&
this.args.classicComponentId
) {
return {
componentId: this.args.classicComponentId,
templateString: unescapeCode(this.args.hbsSnippet),
};
}
}

get currentSnippet() {
const {
hbsSnippet,
jsSnippet,
gtsSnippet,
compactGtsSnippet,
customSnippet,
customLang,
} = this.args;
Comment thread
didoo marked this conversation as resolved.

if (this.currentView === 'js') {
return { snippet: this.jsSnippet, language: 'js' };
return { snippet: unescapeCode(jsSnippet), language: 'js' };
}

if (this.currentView === 'gts') {
if (this.isExpanded) {
return { snippet: this.gtsSnippet, language: 'gts' };
return {
snippet: unescapeCode(gtsSnippet),
language: 'gts',
};
}

// to display the compact gts snippet correctly, need to use hbs syntax highlighting instead of gts
return { snippet: this.compactGtsSnippet, language: 'hbs' };
return {
snippet: unescapeCode(compactGtsSnippet),
// to display the compact gts snippet correctly, need to use hbs syntax highlighting instead of gts
language: 'hbs',
};
}

if (this.currentView === 'custom') {
return {
snippet: this.customSnippet,
language: this.args.customLang || 'text',
snippet: unescapeCode(customSnippet),
language: customLang || 'text',
};
}

return { snippet: this.hbsSnippet, language: 'hbs' };
return { snippet: unescapeCode(hbsSnippet), language: 'hbs' };
}

get hasFooter() {
Expand All @@ -192,25 +213,25 @@ export default class DocCodeGroup extends Component<DocCodeGroupSignature> {
};

get shouldSyncLanguageSelection() {
return this.args.hbsSnippet !== '' && this.args.gtsSnippet !== '';
return !!this.args.hbsSnippet && !!this.args.gtsSnippet;
}

get languageOptions() {
const options: Array<LanguageOption> = [];

if (this.args.hbsSnippet !== '') {
if (this.args.hbsSnippet) {
options.push({ label: '.hbs', value: 'hbs' });
}

if (this.args.jsSnippet !== '') {
if (this.args.jsSnippet) {
options.push({ label: '.js', value: 'js' });
}

if (this.args.gtsSnippet !== '') {
if (this.args.gtsSnippet) {
options.push({ label: '.gts', value: 'gts' });
}

if (this.args.customLang && this.args.customSnippet !== '') {
if (this.args.customLang && !!this.args.customSnippet) {
options.push({ label: `.${this.args.customLang}`, value: 'custom' });
}

Expand Down Expand Up @@ -279,11 +300,11 @@ export default class DocCodeGroup extends Component<DocCodeGroupSignature> {

<template>
<div class="doc-code-group">
{{#if (notEq this.hidePreview true)}}
{{#if this.showPreview}}
<div class="doc-code-group__preview">
<DynamicTemplate
@templateString={{this.hbsSnippet}}
@componentId={{@filename}}
@templateString={{this.preview.templateString}}
@componentId={{this.preview.componentId}}
/>
</div>
{{/if}}
Expand Down
67 changes: 34 additions & 33 deletions website/app/components/dynamic-template.gjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { ensureSafeComponent } from '@embroider/util';
import { setComponentTemplate } from '@ember/component';
import { getOwner } from '@ember/owner';
import { compileTemplate } from '@ember/template-compilation';
import { importSync } from '@embroider/macros';

let templateOwnerMap = new Map();

Expand All @@ -19,58 +18,60 @@ export default class DynamicTemplate extends Component {
let owner = getOwner(this);
let templateMap = templateOwnerMap.get(owner);
if (templateMap === undefined) {
templateMap = templateOwnerMap.set(owner, new Map());
templateMap = new Map();
templateOwnerMap.set(owner, templateMap);
}
this.templateMap = templateMap;
}

get component() {
get backingComponentClass() {
let owner = getOwner(this);
let factory = owner.factoryFor(`component:${this.args.componentId}`);

let { templateString } = this.args;
if (!templateString) {
return null;
if (factory?.class && typeof factory.class === 'function') {
return class extends factory.class {};
}

let component = this.templateMap.get(templateString);
if (component === undefined) {
let compiledTemplate;
try {
compiledTemplate = compileTemplate(templateString);
} catch (err) {
console.error(err);
console.error(templateString);
compiledTemplate = compileTemplate(`<DynamicTemplateError />`);
}
return null;
}

component = owner.factoryFor(`component:${this.args.componentId}`);
get component() {
const { componentId, templateString } = this.args;
const cacheKey = `${componentId || ''}::${templateString || ''}`;

if (component) {
component = class extends component.class {};
} else {
// if component couldn't be found the old way try importing it directly
let module;
let component = this.templateMap.get(cacheKey);
if (component === undefined) {
if (templateString) {
// Runtime template mode (classic Ember component): compile provided template text and attach it to a backing class when one exists for the provided component id.
component = this.backingComponentClass;

let compiledTemplate;
try {
module = importSync(`./docs/${this.args.componentId}.js`);
} catch {
// backing class doesn't exist so just ignore the error
compiledTemplate = compileTemplate(templateString);
} catch (err) {
console.error(err);
console.error(templateString);
compiledTemplate = compileTemplate(`<DynamicTemplateError />`);
}

component = module?.default;
}
if (!component) {
component = class extends Component {};
}

if (!component) {
component = class extends Component {};
setComponentTemplate(compiledTemplate, component);
} else {
// Component module mode (single file component): render a precompiled component by id.
component = componentId
? getOwner(this).factoryFor(`component:${componentId}`)?.class || null
: null;
}

setComponentTemplate(compiledTemplate, component);

// eslint-disable-next-line ember/no-side-effects
this.templateMap.set(templateString, component);
this.templateMap.set(cacheKey, component);
}

return component;
}

<template>{{component (ensureSafeComponent this.component)}}</template>
<template>{{component (ensureSafeComponent this.component this)}}</template>
}
17 changes: 9 additions & 8 deletions website/app/shared/showdown-extensions/content-blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,25 +53,26 @@ export const contentBlocks = function () {

// NOTE: this regex must match the output created in markdown-process-demos.js
const demoRegex = new RegExp(
/<\?php start="demo-block" filename="(.*?)" hbs="(.*?)" js="(.*?)" gts="(.*?)" compactGts="(.*?)" custom="(.*?)" customLang="(.*?)" hidePreview="(.*?)" expanded="(.*?)" \?>\n?/,
/<\?php start="demo-block" classicComponentId="(.*?)" gtsComponentId="(.*?)" hbs="(.*?)" js="(.*?)" gts="(.*?)" compactGts="(.*?)" custom="(.*?)" customLang="(.*?)" hidePreview="(.*?)" expanded="(.*?)" \?>\n?/,
'g',
);

text = text.replace(
demoRegex,
function (
_match,
filename,
hbs,
js,
gts,
compactGts,
custom,
classicComponentId,
gtsComponentId,
hbsSnippet,
jsSnippet,
gtsSnippet,
compactGtsSnippet,
customSnippet,
customLang,
hidePreview,
expanded,
) {
return `<Doc::CodeGroup @filename="${filename}" @hbsSnippet="${hbs}" @jsSnippet="${js}" @gtsSnippet="${gts}" @compactGtsSnippet="${compactGts}" @customSnippet="${custom}" @customLang="${customLang}" @hidePreview="${hidePreview}" @isExpanded="${expanded}">\n`;
return `<Doc::CodeGroup @classicComponentId="${classicComponentId}" @gtsComponentId="${gtsComponentId}" @hbsSnippet="${hbsSnippet}" @jsSnippet="${jsSnippet}" @gtsSnippet="${gtsSnippet}" @compactGtsSnippet="${compactGtsSnippet}" @customSnippet="${customSnippet}" @customLang="${customLang}" @hidePreview="${hidePreview}" @isExpanded="${expanded}">\n`;
},
);

Expand Down
4 changes: 2 additions & 2 deletions website/docs/getting-started/for-engineers/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ Import the CSS by adding this configuration in `ember-cli-build.js`.

If you are are using single file components (i.e., `.gts` or `.gjs` files), the components need to be individually imported into the file for them to render. All components can be imported from the `@hashicorp/design-system-components/components` path. To use a component's signature, you must import it from the definition file.

[[code-snippets/sample-component expanded=true]]
[[code-snippets/sample-component execute=false expanded=true]]

In the rare cases where you need to use an HDS modifier, they are only exported from their definition file

[[code-snippets/sample-imports expanded=true]]
[[code-snippets/sample-imports execute=false expanded=true]]

For more information on single file components, see the Ember docs:
* [Intro to components](https://guides.emberjs.com/release/components/introducing-components/)
Expand Down
2 changes: 1 addition & 1 deletion website/lib/markdown/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ module.exports = {
treeForApp() {
let backingClasses = new Funnel('docs', {
destDir: 'components',
include: ['**/*.js'],
include: ['**/*.js', '**/code-snippets/**/*.gts'],
});
return new MergeTrees([backingClasses]);
},
Expand Down
Loading