{{yield
(hash
PageHeader=MockAppMainPageHeader
GenericTextContent=MockAppMainGenericTextContent
GenericAdvancedTable=MockAppMainGenericAdvancedTable
+ FormComplex=MockAppMainFormComplex
+ TableComplex=MockAppMainTableComplex
)
to="main"
}}
diff --git a/showcase/app/components/mock/app/main/form-complex.gts b/showcase/app/components/mock/app/main/form-complex.gts
new file mode 100644
index 00000000000..4d966dc64c3
--- /dev/null
+++ b/showcase/app/components/mock/app/main/form-complex.gts
@@ -0,0 +1,354 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+import Component from '@glimmer/component';
+import style from 'ember-style-modifier/modifiers/style';
+
+// HDS components
+import {
+ HdsButton,
+ HdsButtonSet,
+ HdsFormCheckboxGroup,
+ HdsFormFileInputField,
+ HdsFormMaskedInputField,
+ HdsFormRadioGroup,
+ HdsFormRadioCardGroup,
+ HdsFormSelectField,
+ HdsFormSuperSelectSingleField,
+ HdsFormSuperSelectMultipleField,
+ HdsFormTextInputField,
+ HdsFormTextareaField,
+ HdsFormToggleField,
+ HdsLinkInline,
+ HdsSeparator,
+ HdsTextBody,
+ HdsTextDisplay,
+} from '@hashicorp/design-system-components/components';
+
+const RADIOCARDS = [
+ {
+ value: '1',
+ label: 'Radio card label 1',
+ badge: 'Badge',
+ checked: true,
+ description: 'Radio card description 1',
+ generic: 'Radio card custom content 1',
+ },
+ {
+ value: '2',
+ label: 'Radio card label 2',
+ badge: 'Badge',
+ description: 'Radio card description 2',
+ generic: 'Radio card custom content 2',
+ },
+ {
+ value: '3',
+ label: 'Radio card label 3',
+ badge: 'Badge',
+ description: 'Radio card description 3',
+ generic: 'Radio card custom content 3',
+ },
+];
+
+const SUPERSELECT1_OPTIONS = [
+ {
+ size: 'Extra Small',
+ description: '2 vCPU | 1 GiB RAM',
+ price: '$0.02',
+ },
+ {
+ size: 'Small',
+ description: '2 vCPU | 2 GiB RAM',
+ price: '$0.04',
+ disabled: true,
+ },
+ {
+ size: 'Medium',
+ description: '4 vCPU | 4 GiB RAM',
+ price: '$0.08',
+ disabled: true,
+ },
+ { size: 'Large', description: '8 vCPU | 8 GiB RAM', price: '$0.16' },
+ {
+ size: 'Extra Large',
+ description: '16 vCPU | 16 GiB RAM',
+ price: '$0.32',
+ },
+];
+const SELECTED_SUPERSELECT1_OPTION = SUPERSELECT1_OPTIONS[1];
+
+const SUPERSELECT2_OPTIONS = ['Option 1', 'Option 2', 'Option 3'];
+const SELECTED_SUPERSELECT2_OPTIONS = [
+ SUPERSELECT2_OPTIONS[0],
+ SUPERSELECT2_OPTIONS[1],
+];
+
+const noop = () => {};
+
+export interface MockAppMainFormComplexSignature {
+ Args: {
+ showAll?: boolean;
+ showErrors?: boolean;
+ showIntro?: boolean;
+ showCheckbox?: boolean;
+ showFileInput?: boolean;
+ showMaskedInput?: boolean;
+ showRadio?: boolean;
+ showRadioCard?: boolean;
+ showSelect?: boolean;
+ showSuperSelect?: boolean;
+ showTextarea?: boolean;
+ showTextInput?: boolean;
+ showToggle?: boolean;
+ showButtons?: boolean;
+ };
+ Element: HTMLDivElement;
+}
+
+export default class MockAppMainFormComplex extends Component
{
+ _showIntro;
+ _showCheckbox;
+ _showFileInput;
+ _showMaskedInput;
+ _showRadio;
+ _showRadioCard;
+ _showSelect;
+ _showSuperSelect;
+ _showTextarea;
+ _showTextInput;
+ _showToggle;
+ _showButtons;
+ _showErrors;
+
+ constructor(owner: unknown, args: MockAppMainFormComplexSignature['Args']) {
+ super(owner, args);
+ this._showIntro = this.args.showIntro ?? this.args.showAll ?? false;
+ this._showCheckbox = this.args.showCheckbox ?? this.args.showAll ?? false;
+ this._showFileInput = this.args.showFileInput ?? this.args.showAll ?? false;
+ this._showMaskedInput =
+ this.args.showMaskedInput ?? this.args.showAll ?? false;
+ this._showRadio = this.args.showRadio ?? this.args.showAll ?? false;
+ this._showRadioCard = this.args.showRadioCard ?? this.args.showAll ?? false;
+ this._showSelect = this.args.showSelect ?? this.args.showAll ?? false;
+ this._showSuperSelect =
+ this.args.showSuperSelect ?? this.args.showAll ?? false;
+ this._showTextarea = this.args.showTextarea ?? this.args.showAll ?? false;
+ this._showToggle = this.args.showToggle ?? this.args.showAll ?? false;
+ this._showErrors = this.args.showErrors ?? this.args.showAll ?? false;
+ // we want at least something to be visible by default
+ this._showTextInput = this.args.showTextInput ?? this.args.showAll ?? true;
+ this._showButtons = this.args.showButtons ?? this.args.showAll ?? true;
+ }
+
+
+
+
+}
diff --git a/showcase/app/components/mock/app/main/generic-text-content.gts b/showcase/app/components/mock/app/main/generic-text-content.gts
index 94c54345959..0fd674d4f35 100644
--- a/showcase/app/components/mock/app/main/generic-text-content.gts
+++ b/showcase/app/components/mock/app/main/generic-text-content.gts
@@ -8,16 +8,23 @@ import type { TemplateOnlyComponent } from '@ember/component/template-only';
// HDS components
import {
HdsLinkInline,
+ HdsTextDisplay,
HdsTextBody,
} from '@hashicorp/design-system-components/components';
export interface MockAppMainGenericTextContentSignature {
+ Args: {
+ showHeadings?: boolean;
+ };
Element: HTMLDivElement;
}
const MockAppMainGenericTextContent: TemplateOnlyComponent =
+ {{#if @showHeadings}}
+
Lorem ipsum dolor
+ {{/if}}
Lorem ipsum dolor sit amet, consectetur
adipisicing elit. Excepturi
aperiam a molestias quisquam
@@ -27,6 +34,9 @@ const MockAppMainGenericTextContent: TemplateOnlyComponent
+ {{#if @showHeadings}}
+ Veritatis fugiat eligendi
+ {{/if}}
Ab, deleniti vel. Optio consequuntur
sint officiis distinctio dolorem nobis porro ipsum natus hic debitis
nihil at nostrum, reiciendis exercitationem quod deserunt inventore,
@@ -36,6 +46,9 @@ const MockAppMainGenericTextContent: TemplateOnlyComponent
+ {{#if @showHeadings}}
+ Facere aut animi praesentium
+ {{/if}}
Velit nemo voluptatum, culpa libero
assumenda ea quae dolorem molestias fugiat, maxime eveniet ipsum, et
facere aut animi praesentium. Eum voluptatum eaque fugit aspernatur
diff --git a/showcase/app/components/mock/app/main/pagination.gts b/showcase/app/components/mock/app/main/pagination.gts
new file mode 100644
index 00000000000..62c6f6b712f
--- /dev/null
+++ b/showcase/app/components/mock/app/main/pagination.gts
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+import { array } from '@ember/helper';
+import style from 'ember-style-modifier/modifiers/style';
+
+import type { TemplateOnlyComponent } from '@ember/component/template-only';
+
+// HDS components
+import { HdsPaginationNumbered } from '@hashicorp/design-system-components/components';
+
+// types
+import type { HdsPaginationNumberedSignature } from '@hashicorp/design-system-components/components/hds/pagination/numbered/index';
+
+export interface MockAppMainPaginationSignature {
+ Element: HdsPaginationNumberedSignature['Element'];
+}
+
+const MockAppMainPagination: TemplateOnlyComponent =
+
+
+ ;
+export default MockAppMainPagination;
diff --git a/showcase/app/components/mock/app/main/table-complex.gts b/showcase/app/components/mock/app/main/table-complex.gts
new file mode 100644
index 00000000000..8c440db464b
--- /dev/null
+++ b/showcase/app/components/mock/app/main/table-complex.gts
@@ -0,0 +1,164 @@
+import Component from '@glimmer/component';
+import style from 'ember-style-modifier/modifiers/style';
+import { array, hash } from '@ember/helper';
+import { eq } from 'ember-truth-helpers';
+
+import {
+ HdsBadge,
+ HdsButton,
+ HdsButtonSet,
+ HdsDropdown,
+ HdsFormTextInputBase,
+ HdsSegmentedGroup,
+ HdsTable,
+} from '@hashicorp/design-system-components/components';
+
+import MockAppMainPagination from './pagination';
+
+import CLUSTERS from 'showcase/mocks/cluster-data';
+
+export interface MockAppMainTableComplexSignature {
+ Args: {
+ showFilters?: boolean;
+ showPagination?: boolean;
+ };
+}
+
+export default class MockAppMainTableComplex extends Component {
+ showFilters = this.args.showFilters ?? false;
+ showPagination = this.args.showPagination ?? false;
+
+
+ {{#if this.showFilters}}
+
+
+
+
+
+
+
+
+ Lorem
+ Ipsum
+ Dolor
+
+
+
+
+
+
+
+
+
+ Active
+ Establishing
+ Pending
+ Failing
+
+
+
+ {{/if}}
+
+ <:body as |B|>
+
+ {{B.data.peer-name}}
+ {{B.data.cluster-partition}}
+
+ {{#if (eq B.data.status "failing")}}
+
+ {{else if (eq B.data.status "active")}}
+
+ {{else if (eq B.data.status "pending")}}
+
+ {{else if (eq B.data.status "establishing")}}
+
+ {{/if}}
+
+ {{B.data.services.imported}}
+ {{B.data.services.exported}}
+
+
+
+ Create
+ Read
+ Update
+
+ Delete
+
+
+
+
+
+ {{#if this.showPagination}}
+
+ {{/if}}
+
+}
diff --git a/showcase/app/components/mock/app/sidebar/app-side-nav.gts b/showcase/app/components/mock/app/sidebar/app-side-nav.gts
index 6f55aa824ba..0f02f18896b 100644
--- a/showcase/app/components/mock/app/sidebar/app-side-nav.gts
+++ b/showcase/app/components/mock/app/sidebar/app-side-nav.gts
@@ -26,6 +26,9 @@ export interface MockAppSidebarAppSideNavSignature {
showDevToggle?: boolean;
onToggleMinimizedStatus?: HdsAppSideNavSignature['Args']['onToggleMinimizedStatus'];
};
+ Blocks: {
+ extraAfter?: [];
+ };
Element: HdsAppSideNavSignature['Element'];
}
@@ -148,6 +151,12 @@ export default class MockAppSidebarAppSideNav extends Component
{{/if}}
+ {{#if (has-block "extraAfter")}}
+
+ {{yield to="extraAfter"}}
+
+ {{/if}}
+
diff --git a/showcase/app/components/mock/app/sidebar/side-nav.gts b/showcase/app/components/mock/app/sidebar/side-nav.gts
index c5f61f41237..c2329a4d04e 100644
--- a/showcase/app/components/mock/app/sidebar/side-nav.gts
+++ b/showcase/app/components/mock/app/sidebar/side-nav.gts
@@ -27,6 +27,10 @@ export interface MockAppSidebarOldSideNavSignature {
showHeader?: boolean;
showFooter?: boolean;
};
+ Blocks: {
+ extraBodyAfter: [];
+ extraFooterBefore: [];
+ };
Element: HdsSideNavSignature['Element'];
}
@@ -153,9 +157,11 @@ export default class MockAppSidebarOldSideNav extends Component
+ {{yield to="extraBodyAfter"}}
<:footer>
{{#if this.showFooter}}
+ {{yield to="extraFooterBefore"}}
+ {{pageTitle "Theming"}}
+
+ Theming
+
+
+
+
+
+ {{! For some reason, Ember tests don't play well with iframes (URL not found) so we don't snapshots them in Percy }}
+
+;
+
+export default ThemingIndex;
diff --git a/showcase/app/components/page-foundations/theming/sub-sections/components.gts b/showcase/app/components/page-foundations/theming/sub-sections/components.gts
new file mode 100644
index 00000000000..0d8cf4e23f7
--- /dev/null
+++ b/showcase/app/components/page-foundations/theming/sub-sections/components.gts
@@ -0,0 +1,107 @@
+import type { TemplateOnlyComponent } from '@ember/component/template-only';
+import { on } from '@ember/modifier';
+
+import ShwDivider from 'showcase/components/shw/divider';
+import ShwFlex from 'showcase/components/shw/flex';
+import ShwTextH2 from 'showcase/components/shw/text/h2';
+import ShwTextH4 from 'showcase/components/shw/text/h4';
+
+import {
+ HdsAppFooter,
+ HdsDropdown,
+ HdsCodeBlock,
+} from '@hashicorp/design-system-components/components';
+
+const SubSectionComponents: TemplateOnlyComponent =
+ "Themed" components
+
+ AppFooter
+
+
+
+
+
+
+
+
+ System
+ Dark
+ Light
+
+
+
+ Changelog
+
+
+
+
+
+
+
+
+
+
+ System
+ Dark
+ Light
+
+
+
+ Changelog
+
+
+
+
+
+
+
+
+ CodeBlock
+
+
+
+ {{! template-lint-disable no-whitespace-for-layout }}
+
+ Title
+ Description
+
+ {{! template-lint-enable no-whitespace-for-layout }}
+
+
+ ;
+
+export default SubSectionComponents;
diff --git a/showcase/app/components/page-foundations/theming/sub-sections/contexts.gts b/showcase/app/components/page-foundations/theming/sub-sections/contexts.gts
new file mode 100644
index 00000000000..e1fd3671a7d
--- /dev/null
+++ b/showcase/app/components/page-foundations/theming/sub-sections/contexts.gts
@@ -0,0 +1,213 @@
+import Component from '@glimmer/component';
+import type { TemplateOnlyComponent } from '@ember/component/template-only';
+import { service } from '@ember/service';
+import style from 'ember-style-modifier';
+
+import ShwTextH2 from 'showcase/components/shw/text/h2';
+import ShwTextH3 from 'showcase/components/shw/text/h3';
+import ShwTextH4 from 'showcase/components/shw/text/h4';
+import ShwTextBody from 'showcase/components/shw/text/body';
+import ShwDivider from 'showcase/components/shw/divider';
+import ShwFlex from 'showcase/components/shw/flex';
+import ShwGrid from 'showcase/components/shw/grid';
+
+import ShwThemingService from 'showcase/services/shw-theming';
+
+interface ThemingBasicContainerSignature {
+ Args: {
+ text?: string;
+ };
+ Blocks: {
+ default: [];
+ };
+ Element: HTMLDivElement;
+}
+
+const ThemingBasicContainer: TemplateOnlyComponent =
+
+
+ {{#if @text}}
+ {{@text}}
+ {{else}}
+ {{yield}}
+ {{/if}}
+
+ ;
+
+export default class SubSectionContexts extends Component {
+ @service declare readonly shwTheming: ShwThemingService;
+
+ get showContextualExamples() {
+ return (
+ this.shwTheming.currentStylesheet === 'css-selectors' ||
+ this.shwTheming.currentStylesheet === 'combined-strategies'
+ );
+ }
+
+
+ Contextual theming
+
+ {{#if this.showContextualExamples}}
+
+ Page-level theming
+
+ This example below should update when changing theme
+
+
+
+ Container with color
+ foreground-strong
+ / background
+ surface-strong
+
+
+
+
+
+
+ Local theming via CSS selectors
+
+ These examples below should remain the same even when
+ changing theme
+
+ Parent container
+
+
+
+ .hds-theme-light
class
+
+
+
+
+
+ .hds-theme-dark
class
+
+
+
+
+
+ [data-hds-theme=light]
+ class
+
+
+
+
+
+ [data-hds-theme=dark]
+ class
+
+
+
+
+
+
+
+
+ Nested
+
+
+
+ .hds-theme-light
+ >
+ .hds-theme-dark
+
+
+
+ .hds-theme-dark
+ >
+ .hds-theme-light
+
+
+
+ .hds-theme-dark
+ >
+ .hds-theme-light
+ >
+ .hds-theme-dark
+
+
+
+ [data-hds-theme=light]
+ >
+ [data-hds-theme=dark]
+
+
+
+ [data-hds-theme=dark]
+ >
+ [data-hds-theme=light]
+
+
+
+ [data-hds-theme=dark]
+ >
+ [data-hds-theme=light]
+ >
+ [data-hds-theme=dark]
+
+
+
+ {{else}}
+
+ These examples are visible only if theming is applied via
+ "CSS selectors" or "combined strategies", please select a theme below
+ one of these two groups of options in the selector at the top of the
+ page
+
+ {{/if}}
+
+}
diff --git a/showcase/app/components/page-foundations/theming/sub-sections/demo.gts b/showcase/app/components/page-foundations/theming/sub-sections/demo.gts
new file mode 100644
index 00000000000..b96f0c27545
--- /dev/null
+++ b/showcase/app/components/page-foundations/theming/sub-sections/demo.gts
@@ -0,0 +1,17 @@
+import type { TemplateOnlyComponent } from '@ember/component/template-only';
+
+import ShwTextH2 from 'showcase/components/shw/text/h2';
+import ShwFrame from 'showcase/components/shw/frame';
+
+const SubSectionDemo: TemplateOnlyComponent =
+ Demo
+
+
+ ;
+
+export default SubSectionDemo;
diff --git a/showcase/app/components/page-foundations/theming/sub-sections/theme-switcher.gts b/showcase/app/components/page-foundations/theming/sub-sections/theme-switcher.gts
new file mode 100644
index 00000000000..ac0cf959ba8
--- /dev/null
+++ b/showcase/app/components/page-foundations/theming/sub-sections/theme-switcher.gts
@@ -0,0 +1,47 @@
+import type { TemplateOnlyComponent } from '@ember/component/template-only';
+import style from 'ember-style-modifier';
+
+import ShwFlex from 'showcase/components/shw/flex';
+import ShwTextH2 from 'showcase/components/shw/text/h2';
+import ShwTextH4 from 'showcase/components/shw/text/h4';
+import ShwDivider from 'showcase/components/shw/divider';
+
+import { HdsThemeSwitcher } from '@hashicorp/design-system-components/components';
+
+const SubSectionThemeSwitcher: TemplateOnlyComponent =
+ Theme switcher
+
+ Size
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Options
+
+
+
+
+
+
+
+
+
+ ;
+
+export default SubSectionThemeSwitcher;
diff --git a/showcase/app/components/shw/theme-switcher/control/select.gts b/showcase/app/components/shw/theme-switcher/control/select.gts
new file mode 100644
index 00000000000..f1e8fa56d0e
--- /dev/null
+++ b/showcase/app/components/shw/theme-switcher/control/select.gts
@@ -0,0 +1,67 @@
+import Component from '@glimmer/component';
+import { guidFor } from '@ember/object/internals';
+import { on } from '@ember/modifier';
+import { eq } from 'ember-truth-helpers';
+
+interface ShwThemeSwitcherControlSelectSignature {
+ Args: {
+ label: string;
+ values?: string[] | Record;
+ selectedValue?: string;
+ onChange?: (event: Event) => void;
+ };
+ Blocks: {
+ default: [];
+ };
+}
+
+export default class ShwThemeSwitcherControlSelect extends Component {
+ selectId = `shw-theme-switcher-select-${guidFor(this)}`;
+
+ get options() {
+ if (Array.isArray(this.args.values)) {
+ // Convert array to an object where keys and values are the same
+ return this.args.values.reduce(
+ (acc, value) => {
+ acc[value] = value;
+ return acc;
+ },
+ {} as Record,
+ );
+ } else {
+ // If values is already an object, return it directly
+ return this.args.values;
+ }
+ }
+
+ onChange = (event: Event) => {
+ if (this.args.onChange) {
+ this.args.onChange(event);
+ }
+ };
+
+
+
+ {{@label}}
+
+ {{#if this.options}}
+ {{#each-in this.options as |key text|}}
+ {{text}}
+ {{/each-in}}
+ {{else}}
+ {{yield}}
+ {{/if}}
+
+
+
+}
diff --git a/showcase/app/components/shw/theme-switcher/control/toggle.gts b/showcase/app/components/shw/theme-switcher/control/toggle.gts
new file mode 100644
index 00000000000..51ae24fd1a6
--- /dev/null
+++ b/showcase/app/components/shw/theme-switcher/control/toggle.gts
@@ -0,0 +1,37 @@
+import Component from '@glimmer/component';
+import { guidFor } from '@ember/object/internals';
+import { on } from '@ember/modifier';
+
+interface ShwThemeSwitcherControlToggleSignature {
+ Args: {
+ label: string;
+ checked?: boolean;
+ onToggle?: (event: Event) => void;
+ };
+}
+
+export default class ShwThemeSwitcherControlToggle extends Component {
+ inputId = `shw-theme-switcher-input-${guidFor(this)}`;
+
+ onToggle = (event: Event) => {
+ if (this.args.onToggle) {
+ this.args.onToggle(event);
+ }
+ };
+
+
+
+ {{@label}}
+
+
+
+}
diff --git a/showcase/app/components/shw/theme-switcher/debugging-panel.gts b/showcase/app/components/shw/theme-switcher/debugging-panel.gts
new file mode 100644
index 00000000000..81988606bac
--- /dev/null
+++ b/showcase/app/components/shw/theme-switcher/debugging-panel.gts
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+import Component from '@glimmer/component';
+import { service } from '@ember/service';
+
+import ShwLabel from '../../shw/label';
+
+import ShwThemingService from 'showcase/services/shw-theming';
+import HdsThemingService from '@hashicorp/design-system-components/services/hds-theming';
+
+export default class ShwThemeSwitcherDebuggingPanel extends Component {
+ @service declare readonly hdsTheming: HdsThemingService;
+ @service declare readonly shwTheming: ShwThemingService;
+
+
+
+ currentStylesheet:
+ {{this.shwTheming.currentStylesheet}}
+ currentTheme:
+ {{this.hdsTheming.currentTheme}}
+ currentMode:
+ {{this.hdsTheming.currentMode}}
+ currentLightTheme:
+ {{this.hdsTheming.currentLightTheme}}
+ currentDarkTheme:
+ {{this.hdsTheming.currentDarkTheme}}
+
+
+}
diff --git a/showcase/app/components/shw/theme-switcher/index.gts b/showcase/app/components/shw/theme-switcher/index.gts
new file mode 100644
index 00000000000..371cf43ea38
--- /dev/null
+++ b/showcase/app/components/shw/theme-switcher/index.gts
@@ -0,0 +1,91 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+import Component from '@glimmer/component';
+import { service } from '@ember/service';
+import { tracked } from '@glimmer/tracking';
+import { guidFor } from '@ember/object/internals';
+import type Owner from '@ember/owner';
+
+import ShwThemeSwitcherPopover from './popover';
+import ShwThemeSwitcherSelector from './selector';
+import ShwThemeSwitcherDebuggingPanel from './debugging-panel';
+
+import HdsThemingService from '@hashicorp/design-system-components/services/hds-theming';
+
+import { HdsIcon } from '@hashicorp/design-system-components/components';
+
+export type ControlsPreferences = {
+ hasFixedControls: boolean;
+ hasDebuggingPanel: boolean;
+};
+
+export type OnApply = (options: ControlsPreferences) => void;
+
+const LOCALSTORAGE_FIXED_CONTROLS = 'shw-theming-has-fixed-controls';
+const LOCALSTORAGE_DEBUGGING_PANEL = 'shw-theming-has-debugging-panel';
+
+export default class ShwThemeSwitcher extends Component {
+ @service declare readonly hdsTheming: HdsThemingService;
+
+ @tracked hasFixedControls: boolean;
+ @tracked hasDebuggingPanel: boolean;
+
+ popoverId = `shw-theming-options-popover-${guidFor(this)}`;
+
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
+ constructor(owner: Owner, args: {}) {
+ super(owner, args);
+
+ const storedHasFixedControls = localStorage.getItem(
+ LOCALSTORAGE_FIXED_CONTROLS,
+ );
+ this.hasFixedControls = storedHasFixedControls === 'true';
+
+ const storedHasDebuggingPanel = localStorage.getItem(
+ LOCALSTORAGE_DEBUGGING_PANEL,
+ );
+ this.hasDebuggingPanel = storedHasDebuggingPanel === 'true';
+ }
+
+ onApply = ({ hasFixedControls, hasDebuggingPanel }: ControlsPreferences) => {
+ this.hasFixedControls = hasFixedControls;
+ this.hasDebuggingPanel = hasDebuggingPanel;
+
+ localStorage.setItem(
+ LOCALSTORAGE_FIXED_CONTROLS,
+ String(this.hasFixedControls),
+ );
+ localStorage.setItem(
+ LOCALSTORAGE_DEBUGGING_PANEL,
+ String(this.hasDebuggingPanel),
+ );
+ };
+
+
+
+
+
+
+
+ {{#if this.hasDebuggingPanel}}
+
+ {{/if}}
+
+
+}
diff --git a/showcase/app/components/shw/theme-switcher/popover.gts b/showcase/app/components/shw/theme-switcher/popover.gts
new file mode 100644
index 00000000000..bdf93a2884d
--- /dev/null
+++ b/showcase/app/components/shw/theme-switcher/popover.gts
@@ -0,0 +1,182 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+import Component from '@glimmer/component';
+import { tracked } from '@glimmer/tracking';
+import { on } from '@ember/modifier';
+import { hash, fn } from '@ember/helper';
+import { service } from '@ember/service';
+import type Owner from '@ember/owner';
+
+import ShwThemeSwitcherControlSelect from './control/select';
+import ShwThemeSwitcherControlToggle from './control/toggle';
+
+import HdsThemingService from '@hashicorp/design-system-components/services/hds-theming';
+import {
+ MODES_LIGHT,
+ MODES_DARK,
+} from '@hashicorp/design-system-components/services/hds-theming';
+import type {
+ HdsModesLight,
+ HdsModesDark,
+ HdsCssSelectors,
+} from '@hashicorp/design-system-components/services/hds-theming';
+
+import type { OnApply } from './index';
+
+interface ShwThemeSwitcherPopoverSignature {
+ Args: {
+ popoverId: string;
+ hasFixedControls: boolean;
+ hasDebuggingPanel: boolean;
+ onApply: OnApply;
+ };
+ Element: HTMLDivElement;
+}
+
+export default class ShwThemeSwitcherPopover extends Component {
+ @service declare readonly hdsTheming: HdsThemingService;
+
+ @tracked _selectedLightTheme;
+ @tracked _selectedDarkTheme;
+ @tracked _selectedCssSelector;
+ @tracked _hasFixedControls: boolean;
+ @tracked _hasDebuggingPanel: boolean;
+
+ constructor(owner: Owner, args: ShwThemeSwitcherPopoverSignature['Args']) {
+ super(owner, args);
+ this._selectedLightTheme = this.hdsTheming.currentLightTheme;
+ this._selectedDarkTheme = this.hdsTheming.currentDarkTheme;
+ this._selectedCssSelector = this.hdsTheming.currentCssSelector;
+ this._hasFixedControls = this.args.hasFixedControls;
+ this._hasDebuggingPanel = this.args.hasDebuggingPanel;
+ }
+
+ onChangeAdvancedOption = (optionName: string, event: Event) => {
+ const select = event.target as HTMLSelectElement;
+ switch (optionName) {
+ case 'light-theme':
+ this._selectedLightTheme = select.value as HdsModesLight;
+ break;
+ case 'dark-theme':
+ this._selectedDarkTheme = select.value as HdsModesDark;
+ break;
+ case 'css-selector':
+ this._selectedCssSelector = select.value as HdsCssSelectors;
+ break;
+ }
+ };
+
+ onTogglePreference = (preferenceName: string, event: Event) => {
+ const input = event.target as HTMLInputElement;
+ switch (preferenceName) {
+ case 'fixed-controls':
+ this._hasFixedControls = input.checked;
+ break;
+ case 'debugging-panel':
+ this._hasDebuggingPanel = input.checked;
+ break;
+ }
+ };
+
+ onApplyThemingPreferences = () => {
+ this.hdsTheming.setTheme({
+ // we reuse the current theme (we're not changing it here)
+ theme: this.hdsTheming.currentTheme,
+ // we update the options
+ options: {
+ lightTheme: this._selectedLightTheme,
+ darkTheme: this._selectedDarkTheme,
+ cssSelector: this._selectedCssSelector,
+ },
+ });
+
+ if (typeof this.args.onApply === 'function') {
+ this.args.onApply({
+ hasFixedControls: this._hasFixedControls,
+ hasDebuggingPanel: this._hasDebuggingPanel,
+ });
+ }
+
+ // programmatically close the popover
+ const popoverElement = document.getElementById(this.args.popoverId);
+ if (popoverElement && 'hidePopover' in popoverElement) {
+ popoverElement.hidePopover();
+ }
+ };
+
+
+
+
Advanced options
+
You can change what
+ modes are used for the light/dark themes, and what CSS selector is used
+ to apply the mode to the page:
+
+
+
+
+
+
+
+
+
+
You can fix the theming
+ controls on the page, and show an extra debugging panel:
+
+
+
+
+
+
+
+
+
+
+ Apply
+
+
+ Cancel
+
+
+
+
+}
diff --git a/showcase/app/components/shw/theme-switcher/selector.gts b/showcase/app/components/shw/theme-switcher/selector.gts
new file mode 100644
index 00000000000..28d4134ebf4
--- /dev/null
+++ b/showcase/app/components/shw/theme-switcher/selector.gts
@@ -0,0 +1,98 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+import Component from '@glimmer/component';
+import { eq } from 'ember-truth-helpers';
+import { service } from '@ember/service';
+
+import ShwThemeSwitcherControlSelect from './control/select';
+import ShwThemingService from 'showcase/services/shw-theming';
+import type { ShwStylesheets } from 'showcase/services/shw-theming';
+
+import type { HdsThemes } from '@hashicorp/design-system-components/services/hds-theming';
+
+import HdsThemingService from '@hashicorp/design-system-components/services/hds-theming';
+
+export default class ShwThemeSwitcherSelector extends Component {
+ @service declare readonly hdsTheming: HdsThemingService;
+ @service declare readonly shwTheming: ShwThemingService;
+
+ get gLight() {
+ return this.hdsTheming.currentLightTheme.replace('cds-', '');
+ }
+
+ get gDark() {
+ return this.hdsTheming.currentDarkTheme.replace('cds-', '');
+ }
+
+ get selectedOption() {
+ return `${this.shwTheming.currentStylesheet ?? 'no-theming'}|${this.hdsTheming.currentTheme ?? ''}`;
+ }
+
+ get themingOptions(): Record> {
+ return {
+ 'No theming': {
+ 'no-theming|': 'HDS / Standard',
+ },
+ 'Theming via prefers-color-scheme': {
+ 'prefers-color-scheme|system': 'Carbon / System',
+ },
+ 'Theming via CSS selectors': {
+ 'css-selectors|': 'HDS / Default',
+ 'css-selectors|light': `Carbon / Light (${this.gLight})`,
+ 'css-selectors|dark': `Carbon / Dark (${this.gDark})`,
+ },
+ 'Theming via combined strategies': {
+ 'combined-strategies|': 'HDS / Default',
+ 'combined-strategies|system': 'Carbon / System',
+ 'combined-strategies|light': `Carbon / Light (${this.gLight})`,
+ 'combined-strategies|dark': `Carbon / Dark (${this.gDark})`,
+ },
+ };
+ }
+
+ onSelectPageTheme = (event: Event) => {
+ const select = event.target as HTMLSelectElement;
+ const selectValue = select.value;
+
+ const [selectedStylesheet, selectedTheme] = selectValue.split('|') as [
+ ShwStylesheets,
+ HdsThemes | '',
+ ];
+
+ // we set the `currentStylesheet` in the `shwTheming` service
+ this.shwTheming.setStylesheet(selectedStylesheet);
+ // we set the `currentTheme` in the `hdsTheming` service
+ this.hdsTheming.setTheme({
+ theme: selectedTheme === '' ? undefined : selectedTheme,
+ // example of how a consumer could use the `onSetTheme` callback by passing it to the `setTheme` function as extra option
+ // onSetTheme: ({ currentTheme, currentMode }) => {
+ // console.log(
+ // '➡️ LOCAL INVOCATION via setShwHdsThemes callback',
+ // currentTheme,
+ // currentMode,
+ // );
+ // },
+ });
+ };
+
+
+
+ {{#each-in this.themingOptions as |groupLabel options|}}
+
+ {{#each-in options as |value label|}}
+ {{label}}
+ {{/each-in}}
+
+ {{/each-in}}
+
+
+}
diff --git a/showcase/app/controllers/application.ts b/showcase/app/controllers/application.ts
index 79d6ff8ac42..b1dbad2a4b7 100644
--- a/showcase/app/controllers/application.ts
+++ b/showcase/app/controllers/application.ts
@@ -10,14 +10,18 @@ import { tracked } from '@glimmer/tracking';
import type RouterService from '@ember/routing/router-service';
import type Owner from '@ember/owner';
+import HdsThemingService from '@hashicorp/design-system-components/services/hds-theming';
+
export default class ApplicationController extends Controller {
@service declare readonly router: RouterService;
+ @service declare readonly hdsTheming: HdsThemingService;
@tracked isFrameless = false;
constructor(owner: Owner) {
super(owner);
this.router.on('routeDidChange', this.routeDidChange.bind(this));
+ this.hdsTheming.initializeTheme();
}
routeDidChange() {
diff --git a/showcase/app/index.html b/showcase/app/index.html
index 116b91e14f9..e6cba2057a5 100644
--- a/showcase/app/index.html
+++ b/showcase/app/index.html
@@ -14,6 +14,14 @@
{{content-for "head"}}
+
+
+
+
+
+
@@ -26,7 +34,7 @@
-
+
{{content-for "body-footer"}}