Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stepper::Navigation and Stepper::List: Create components #2714

Open
wants to merge 50 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
5f42383
HDS-4411 `Stepper::Navigation` and `Stepper::List` components added
dchyun Feb 19, 2025
131d336
Fix: A11y improvements
dchyun Feb 19, 2025
f2dc928
Feat: Add panel component, align to tabs implementation
dchyun Feb 21, 2025
f16816e
Fix: a11y structure improvements for non-interactive steps
dchyun Feb 21, 2025
796f43b
Feat: body block when using `@steps`
dchyun Feb 21, 2025
f5f1215
Fix: showcase active state and a11y automated tests
dchyun Feb 21, 2025
8f5a977
Feat: Tests added, Bugs from tests fixed in list and navigation
dchyun Feb 24, 2025
344d81d
Fix: Convert did-insert to custom modifier
dchyun Feb 24, 2025
fae4a80
Feat: Assertion for number of panels and steps
dchyun Feb 24, 2025
d8f2b69
Chore: Linting fixes
dchyun Feb 24, 2025
9b773e5
Feat: Design style alignment
dchyun Feb 25, 2025
e755a58
Feat: Move isInteractive from `Navigation::Step` to `Navigation`, hav…
dchyun Feb 26, 2025
d750208
Feat: Rename `Stepper::Navigation` to `Stepper::Nav`
dchyun Feb 26, 2025
7faef9e
Fix: Prevent click and keyboard on non-interactive steps
dchyun Feb 26, 2025
30a3e0e
Chore: Linting fixes
dchyun Feb 26, 2025
b5180c7
Fix: Align class names in List
dchyun Feb 26, 2025
f9957ea
Fix: List failing test
dchyun Feb 26, 2025
50b6b8a
Fix: Navigation interactive title color
dchyun Feb 26, 2025
0e61c4d
Fix: Remove stepNumber arg from step sub components
dchyun Feb 27, 2025
6451a4d
Chore: Linting fixes
dchyun Feb 27, 2025
2062f70
Feat: Convert list step to custom modifier
dchyun Feb 27, 2025
6daef3f
Chore: Add changeset
dchyun Feb 27, 2025
a3281e1
Fix: Address design feedback
dchyun Mar 3, 2025
24c8f68
Feat: `Stepper::Nav` mobile design
dchyun Mar 3, 2025
4fa8bbb
Chore: Address changeset feedback
dchyun Mar 4, 2025
4ffe851
Fix: Align to BEM class naming
dchyun Mar 4, 2025
4e9a99e
Fix: Address list feedback
dchyun Mar 5, 2025
8297449
Fix: Remove insert & destroy actions from `Stepper::List`
dchyun Mar 5, 2025
997ac87
Fix: Prevent steps from getting set to complete initially
dchyun Mar 6, 2025
aab5c15
Feat: Separate stepper showcase to sub pages
dchyun Mar 6, 2025
ea815fc
Fix: Address a11y feedback
dchyun Mar 6, 2025
53efb38
Fix: Address feedback
dchyun Mar 6, 2025
6625c92
Feat: `Stepper::Nav` animation
dchyun Mar 7, 2025
b009819
Fix: Add showcase list examples and cleanup
dchyun Mar 7, 2025
598136d
Feat: Nav support for not using panel blocks
dchyun Mar 7, 2025
3d85f79
Feat: Add `ariaLabel` arg for nav and list
dchyun Mar 7, 2025
a6c0637
Fix: Address a11y feedback
dchyun Mar 7, 2025
4495935
Fix: Step ordering issue when steps added or removed
dchyun Mar 10, 2025
7ebf05b
Chore: Showcase linting
dchyun Mar 10, 2025
40c3b2e
Chore: component linting
dchyun Mar 10, 2025
824eca4
Fix: Nav style and animation bugs
dchyun Mar 11, 2025
92e1d23
Fix: Address style color feedback
dchyun Mar 11, 2025
a98b247
Feat: Style updates for nav interactive indicator
dchyun Mar 12, 2025
fc29946
Feat: Nav - set `isInteractive` default value to `true`
dchyun Mar 12, 2025
03ed345
Fix: Set description color on list
dchyun Mar 12, 2025
4e9e3e3
Fix: `List::Step` status set to optional
dchyun Mar 13, 2025
cef1bc6
Fix: Address code feedback
dchyun Mar 14, 2025
74c6bc1
Fix: Nav sr only text placement
dchyun Mar 14, 2025
6de254f
Fix: Refactor Nav methods
dchyun Mar 14, 2025
408ea54
Fix: List progress bar visiblity
dchyun Mar 19, 2025
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
7 changes: 7 additions & 0 deletions .changeset/four-jokes-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@hashicorp/design-system-components": minor
---

`Stepper::List` - Added `Stepper::List` component and related sub-components

