diff --git a/package-lock.json b/package-lock.json
index de19ab0f1..835ba59b1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2819,9 +2819,9 @@
"link": true
},
"node_modules/@cdssnc/gcds-tokens": {
- "version": "1.16.1",
- "resolved": "https://registry.npmjs.org/@cdssnc/gcds-tokens/-/gcds-tokens-1.16.1.tgz",
- "integrity": "sha512-Z9u2WhGfZcREgYoEfWTbz0OFXLiKVpZEgDWT1RZVODB+n8PThKEPBBu3aPnQkz40yIYX10m5G3bTP7NdT5d4+w==",
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/@cdssnc/gcds-tokens/-/gcds-tokens-1.17.0.tgz",
+ "integrity": "sha512-nA5qJGCOSIqKXzMybTdACUdxLThnbwpeLzc2oyH6hL9m5pfAF7QC4aqacvp+tLEky2BCL8T0VL2XVPS9mZNHOw==",
"dev": true
},
"node_modules/@cnakazawa/watch": {
@@ -48548,7 +48548,7 @@
"@babel/core": "^7.20.12",
"@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.21.0",
- "@cdssnc/gcds-tokens": "^1.16.1",
+ "@cdssnc/gcds-tokens": "^1.17.0",
"@fortawesome/fontawesome-free": "^6.3.0",
"@stencil/angular-output-target": "file:../../utils/angular-output-target",
"@stencil/postcss": "^2.1.0",
diff --git a/packages/angular/src/lib/stencil-generated/components.ts b/packages/angular/src/lib/stencil-generated/components.ts
index 4bfb1abe6..455883b17 100644
--- a/packages/angular/src/lib/stencil-generated/components.ts
+++ b/packages/angular/src/lib/stencil-generated/components.ts
@@ -1081,14 +1081,14 @@ export declare interface GcdsSrOnly extends Components.GcdsSrOnly {}
@ProxyCmp({
- inputs: ['currentStep', 'totalSteps']
+ inputs: ['currentStep', 'tag', 'totalSteps']
})
@Component({
selector: 'gcds-stepper',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
- inputs: ['currentStep', 'totalSteps'],
+ inputs: ['currentStep', 'tag', 'totalSteps'],
})
export class GcdsStepper {
protected el: HTMLElement;
diff --git a/packages/vue/lib/components.ts b/packages/vue/lib/components.ts
index febbf3e48..704678de2 100644
--- a/packages/vue/lib/components.ts
+++ b/packages/vue/lib/components.ts
@@ -401,7 +401,8 @@ export const GcdsSrOnly = /*@__PURE__*/ defineContainer('gcds-sr
export const GcdsStepper = /*@__PURE__*/ defineContainer('gcds-stepper', undefined, [
'currentStep',
- 'totalSteps'
+ 'totalSteps',
+ 'tag'
]);
diff --git a/packages/web/package.json b/packages/web/package.json
index 2ae399ece..1ea6c39f1 100644
--- a/packages/web/package.json
+++ b/packages/web/package.json
@@ -45,7 +45,7 @@
"@babel/core": "^7.20.12",
"@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.21.0",
- "@cdssnc/gcds-tokens": "^1.16.1",
+ "@cdssnc/gcds-tokens": "^1.17.0",
"@fortawesome/fontawesome-free": "^6.3.0",
"@stencil/angular-output-target": "file:../../utils/angular-output-target",
"@stencil/postcss": "^2.1.0",
diff --git a/packages/web/src/components.d.ts b/packages/web/src/components.d.ts
index f1701279b..84137bd6c 100644
--- a/packages/web/src/components.d.ts
+++ b/packages/web/src/components.d.ts
@@ -1017,6 +1017,10 @@ export namespace Components {
* Defines the current step.
*/
"currentStep": number;
+ /**
+ * Defines the heading tag to render
+ */
+ "tag": 'h1' | 'h2' | 'h3';
/**
* Defines the total amount of steps.
*/
@@ -2985,6 +2989,10 @@ declare namespace LocalJSX {
* Defines the current step.
*/
"currentStep": number;
+ /**
+ * Defines the heading tag to render
+ */
+ "tag"?: 'h1' | 'h2' | 'h3';
/**
* Defines the total amount of steps.
*/
diff --git a/packages/web/src/components/gcds-sr-only/readme.md b/packages/web/src/components/gcds-sr-only/readme.md
index 86aa46fe9..27b487aa4 100644
--- a/packages/web/src/components/gcds-sr-only/readme.md
+++ b/packages/web/src/components/gcds-sr-only/readme.md
@@ -21,6 +21,7 @@
- [gcds-footer](../gcds-footer)
- [gcds-lang-toggle](../gcds-lang-toggle)
- [gcds-search](../gcds-search)
+ - [gcds-stepper](../gcds-stepper)
- [gcds-topic-menu](../gcds-topic-menu)
### Graph
@@ -31,6 +32,7 @@ graph TD;
gcds-footer --> gcds-sr-only
gcds-lang-toggle --> gcds-sr-only
gcds-search --> gcds-sr-only
+ gcds-stepper --> gcds-sr-only
gcds-topic-menu --> gcds-sr-only
style gcds-sr-only fill:#f9f,stroke:#333,stroke-width:4px
```
diff --git a/packages/web/src/components/gcds-stepper/gcds-stepper.css b/packages/web/src/components/gcds-stepper/gcds-stepper.css
index aafc559b9..e398ff3f1 100644
--- a/packages/web/src/components/gcds-stepper/gcds-stepper.css
+++ b/packages/web/src/components/gcds-stepper/gcds-stepper.css
@@ -7,7 +7,14 @@
}
@layer default {
- :host .gcds-stepper {
- color: var(--gcds-stepper-text);
+ :host .gcds-stepper .gcds-stepper__steps {
+ font: var(--gcds-stepper-font-desktop);
+ display: block;
+ margin: var(--gcds-stepper-margin-desktop);
+
+ @media only screen and (width < 48em) {
+ font: var(--gcds-stepper-font-mobile);
+ margin: var(--gcds-stepper-margin-mobile);
+ }
}
}
diff --git a/packages/web/src/components/gcds-stepper/gcds-stepper.tsx b/packages/web/src/components/gcds-stepper/gcds-stepper.tsx
index 41d9da066..2459f7de2 100644
--- a/packages/web/src/components/gcds-stepper/gcds-stepper.tsx
+++ b/packages/web/src/components/gcds-stepper/gcds-stepper.tsx
@@ -1,5 +1,5 @@
-import { Component, Element, Host, Prop, State, h } from '@stencil/core';
-import { assignLanguage, observerConfig } from '../../utils/utils';
+import { Component, Element, Host, Prop, State, Watch, h } from '@stencil/core';
+import { assignLanguage, observerConfig, logError } from '../../utils/utils';
import i18n from './i18n/i18n';
@Component({
@@ -17,12 +17,47 @@ export class GcdsStepper {
/**
* Defines the current step.
*/
- @Prop() currentStep!: number;
+ @Prop({ mutable: true }) currentStep!: number;
+ @Watch('currentStep')
+ validateCurrentStep() {
+ if (
+ this.currentStep <= 0 ||
+ isNaN(this.currentStep) ||
+ this.currentStep > this.totalSteps
+ ) {
+ this.errors.push('currentStep');
+ } else if (this.errors.includes('currentStep')) {
+ this.errors.splice(this.errors.indexOf('currentStep'), 1);
+ }
+ }
/**
* Defines the total amount of steps.
*/
- @Prop() totalSteps!: number;
+ @Prop({ mutable: true }) totalSteps!: number;
+ @Watch('totalSteps')
+ validateTotalSteps() {
+ if (
+ this.totalSteps <= 0 ||
+ isNaN(this.totalSteps) ||
+ this.totalSteps < this.currentStep
+ ) {
+ this.errors.push('totalSteps');
+ } else if (this.errors.includes('totalSteps')) {
+ this.errors.splice(this.errors.indexOf('totalSteps'), 1);
+ }
+ }
+
+ /**
+ * Defines the heading tag to render
+ */
+ @Prop() tag: 'h1' | 'h2' | 'h3' = 'h2';
+
+ /**
+ * State to track validation on properties
+ * Contains a list of properties that have an error associated with them
+ */
+ @State() errors: Array = [];
/**
* Language of rendered component
@@ -41,26 +76,63 @@ export class GcdsStepper {
observer.observe(this.el, observerConfig);
}
+ private validateChildren() {
+ if (this.el.innerHTML.trim() == '') {
+ this.errors.push('children');
+ } else if (this.errors.includes('children')) {
+ this.errors.splice(this.errors.indexOf('children'), 1);
+ }
+ }
+
+ private validateRequiredProps() {
+ this.validateCurrentStep();
+ this.validateTotalSteps();
+ this.validateChildren();
+
+ if (
+ this.errors.includes('totalSteps') ||
+ this.errors.includes('currentStep') ||
+ this.errors.includes('children')
+ ) {
+ return false;
+ }
+ return true;
+ }
+
async componentWillLoad() {
// Define lang attribute
this.lang = assignLanguage(this.el);
this.updateLang();
+
+ let valid = this.validateRequiredProps();
+
+ if (!valid) {
+ logError('gcds-stepper', this.errors);
+ }
}
render() {
- const { currentStep, lang, totalSteps } = this;
+ const { currentStep, lang, totalSteps, tag } = this;
return (
-
- {`${i18n[lang].step} ${currentStep} ${i18n[lang].of} ${totalSteps}`}
-
+ {this.validateRequiredProps() && (
+
+
+ {`${i18n[lang].step} ${currentStep} ${i18n[lang].of} ${totalSteps}`}
+
+ {/* Hidden colon to provide pause between caption and heading text for AT */}
+ :
+
+
+
+ )}
);
}
diff --git a/packages/web/src/components/gcds-stepper/readme.md b/packages/web/src/components/gcds-stepper/readme.md
index cc100ba39..9d8b7bfa0 100644
--- a/packages/web/src/components/gcds-stepper/readme.md
+++ b/packages/web/src/components/gcds-stepper/readme.md
@@ -7,10 +7,11 @@
## Properties
-| Property | Attribute | Description | Type | Default |
-| -------------------------- | -------------- | ---------------------------------- | -------- | ----------- |
-| `currentStep` _(required)_ | `current-step` | Defines the current step. | `number` | `undefined` |
-| `totalSteps` _(required)_ | `total-steps` | Defines the total amount of steps. | `number` | `undefined` |
+| Property | Attribute | Description | Type | Default |
+| -------------------------- | -------------- | ---------------------------------- | ---------------------- | ----------- |
+| `currentStep` _(required)_ | `current-step` | Defines the current step. | `number` | `undefined` |
+| `tag` | `tag` | Defines the heading tag to render | `"h1" \| "h2" \| "h3"` | `'h2'` |
+| `totalSteps` _(required)_ | `total-steps` | Defines the total amount of steps. | `number` | `undefined` |
## Dependencies
@@ -18,11 +19,13 @@
### Depends on
- [gcds-heading](../gcds-heading)
+- [gcds-sr-only](../gcds-sr-only)
### Graph
```mermaid
graph TD;
gcds-stepper --> gcds-heading
+ gcds-stepper --> gcds-sr-only
style gcds-stepper fill:#f9f,stroke:#333,stroke-width:4px
```
diff --git a/packages/web/src/components/gcds-stepper/stories/gcds-stepper.stories.tsx b/packages/web/src/components/gcds-stepper/stories/gcds-stepper.stories.tsx
index 9bffc711f..ac844ae79 100644
--- a/packages/web/src/components/gcds-stepper/stories/gcds-stepper.stories.tsx
+++ b/packages/web/src/components/gcds-stepper/stories/gcds-stepper.stories.tsx
@@ -27,6 +27,29 @@ export default {
required: true,
},
},
+ tag: {
+ control: 'select',
+ options: ['h1', 'h2', 'h3'],
+ table: {
+ type: { summary: 'string' },
+ defaultValue: { summary: 'h2' },
+ },
+ },
+
+ // Slots
+ default: {
+ control: {
+ type: 'text',
+ },
+ description:
+ 'Customize the content or include additional elements. | Personnalisez le contenu ou ajoutez des éléments supplémentaires.',
+ table: {
+ category: 'Slots | Fentes',
+ },
+ type: {
+ required: true,
+ },
+ },
...langProp,
},
};
@@ -36,13 +59,15 @@ const Template = args =>
+ }" tag="${args.tag}" ${args.lang != 'en' ? `lang="${args.lang}"` : null}>
+ ${args.default}
+ }" tag="${args.tag}" ${args.lang != 'en' ? `lang="${args.lang}"` : null}>
+ ${args.default}
`.replace(/ null/g, '');
@@ -50,8 +75,9 @@ const TemplatePlayground = args => `
+ tag="${args.tag}"
+ ${args.lang != 'en' ? `lang="${args.lang}"` : null}>
+ ${args.default}
`;
@@ -62,6 +88,41 @@ Default.args = {
lang: 'en',
currentStep: 1,
totalSteps: 4,
+ tag: 'h2',
+ default: 'Section title',
+};
+
+// ------ Stepper tag: H1 ------
+
+export const tagH1 = Template.bind({});
+tagH1.args = {
+ lang: 'en',
+ currentStep: 1,
+ totalSteps: 4,
+ tag: 'h1',
+ default: 'Section title',
+};
+
+// ------ Stepper tag: H2 ------
+
+export const tagH2 = Template.bind({});
+tagH2.args = {
+ lang: 'en',
+ currentStep: 1,
+ totalSteps: 4,
+ tag: 'h2',
+ default: 'Section title',
+};
+
+// ------ Stepper tag: H3 ------
+
+export const tagH3 = Template.bind({});
+tagH3.args = {
+ lang: 'en',
+ currentStep: 1,
+ totalSteps: 4,
+ tag: 'h3',
+ default: 'Section title',
};
// ------ Stepper french ------
@@ -71,6 +132,8 @@ French.args = {
lang: 'fr',
currentStep: 1,
totalSteps: 4,
+ tag: 'h2',
+ default: 'Section title',
};
// ------ Stepper events & props ------
@@ -80,6 +143,8 @@ Props.args = {
lang: 'en',
currentStep: 1,
totalSteps: 4,
+ tag: 'h2',
+ default: 'Section title',
};
// ------ Stepper playground ------
@@ -89,4 +154,6 @@ Playground.args = {
lang: 'en',
currentStep: 1,
totalSteps: 4,
+ tag: 'h2',
+ default: 'Section title',
};
diff --git a/packages/web/src/components/gcds-stepper/stories/overview.mdx b/packages/web/src/components/gcds-stepper/stories/overview.mdx
index 2f977ff89..76de427c2 100644
--- a/packages/web/src/components/gcds-stepper/stories/overview.mdx
+++ b/packages/web/src/components/gcds-stepper/stories/overview.mdx
@@ -15,6 +15,20 @@ A stepper is a progress tracker for a multi-step process.
+### Tag
+
+#### Heading 1
+
+
+
+#### Heading 2
+
+
+
+#### Heading 3
+
+
+
### Language
#### English
diff --git a/packages/web/src/components/gcds-stepper/test/gcds-stepper.e2e.ts b/packages/web/src/components/gcds-stepper/test/gcds-stepper.e2e.ts
index 364464c7d..21608e312 100644
--- a/packages/web/src/components/gcds-stepper/test/gcds-stepper.e2e.ts
+++ b/packages/web/src/components/gcds-stepper/test/gcds-stepper.e2e.ts
@@ -4,7 +4,7 @@ const { AxePuppeteer } = require('@axe-core/puppeteer');
describe('gcds-stepper', () => {
it('renders', async () => {
const page = await newE2EPage();
- await page.setContent('');
+ await page.setContent('Section title');
const element = await page.find('gcds-stepper');
expect(element).toHaveClass('hydrated');
@@ -23,11 +23,7 @@ describe('gcds-stepper a11y tests', () => {
it('colour contrast', async () => {
const page = await newE2EPage();
await page.setContent(`
-
-
-
+ Section title
`);
const colorContrastTest = new AxePuppeteer(page)
@@ -35,6 +31,22 @@ describe('gcds-stepper a11y tests', () => {
.analyze();
const results = await colorContrastTest;
+ expect(results.violations.length).toBe(0);
+ });
+ /**
+ * Discernable text in heading
+ */
+ it('heading text', async () => {
+ const page = await newE2EPage();
+ await page.setContent(`
+ Section title
+ `);
+
+ const emptyHeadingtest = new AxePuppeteer(page)
+ .withRules('empty-heading')
+ .analyze();
+ const results = await emptyHeadingtest;
+
expect(results.violations.length).toBe(0);
});
});
diff --git a/packages/web/src/components/gcds-stepper/test/gcds-stepper.spec.tsx b/packages/web/src/components/gcds-stepper/test/gcds-stepper.spec.tsx
index 8cfb5b7f3..04a278f65 100644
--- a/packages/web/src/components/gcds-stepper/test/gcds-stepper.spec.tsx
+++ b/packages/web/src/components/gcds-stepper/test/gcds-stepper.spec.tsx
@@ -5,47 +5,217 @@ describe('gcds-stepper', () => {
it('renders', async () => {
const page = await newSpecPage({
components: [GcdsStepper],
- html: ``,
+ html: `Section title`,
});
expect(page.root).toEqualHtml(`
- Step 2 of 6
+
+
+ Step 2 of 6
+ :
+
+
+ Section title
`);
});
- /**
- * Renders current + total steps
- */
- it('renders steps', async () => {
+ it('renders - current and total steps', async () => {
const page = await newSpecPage({
components: [GcdsStepper],
- html: ``,
+ html: `Section title`,
});
expect(page.root).toEqualHtml(`
- Step 2 of 6
+
+
+ Step 2 of 6
+ :
+
+
+
+ Section title
+
+ `);
+ });
+
+ it('renders - tag h1', async () => {
+ const page = await newSpecPage({
+ components: [GcdsStepper],
+ html: `Section title`,
+ });
+ expect(page.root).toEqualHtml(`
+
+
+
+
+ Step 2 of 6
+ :
+
+
+
+ Section title
+
+ `);
+ });
+
+ it('renders - tag h2', async () => {
+ const page = await newSpecPage({
+ components: [GcdsStepper],
+ html: `Section title`,
+ });
+ expect(page.root).toEqualHtml(`
+
+
+
+
+ Step 2 of 6
+ :
+
+
+
+ Section title
+
+ `);
+ });
+
+ it('renders - tag h3', async () => {
+ const page = await newSpecPage({
+ components: [GcdsStepper],
+ html: `Section title`,
+ });
+ expect(page.root).toEqualHtml(`
+
+
+
+
+ Step 2 of 6
+ :
+
+
+ Section title
`);
});
- /**
- * Renders stepper in french
- */
- it('renders stepper in french', async () => {
+ it('renders - French', async () => {
const page = await newSpecPage({
components: [GcdsStepper],
- html: ``,
+ html: `Section title`,
});
expect(page.root).toEqualHtml(`
- Étape 2 sur 6
+
+
+ Étape 2 sur 6
+ :
+
+
+
+ Section title
+
+ `);
+ });
+
+ it('renders - HTML children', async () => {
+ const page = await newSpecPage({
+ components: [GcdsStepper],
+ html: `
+ Section title
+ `,
+ });
+ expect(page.root).toEqualHtml(`
+
+
+
+
+ Step 2 of 6
+ :
+
+
+
+ Section title
+
+ `);
+ });
+
+ it('does not render - no children', async () => {
+ const page = await newSpecPage({
+ components: [GcdsStepper],
+ html: ``,
+ });
+ expect(page.root).toEqualHtml(`
+
+
+
+
+ `);
+ });
+
+ it('does not render - no required attributes', async () => {
+ const page = await newSpecPage({
+ components: [GcdsStepper],
+ html: `Section title`,
+ });
+ expect(page.root).toEqualHtml(`
+
+
+
+ Section title
+
+ `);
+ });
+
+ it('does not render - higher current step', async () => {
+ const page = await newSpecPage({
+ components: [GcdsStepper],
+ html: `Section title`,
+ });
+ expect(page.root).toEqualHtml(`
+
+
+
+ Section title
+
+ `);
+ });
+
+ it('does not render - negative current step', async () => {
+ const page = await newSpecPage({
+ components: [GcdsStepper],
+ html: `Section title`,
+ });
+ expect(page.root).toEqualHtml(`
+
+
+
+ Section title
+
+ `);
+ });
+
+ it('does not render - NaN total steps', async () => {
+ const page = await newSpecPage({
+ components: [GcdsStepper],
+ html: `Section title`,
+ });
+ expect(page.root).toEqualHtml(`
+
+
+ Section title
`);
});
diff --git a/packages/web/src/index.html b/packages/web/src/index.html
index ea3ff477d..a5029e978 100644
--- a/packages/web/src/index.html
+++ b/packages/web/src/index.html
@@ -250,7 +250,11 @@
Form elements (including stepper and error summary)
-
+
+
+ Section title
+
+