diff --git a/change/@ni-nimble-components-ceebd404-0fb3-43e5-8f98-04f4eed64531.json b/change/@ni-nimble-components-ceebd404-0fb3-43e5-8f98-04f4eed64531.json new file mode 100644 index 0000000000..7a92885914 --- /dev/null +++ b/change/@ni-nimble-components-ceebd404-0fb3-43e5-8f98-04f4eed64531.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "Add interaction stories for buttons, number field, and anchor", + "packageName": "@ni/nimble-components", + "email": "7282195+m-akinc@users.noreply.github.com", + "dependentChangeType": "none" +} diff --git a/package-lock.json b/package-lock.json index 4f4fd874ca..b7d1560a0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29445,6 +29445,29 @@ "url": "https://opencollective.com/storybook" } }, + "node_modules/storybook-addon-pseudo-states": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/storybook-addon-pseudo-states/-/storybook-addon-pseudo-states-2.2.1.tgz", + "integrity": "sha512-4LoaiML0BM9sZcQbXjDhRh9jUUKIRTWEQMl91ihP2wIE10n+rL/5c8IBpNiMZLV1rnm24degEncSMY9ck+bpgg==", + "dev": true, + "peerDependencies": { + "@storybook/components": "^7.4.6", + "@storybook/core-events": "^7.4.6", + "@storybook/manager-api": "^7.4.6", + "@storybook/preview-api": "^7.4.6", + "@storybook/theming": "^7.4.6", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/stream-shift": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", @@ -33287,6 +33310,7 @@ "rollup-plugin-sourcemaps": "^0.6.3", "source-map-loader": "^5.0.0", "storybook": "^8.0.4", + "storybook-addon-pseudo-states": "^2.2.1", "terser-webpack-plugin": "^5.3.10", "ts-loader": "^9.2.5", "typescript": "~4.9.5", diff --git a/packages/nimble-components/.storybook/main.js b/packages/nimble-components/.storybook/main.js index 59cdfe2581..dda6dc10f8 100644 --- a/packages/nimble-components/.storybook/main.js +++ b/packages/nimble-components/.storybook/main.js @@ -27,7 +27,8 @@ export const addons = [ getAbsolutePath('@storybook/addon-a11y'), getAbsolutePath('@storybook/addon-interactions'), getAbsolutePath('@chromatic-com/storybook'), - getAbsolutePath('@storybook/addon-webpack5-compiler-swc') + getAbsolutePath('@storybook/addon-webpack5-compiler-swc'), + getAbsolutePath('storybook-addon-pseudo-states') ]; export function webpackFinal(config) { config.module.rules.push({ diff --git a/packages/nimble-components/.storybook/manager.js b/packages/nimble-components/.storybook/manager.js index 1159c9cc0c..4a9b2df3af 100644 --- a/packages/nimble-components/.storybook/manager.js +++ b/packages/nimble-components/.storybook/manager.js @@ -15,5 +15,8 @@ addons.setConfig({ } } }, - theme + theme, + toolbar: { + 'storybook/pseudo-states/tool': { hidden: true } + } }); diff --git a/packages/nimble-components/package.json b/packages/nimble-components/package.json index 53af8b72ba..35f970487d 100644 --- a/packages/nimble-components/package.json +++ b/packages/nimble-components/package.json @@ -162,6 +162,7 @@ "rollup-plugin-sourcemaps": "^0.6.3", "source-map-loader": "^5.0.0", "storybook": "^8.0.4", + "storybook-addon-pseudo-states": "^2.2.1", "terser-webpack-plugin": "^5.3.10", "ts-loader": "^9.2.5", "typescript": "~4.9.5", diff --git a/packages/nimble-components/src/anchor-button/tests/anchor-button-matrix.stories.ts b/packages/nimble-components/src/anchor-button/tests/anchor-button-matrix.stories.ts index 5306713f61..e2cdb65459 100644 --- a/packages/nimble-components/src/anchor-button/tests/anchor-button-matrix.stories.ts +++ b/packages/nimble-components/src/anchor-button/tests/anchor-button-matrix.stories.ts @@ -3,9 +3,15 @@ import { html, ViewTemplate, when } from '@microsoft/fast-element'; import { createMatrix, sharedMatrixParameters, - createMatrixThemeStory + createMatrixThemeStory, + cartesianProduct, + createMatrixInteractionsFromStates } from '../../utilities/tests/matrix'; -import { disabledStates, DisabledState } from '../../utilities/tests/states'; +import { + disabledStates, + DisabledState, + disabledStateIsEnabled +} from '../../utilities/tests/states'; import { createStory } from '../../utilities/tests/storybook'; import { hiddenWrapper } from '../../utilities/tests/hidden'; import { textCustomizationWrapper } from '../../utilities/tests/text-customization'; @@ -18,7 +24,8 @@ import { type AppearanceVariantState, type PartVisibilityState, appearanceVariantStates, - partVisibilityStates + partVisibilityStates, + partVisibilityStatesOnlyLabel } from '../../patterns/button/tests/states'; const metadata: Meta = { @@ -59,6 +66,29 @@ export const anchorButtonThemeMatrix: StoryFn = createMatrixThemeStory( ]) ); +const interactionStatesHover = cartesianProduct([ + disabledStates, + appearanceStates, + appearanceVariantStates, + [partVisibilityStatesOnlyLabel] +] as const); + +const interactionStates = cartesianProduct([ + [disabledStateIsEnabled], + appearanceStates, + appearanceVariantStates, + [partVisibilityStatesOnlyLabel] +] as const); + +export const anchorButtonInteractionsThemeMatrix: StoryFn = createMatrixThemeStory( + createMatrixInteractionsFromStates(component, { + hover: interactionStatesHover, + hoverActive: interactionStates, + active: interactionStates, + focus: interactionStates + }) +); + export const hiddenAnchorButton: StoryFn = createStory( hiddenWrapper( html`<${anchorButtonTag} hidden diff --git a/packages/nimble-components/src/anchor/tests/anchor-matrix.stories.ts b/packages/nimble-components/src/anchor/tests/anchor-matrix.stories.ts index 255bc21f20..8d05a24f04 100644 --- a/packages/nimble-components/src/anchor/tests/anchor-matrix.stories.ts +++ b/packages/nimble-components/src/anchor/tests/anchor-matrix.stories.ts @@ -4,7 +4,9 @@ import { pascalCase } from '@microsoft/fast-web-utilities'; import { createMatrix, sharedMatrixParameters, - createMatrixThemeStory + createMatrixThemeStory, + cartesianProduct, + createMatrixInteractionsFromStates } from '../../utilities/tests/matrix'; import { createStory } from '../../utilities/tests/storybook'; import { hiddenWrapper } from '../../utilities/tests/hidden'; @@ -12,6 +14,11 @@ import { textCustomizationWrapper } from '../../utilities/tests/text-customizati import { AnchorAppearance } from '../types'; import { bodyFont } from '../../theme-provider/design-tokens'; import { anchorTag } from '..'; +import { + disabledStates, + type DisabledState, + disabledStateIsEnabled +} from '../../utilities/tests/states'; const metadata: Meta = { title: 'Tests/Anchor', @@ -22,12 +29,6 @@ const metadata: Meta = { export default metadata; -const disabledStates = [ - ['', 'https://nimble.ni.dev'], - ['Disabled', null] -] as const; -type DisabledState = (typeof disabledStates)[number]; - const underlineHiddenStates = [ ['', false], ['Underline Hidden', true] @@ -41,12 +42,12 @@ type AppearanceState = (typeof appearanceStates)[number]; // prettier-ignore const component = ( - [disabledName, href]: DisabledState, + [disabledName, disabled]: DisabledState, [underlineHiddenName, underlineHidden]: UnderlineHiddenState, [appearanceName, appearance]: AppearanceState ): ViewTemplate => html` <${anchorTag} - href=${() => href} + href=${() => (disabled ? undefined : 'https://nimble.ni.dev')} ?underline-hidden="${() => underlineHidden}" appearance="${() => appearance}" style="margin-right: 8px; margin-bottom: 8px;"> @@ -61,6 +62,27 @@ export const anchorThemeMatrix: StoryFn = createMatrixThemeStory( ]) ); +const interactionStatesHover = cartesianProduct([ + disabledStates, + underlineHiddenStates, + appearanceStates +] as const); + +const interactionStates = cartesianProduct([ + [disabledStateIsEnabled], + underlineHiddenStates, + appearanceStates +] as const); + +export const anchorInteractionsThemeMatrix: StoryFn = createMatrixThemeStory( + createMatrixInteractionsFromStates(component, { + hover: interactionStatesHover, + hoverActive: interactionStates, + active: interactionStates, + focus: interactionStates + }) +); + export const hiddenAnchor: StoryFn = createStory( hiddenWrapper(html`<${anchorTag} hidden>Hidden Anchor`) ); diff --git a/packages/nimble-components/src/button/tests/button-matrix.stories.ts b/packages/nimble-components/src/button/tests/button-matrix.stories.ts index fc0e33694d..2b0231ede1 100644 --- a/packages/nimble-components/src/button/tests/button-matrix.stories.ts +++ b/packages/nimble-components/src/button/tests/button-matrix.stories.ts @@ -3,9 +3,15 @@ import { html, ViewTemplate, when } from '@microsoft/fast-element'; import { createMatrix, sharedMatrixParameters, - createMatrixThemeStory + createMatrixThemeStory, + cartesianProduct, + createMatrixInteractionsFromStates } from '../../utilities/tests/matrix'; -import { disabledStates, DisabledState } from '../../utilities/tests/states'; +import { + disabledStates, + DisabledState, + disabledStateIsEnabled +} from '../../utilities/tests/states'; import { createStory } from '../../utilities/tests/storybook'; import { hiddenWrapper } from '../../utilities/tests/hidden'; import { textCustomizationWrapper } from '../../utilities/tests/text-customization'; @@ -19,7 +25,8 @@ import { type AppearanceVariantState, type PartVisibilityState, appearanceVariantStates, - partVisibilityStates + partVisibilityStates, + partVisibilityStatesOnlyLabel } from '../../patterns/button/tests/states'; const metadata: Meta = { @@ -59,6 +66,29 @@ export const buttonThemeMatrix: StoryFn = createMatrixThemeStory( ]) ); +const interactionStates = cartesianProduct([ + [disabledStateIsEnabled], + appearanceStates, + appearanceVariantStates, + [partVisibilityStatesOnlyLabel] +] as const); + +const interactionStatesHover = cartesianProduct([ + disabledStates, + appearanceStates, + appearanceVariantStates, + [partVisibilityStatesOnlyLabel] +] as const); + +export const buttonInteractionsThemeMatrix: StoryFn = createMatrixThemeStory( + createMatrixInteractionsFromStates(component, { + hover: interactionStatesHover, + hoverActive: interactionStates, + active: interactionStates, + focus: interactionStates + }) +); + export const hiddenButton: StoryFn = createStory( hiddenWrapper(html`<${buttonTag} hidden>Hidden Button`) ); diff --git a/packages/nimble-components/src/menu-button/tests/menu-button-matrix.stories.ts b/packages/nimble-components/src/menu-button/tests/menu-button-matrix.stories.ts index 14f7d570cd..65a5598338 100644 --- a/packages/nimble-components/src/menu-button/tests/menu-button-matrix.stories.ts +++ b/packages/nimble-components/src/menu-button/tests/menu-button-matrix.stories.ts @@ -3,23 +3,28 @@ import { html, ViewTemplate, when } from '@microsoft/fast-element'; import { createMatrix, sharedMatrixParameters, - createMatrixThemeStory + createMatrixThemeStory, + cartesianProduct, + createMatrixInteractionsFromStates } from '../../utilities/tests/matrix'; -import { disabledStates, DisabledState } from '../../utilities/tests/states'; +import { + disabledStates, + DisabledState, + disabledStateIsEnabled +} from '../../utilities/tests/states'; import { createStory } from '../../utilities/tests/storybook'; import { hiddenWrapper } from '../../utilities/tests/hidden'; import { iconArrowExpanderDownTag } from '../../icons/arrow-expander-down'; import { iconKeyTag } from '../../icons/key'; import { menuButtonTag } from '..'; -import { menuTag } from '../../menu'; -import { menuItemTag } from '../../menu-item'; import { appearanceStates, type AppearanceState, type AppearanceVariantState, type PartVisibilityState, appearanceVariantStates, - partVisibilityStates + partVisibilityStates, + partVisibilityStatesOnlyLabel } from '../../patterns/button/tests/states'; const metadata: Meta = { @@ -31,8 +36,15 @@ const metadata: Meta = { export default metadata; +const openStates = [ + ['', false], + ['Open', true] +] as const; +type OpenState = (typeof openStates)[number]; + // prettier-ignore const component = ( + [openName, open]: OpenState, [iconVisible, labelVisible, endIconVisible]: PartVisibilityState, [disabledName, disabled]: DisabledState, [appearanceName, appearance]: AppearanceState, @@ -41,22 +53,19 @@ const component = ( <${menuButtonTag} appearance="${() => appearance}" appearance-variant="${() => appearanceVariant}" + ?open="${() => open}" ?disabled=${() => disabled} ?content-hidden=${() => !labelVisible} style="margin-right: 8px; margin-bottom: 8px;"> ${when(() => iconVisible, html`<${iconKeyTag} slot="start">`)} - ${() => `${appearanceVariantName} ${appearanceName} Menu Button ${disabledName}`} + ${() => `${openName} ${appearanceVariantName} ${appearanceName} Menu Button ${disabledName}`} ${when(() => endIconVisible, html`<${iconArrowExpanderDownTag} slot="end">`)} - - <${menuTag} slot="menu"> - <${menuItemTag}>Item 1 - <${menuItemTag}>Item 2 - `; export const menuButtonThemeMatrix: StoryFn = createMatrixThemeStory( createMatrix(component, [ + openStates, partVisibilityStates, disabledStates, appearanceStates, @@ -64,6 +73,31 @@ export const menuButtonThemeMatrix: StoryFn = createMatrixThemeStory( ]) ); +const interactionStatesHover = cartesianProduct([ + openStates, + [partVisibilityStatesOnlyLabel], + disabledStates, + appearanceStates, + appearanceVariantStates +] as const); + +const interactionStates = cartesianProduct([ + openStates, + [partVisibilityStatesOnlyLabel], + [disabledStateIsEnabled], + appearanceStates, + appearanceVariantStates +] as const); + +export const menuButtonInteractionsThemeMatrix: StoryFn = createMatrixThemeStory( + createMatrixInteractionsFromStates(component, { + hover: interactionStatesHover, + hoverActive: interactionStates, + active: interactionStates, + focus: interactionStates + }) +); + export const hiddenMenuButton: StoryFn = createStory( hiddenWrapper( html`<${menuButtonTag} hidden>Hidden Menu Button` diff --git a/packages/nimble-components/src/number-field/tests/number-field-matrix.stories.ts b/packages/nimble-components/src/number-field/tests/number-field-matrix.stories.ts index a6f3f35220..56ea4cbcc2 100644 --- a/packages/nimble-components/src/number-field/tests/number-field-matrix.stories.ts +++ b/packages/nimble-components/src/number-field/tests/number-field-matrix.stories.ts @@ -5,13 +5,18 @@ import { createStory } from '../../utilities/tests/storybook'; import { createMatrixThemeStory, createMatrix, - sharedMatrixParameters + sharedMatrixParameters, + cartesianProduct, + createMatrixInteractionsFromStates } from '../../utilities/tests/matrix'; import { disabledStates, DisabledState, errorStates, - ErrorState + ErrorState, + disabledStateIsEnabled, + errorStatesNoError, + errorStatesErrorWithMessage } from '../../utilities/tests/states'; import { hiddenWrapper } from '../../utilities/tests/hidden'; import { NumberFieldAppearance } from '../types'; @@ -31,6 +36,7 @@ const valueStates = [ ['Value', '1234', null] ] as const; type ValueState = (typeof valueStates)[number]; +const valueStatesHasValue = valueStates[1]; const appearanceStates = Object.entries(NumberFieldAppearance).map( ([key, value]) => [pascalCase(key), value] @@ -42,6 +48,7 @@ const hideStepStates = [ ['Hide Step', true] ] as const; type HideStepState = (typeof hideStepStates)[number]; +const hideStepStateStepVisible = hideStepStates[0]; const component = ( [disabledName, disabled]: DisabledState, @@ -52,7 +59,6 @@ const component = ( ): ViewTemplate => html` <${numberFieldTag} style="width: 250px; padding: 8px;" - class="${() => errorVisible}" value="${() => valueValue}" placeholder="${() => placeholderValue}" appearance="${() => appearance}" @@ -76,6 +82,31 @@ export const numberFieldThemeMatrix: StoryFn = createMatrixThemeStory( ]) ); +const interactionStatesHover = cartesianProduct([ + disabledStates, + [hideStepStateStepVisible], + [valueStatesHasValue], + [errorStatesNoError, errorStatesErrorWithMessage], + appearanceStates +] as const); + +const interactionStates = cartesianProduct([ + [disabledStateIsEnabled], + [hideStepStateStepVisible], + [valueStatesHasValue], + [errorStatesNoError, errorStatesErrorWithMessage], + appearanceStates +] as const); + +export const numberFieldInteractionsThemeMatrix: StoryFn = createMatrixThemeStory( + createMatrixInteractionsFromStates(component, { + hover: interactionStatesHover, + hoverActive: interactionStates, + active: interactionStates, + focus: interactionStates + }) +); + export const hiddenNumberField: StoryFn = createStory( hiddenWrapper( html` diff --git a/packages/nimble-components/src/patterns/button/tests/states.ts b/packages/nimble-components/src/patterns/button/tests/states.ts index 7ea753741f..bbc0eb425b 100644 --- a/packages/nimble-components/src/patterns/button/tests/states.ts +++ b/packages/nimble-components/src/patterns/button/tests/states.ts @@ -9,6 +9,7 @@ export const partVisibilityStates = [ [false, true, true] ] as const; export type PartVisibilityState = (typeof partVisibilityStates)[number]; +export const partVisibilityStatesOnlyLabel = partVisibilityStates[2]; export const appearanceStates = [ ['Outline', ButtonAppearance.outline], diff --git a/packages/nimble-components/src/toggle-button/tests/toggle-button-matrix.stories.ts b/packages/nimble-components/src/toggle-button/tests/toggle-button-matrix.stories.ts index 7e6fc7e002..1be9058b29 100644 --- a/packages/nimble-components/src/toggle-button/tests/toggle-button-matrix.stories.ts +++ b/packages/nimble-components/src/toggle-button/tests/toggle-button-matrix.stories.ts @@ -3,9 +3,15 @@ import { html, ViewTemplate, when } from '@microsoft/fast-element'; import { createMatrix, sharedMatrixParameters, - createMatrixThemeStory + createMatrixThemeStory, + cartesianProduct, + createMatrixInteractionsFromStates } from '../../utilities/tests/matrix'; -import { disabledStates, DisabledState } from '../../utilities/tests/states'; +import { + disabledStates, + DisabledState, + disabledStateIsEnabled +} from '../../utilities/tests/states'; import { createStory } from '../../utilities/tests/storybook'; import { hiddenWrapper } from '../../utilities/tests/hidden'; import { textCustomizationWrapper } from '../../utilities/tests/text-customization'; @@ -18,7 +24,8 @@ import { type AppearanceVariantState, type PartVisibilityState, appearanceVariantStates, - partVisibilityStates + partVisibilityStates, + partVisibilityStatesOnlyLabel } from '../../patterns/button/tests/states'; const metadata: Meta = { @@ -67,6 +74,31 @@ export const toggleButtonThemeMatrix: StoryFn = createMatrixThemeStory( ]) ); +const interactionStatesHover = cartesianProduct([ + [partVisibilityStatesOnlyLabel], + checkedStates, + disabledStates, + appearanceStates, + appearanceVariantStates +] as const); + +const interactionStates = cartesianProduct([ + [partVisibilityStatesOnlyLabel], + checkedStates, + [disabledStateIsEnabled], + appearanceStates, + appearanceVariantStates +] as const); + +export const toggleButtonInteractionsThemeMatrix: StoryFn = createMatrixThemeStory( + createMatrixInteractionsFromStates(component, { + hover: interactionStatesHover, + hoverActive: interactionStates, + active: interactionStates, + focus: interactionStates + }) +); + export const hiddenButton: StoryFn = createStory( hiddenWrapper( html`<${toggleButtonTag} hidden diff --git a/packages/nimble-components/src/utilities/tests/matrix.ts b/packages/nimble-components/src/utilities/tests/matrix.ts index c98dc83b0b..1e641a4592 100644 --- a/packages/nimble-components/src/utilities/tests/matrix.ts +++ b/packages/nimble-components/src/utilities/tests/matrix.ts @@ -2,6 +2,7 @@ import { html, repeat, ViewTemplate } from '@microsoft/fast-element'; import { fastParameters, renderViewTemplate } from './storybook'; import { themeProviderTag } from '../../theme-provider'; import { type BackgroundState, backgroundStates } from './states'; +import { bodyFont, bodyFontColor } from '../../theme-provider/design-tokens'; export const sharedMatrixParameters = () => ({ ...fastParameters(), @@ -23,182 +24,15 @@ export const sharedMatrixParameters = () => ({ } }) as const; +type MakeTupleEntriesArrays = { [K in keyof T]: readonly T[K][] }; + /** - * Takes an array of state values that can be used with the template to match the permutations of the provided states. + * Calculates the cartesian product of an array of sets. */ -export function createMatrix(component: () => ViewTemplate): ViewTemplate; - -export function createMatrix( - component: (state1: State1) => ViewTemplate, - dimensions: readonly [readonly State1[]] -): ViewTemplate; - -export function createMatrix( - component: (state1: State1, state2: State2) => ViewTemplate, - dimensions: readonly [readonly State1[], readonly State2[]] -): ViewTemplate; - -export function createMatrix( - component: (state1: State1, state2: State2, state3: State3) => ViewTemplate, - dimensions: readonly [ - readonly State1[], - readonly State2[], - readonly State3[] - ] -): ViewTemplate; - -export function createMatrix( - component: ( - state1: State1, - state2: State2, - state3: State3, - state4: State4 - ) => ViewTemplate, - dimensions: readonly [ - readonly State1[], - readonly State2[], - readonly State3[], - readonly State4[] - ] -): ViewTemplate; - -export function createMatrix( - component: ( - state1: State1, - state2: State2, - state3: State3, - state4: State4, - state5: State5 - ) => ViewTemplate, - dimensions: readonly [ - readonly State1[], - readonly State2[], - readonly State3[], - readonly State4[], - readonly State5[] - ] -): ViewTemplate; - -export function createMatrix( - component: ( - state1: State1, - state2: State2, - state3: State3, - state4: State4, - state5: State5, - state6: State6 - ) => ViewTemplate, - dimensions: readonly [ - readonly State1[], - readonly State2[], - readonly State3[], - readonly State4[], - readonly State5[], - readonly State6[] - ] -): ViewTemplate; - -export function createMatrix< - State1, - State2, - State3, - State4, - State5, - State6, - State7 ->( - component: ( - state1: State1, - state2: State2, - state3: State3, - state4: State4, - state5: State5, - state6: State6, - state7: State7 - ) => ViewTemplate, - dimensions: readonly [ - readonly State1[], - readonly State2[], - readonly State3[], - readonly State4[], - readonly State5[], - readonly State6[], - readonly State7[] - ] -): ViewTemplate; - -export function createMatrix< - State1, - State2, - State3, - State4, - State5, - State6, - State7, - State8 ->( - component: ( - state1: State1, - state2: State2, - state3: State3, - state4: State4, - state5: State5, - state6: State6, - state7: State7, - state8: State8 - ) => ViewTemplate, - dimensions: readonly [ - readonly State1[], - readonly State2[], - readonly State3[], - readonly State4[], - readonly State5[], - readonly State6[], - readonly State7[], - readonly State8[] - ] -): ViewTemplate; - -export function createMatrix< - State1, - State2, - State3, - State4, - State5, - State6, - State7, - State8, - State9 ->( - component: ( - state1: State1, - state2: State2, - state3: State3, - state4: State4, - state5: State5, - state6: State6, - state7: State7, - state8: State8, - state9: State9 - ) => ViewTemplate, - dimensions: readonly [ - readonly State1[], - readonly State2[], - readonly State3[], - readonly State4[], - readonly State5[], - readonly State6[], - readonly State7[], - readonly State8[], - readonly State9[] - ] -): ViewTemplate; - -export function createMatrix( - component: (...states: readonly unknown[]) => ViewTemplate, - dimensions?: readonly (readonly unknown[])[] -): ViewTemplate { - const matrix: ViewTemplate[] = []; +export function cartesianProduct( + dimensions?: MakeTupleEntriesArrays +): [...T][] { + const result: [...T][] = []; const recurseDimensions = ( currentDimensions?: readonly (readonly unknown[])[], ...states: readonly unknown[] @@ -209,16 +43,37 @@ export function createMatrix( recurseDimensions(remainingDimensions, ...states, currentState); } } else { - matrix.push(component(...states)); + result.push(states as [...T]); } }; recurseDimensions(dimensions); + return result; +} + +/** + * Passes each of the given state combinations into a template function and returns the combined output. + */ +function createMatrixFromStates( + component: (...states: T) => ViewTemplate, + states: T[] +): ViewTemplate { // prettier-ignore return html` - ${repeat(() => matrix, html` - ${(x: ViewTemplate): ViewTemplate => x} - `)} - `; + ${repeat(() => states, html` + ${(x: T): ViewTemplate => component(...x)} + `)} +`; +} + +/** + * Creates a template that renders all combinations of states in the given dimensions. + */ +export function createMatrix( + component: (...states: T) => ViewTemplate, + dimensions?: MakeTupleEntriesArrays +): ViewTemplate { + const states = cartesianProduct(dimensions); + return createMatrixFromStates(component, states); } /** @@ -247,3 +102,45 @@ export const createMatrixThemeStory = ( return content; }; }; + +export function createMatrixInteractionsFromStates< + THover extends readonly unknown[], + THoverActive extends readonly unknown[], + TActive extends readonly unknown[], + TFocus extends readonly unknown[] +>( + component: ( + ...states: THover | TActive | THoverActive | TFocus + ) => ViewTemplate, + states: { + hover: THover[], + hoverActive: THoverActive[], + active: TActive[], + focus: TFocus[] + } +): ViewTemplate { + // prettier-ignore + return html` +
+
+