`Stepper::Navigation` - Added `Stepper::Navigation` component and related sub-components
5 changes: 5 additions & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,11 @@
"./components/hds/side-nav/portal/index.js": "./dist/_app_/components/hds/side-nav/portal/index.js",
"./components/hds/side-nav/portal/target.js": "./dist/_app_/components/hds/side-nav/portal/target.js",
"./components/hds/side-nav/toggle-button.js": "./dist/_app_/components/hds/side-nav/toggle-button.js",
"./components/hds/stepper/list/index.js": "./dist/_app_/components/hds/stepper/list/index.js",
"./components/hds/stepper/list/step.js": "./dist/_app_/components/hds/stepper/list/step.js",
"./components/hds/stepper/nav/index.js": "./dist/_app_/components/hds/stepper/nav/index.js",
"./components/hds/stepper/nav/panel.js": "./dist/_app_/components/hds/stepper/nav/panel.js",
"./components/hds/stepper/nav/step.js": "./dist/_app_/components/hds/stepper/nav/step.js",
"./components/hds/stepper/step/indicator.js": "./dist/_app_/components/hds/stepper/step/indicator.js",
"./components/hds/stepper/task/indicator.js": "./dist/_app_/components/hds/stepper/task/indicator.js",
"./components/hds/table/index.js": "./dist/_app_/components/hds/table/index.js",
Expand Down
5 changes: 5 additions & 0 deletions packages/components/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,11 @@ export { default as HdsSideNavPortalTarget } from './components/hds/side-nav/por
export { default as HdsSideNavToggleButton } from './components/hds/side-nav/toggle-button.ts';

// Stepper
export { default as HdsStepperList } from './components/hds/stepper/list/index.ts';
export { default as HdsStepperListStep } from './components/hds/stepper/list/step.ts';
export { default as HdsStepperNav } from './components/hds/stepper/nav/index.ts';
export { default as HdsStepperNavPanel } from './components/hds/stepper/nav/panel.ts';
export { default as HdsStepperNavStep } from './components/hds/stepper/nav/step.ts';
export { default as HdsStepperStepIndicator } from './components/hds/stepper/step/indicator.ts';
export { default as HdsStepperTaskIndicator } from './components/hds/stepper/task/indicator.ts';
export * from './components/hds/stepper/types.ts';
Expand Down
17 changes: 17 additions & 0 deletions packages/components/src/components/hds/stepper/list/index.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: MPL-2.0
}}
<ol class="hds-stepper-list" aria-label={{@ariaLabel}} ...attributes>
{{yield
(hash
Step=(component
"hds/stepper/list/step"
titleTag=this.titleTag
stepIds=this._stepIds
didInsertNode=this.didInsertStep
willDestroyNode=this.willDestroyStep
)
)
}}
</ol>
52 changes: 52 additions & 0 deletions packages/components/src/components/hds/stepper/list/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { schedule } from '@ember/runloop';
import type { ComponentLike } from '@glint/template';
import type { HdsStepperListStepSignature } from './step';

import { HdsStepperTitleTagValues } from '../types.ts';
import type { HdsStepperTitleTags, HdsStepperListStepIds } from '../types.ts';

export interface HdsStepperListSignature {
Args: {
titleTag?: HdsStepperTitleTags;
ariaLabel: string;
};
Blocks: {
default: [
{
Step?: ComponentLike<HdsStepperListStepSignature>;
},
];
};
Element: HTMLElement;
}

export default class HdsStepperList extends Component<HdsStepperListSignature> {
@tracked private _stepIds: HdsStepperListStepIds = [];

get titleTag(): HdsStepperTitleTags {
return this.args.titleTag ?? HdsStepperTitleTagValues.Div;
}

@action
didInsertStep(element: HTMLElement): void {
// eslint-disable-next-line ember/no-runloop
schedule('afterRender', (): void => {
this._stepIds = [...this._stepIds, element.id];
});
}

@action
willDestroyStep(element: HTMLElement): void {
this._stepIds = this._stepIds.filter(
(stepId): boolean => stepId !== element.id
);
}
}
32 changes: 32 additions & 0 deletions packages/components/src/components/hds/stepper/list/step.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: MPL-2.0
}}
<li class={{this.classNames}} ...attributes id={{this._stepId}} {{this._setUpStep @didInsertNode @willDestroyNode}}>
<div class="hds-stepper-list__step-progress">
<Hds::Stepper::Step::Indicator
@text="{{this.stepNumber}}"
@status={{this.status}}
@isInteractive={{false}}
class="hds-stepper-list__step-indicator"
/>
</div>
<div class="hds-stepper-list__step-text">
<Hds::Text::Body class="hds-stepper-list__step-title" @tag={{this.titleTag}} @size="200" @weight="semibold">
{{yield to="title"}}
<span class="sr-only">{{this.statusSrOnlyText}}</span>
</Hds::Text::Body>
<Hds::Text::Body
class="hds-stepper-list__step-description"
@tag="div"
@size="100"
@weight="regular"
@color="regular"
>
{{yield to="description"}}
</Hds::Text::Body>
<div class="hds-stepper-list__step-content">
{{yield to="content"}}
</div>
</div>
</li>
96 changes: 96 additions & 0 deletions packages/components/src/components/hds/stepper/list/step.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

import Component from '@glimmer/component';
import { assert } from '@ember/debug';
import { guidFor } from '@ember/object/internals';
import { modifier } from 'ember-modifier';