Hover

+ ${createMatrixFromStates(component, states.hover)} +
+
+

Hover and active

+ ${createMatrixFromStates(component, states.hoverActive)} +
+
+

Active

+ ${createMatrixFromStates(component, states.active)} +
+
+

Focus

+ ${createMatrixFromStates(component, states.focus)} +
+
+`; +} diff --git a/packages/nimble-components/src/utilities/tests/states.ts b/packages/nimble-components/src/utilities/tests/states.ts index bbe9820e11..91f0c29afb 100644 --- a/packages/nimble-components/src/utilities/tests/states.ts +++ b/packages/nimble-components/src/utilities/tests/states.ts @@ -25,6 +25,7 @@ export const disabledStates = [ ['Disabled', true] ] as const; export type DisabledState = (typeof disabledStates)[number]; +export const disabledStateIsEnabled = disabledStates[0]; export const errorStates = [ ['', false, ''], @@ -32,6 +33,9 @@ export const errorStates = [ ['Error No Message', true, ''] ] as const; export type ErrorState = (typeof errorStates)[number]; +export const errorStatesNoError = errorStates[0]; +export const errorStatesErrorWithMessage = errorStates[1]; +export const errorStatesErrorNoMessage = errorStates[2]; export const readOnlyStates = [ ['', false],