import {
HdsStepperStatusesValues,
HdsStepperTitleTagValues,
} from '../types.ts';
import {
type HdsStepperStatuses,
type HdsStepperTitleTags,
type HdsStepperListStepIds,
HdsStepperStatusToSrOnlyText,
} from '../types.ts';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dchyun Should the last one go above? And then you could import the types together without repeating "type".

Like:

  HdsStepperStatusesValues,
  HdsStepperTitleTagValues,
  HdsStepperStatusToSrOnlyText,
} from '../types.ts';

import type {
  HdsStepperStatuses,
  HdsStepperTitleTags,
  HdsStepperListStepIds,
} from '../types.ts';


export const DEFAULT_STATUS = HdsStepperStatusesValues.Incomplete;
export const STATUSES: string[] = Object.values(HdsStepperStatusesValues);
export const MAPPING_STATUS_TO_SR_ONLY_TEXT = HdsStepperStatusToSrOnlyText;

export interface HdsStepperListStepSignature {
Args: {
status?: HdsStepperStatusesValues;
titleTag?: HdsStepperTitleTags;
stepIds?: HdsStepperListStepIds;
didInsertNode?: (element: HTMLElement) => void;
willDestroyNode?: (element: HTMLElement) => void;
};
Blocks: {
title: [];
description?: [];
content?: [];
};
Element: HTMLElement;
}

export default class HdsStepperListStep extends Component<HdsStepperListStepSignature> {
private _stepId = 'step-' + guidFor(this);

private _setUpStep = modifier(
(
element: HTMLElement,
[insertCallbackFunction, destoryCallbackFunction]
) => {
if (typeof insertCallbackFunction === 'function') {
insertCallbackFunction(element);
}

return () => {
if (typeof destoryCallbackFunction === 'function') {
destoryCallbackFunction(element);
}
};
}
);

get stepNumber(): number | undefined {
return this.args.stepIds
? this.args.stepIds.indexOf(this._stepId) + 1
: undefined;
}

get status(): HdsStepperStatuses {
const { status = DEFAULT_STATUS } = this.args;

assert(
`@status for "Hds::Stepper::List::Step" must be one of the following: ${STATUSES.join(
', '
)}; received: ${status}`,
STATUSES.includes(status)
);

return status;
}

get statusSrOnlyText(): string {
return MAPPING_STATUS_TO_SR_ONLY_TEXT[this.status];
}

get titleTag(): HdsStepperTitleTags {
return this.args.titleTag ?? HdsStepperTitleTagValues.Div;
}

get classNames(): string {
const classes = ['hds-stepper-list__step'];

classes.push(`hds-stepper-list__step--${this.status}`);

return classes.join(' ');
}
}
76 changes: 76 additions & 0 deletions packages/components/src/components/hds/stepper/nav/index.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: MPL-2.0
}}
<div
class={{this.classNames}}
...attributes
{{style --progress-bar-width=this.progressBarWidthStyle}}
{{this._setUpStepperNav}}
>
<ol class="hds-stepper-nav__list" aria-label={{@ariaLabel}} role={{if this.isInteractive "tablist"}}>
{{#if @steps}}
{{#each @steps as |step|}}
<Hds::Stepper::Nav::Step
@currentStep={{this.currentStep}}
@isNavInteractive={{this.isInteractive}}
@titleTag={{this.titleTag}}
@didInsertNode={{this.didInsertStep}}
@willDestroyNode={{this.willDestroyStep}}
@stepIds={{this._stepIds}}
@panelIds={{this._panelIds}}
@onStepChange={{@onStepChange}}
@onKeyUp={{this.onKeyUp}}
>
<:title>{{step.title}}</:title>
<:description>{{step.description}}</:description>
</Hds::Stepper::Nav::Step>
{{/each}}
{{else}}
{{yield
(hash
Step=(component
"hds/stepper/nav/step"
currentStep=this.currentStep
isNavInteractive=this.isInteractive
titleTag=this.titleTag
stepIds=this._stepIds
panelIds=this._panelIds
didInsertNode=this.didInsertStep
willDestroyNode=this.willDestroyStep
onStepChange=@onStepChange
onKeyUp=this.onKeyUp
)
)
}}
{{/if}}
</ol>
{{#if (and @steps (has-block "body"))}}
{{#each @steps}}
<Hds::Stepper::Nav::Panel
@currentStep={{this.currentStep}}
@isNavInteractive={{this.isInteractive}}
@stepIds={{this._stepIds}}
@panelIds={{this._panelIds}}
@didInsertNode={{this.didInsertPanel}}
@willDestroyNode={{this.willDestroyPanel}}
>
{{yield to="body"}}
</Hds::Stepper::Nav::Panel>
{{/each}}
{{else}}
{{yield
(hash
Panel=(component
"hds/stepper/nav/panel"
currentStep=this.currentStep
isNavInteractive=this.isInteractive
stepIds=this._stepIds
panelIds=this._panelIds
didInsertNode=this.didInsertPanel
willDestroyNode=this.willDestroyPanel
)
)
}}
{{/if}}
</div>
Loading
Loading