Skip to content

Commit 2cd7b9a

Browse files
Card - Add tag argument (HDS-4688) (#2787)
Co-authored-by: Dylan Hyun <dylan.hyun@hashicorp.com>
1 parent 5c62ba1 commit 2cd7b9a

File tree

7 files changed

+110
-47
lines changed

7 files changed

+110
-47
lines changed

Diff for: .changeset/wise-stingrays-heal.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hashicorp/design-system-components": minor
3+
---
4+
5+
`Card` - Add `tag` argument to choose between using a `div` tag (the default) or an `li` tag

Diff for: packages/components/src/components/hds/card/container.hbs

+11-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
Copyright (c) HashiCorp, Inc.
33
SPDX-License-Identifier: MPL-2.0
44
}}
5-
<div class={{this.classNames}} ...attributes>
6-
{{yield}}
7-
</div>
5+
{{!
6+
Dynamically generating an HTML tag in Ember creates a dynamic component class (with the corresponding tagName), while rendering
7+
a plain HTML element requires less computing cycles for Ember (you will notice it doesn't add the `ember-view` class to it).
8+
}}
9+
{{#if (eq this.componentTag "div")}}
10+
<div class={{this.classNames}} ...attributes>{{yield}}</div>
11+
{{else}}
12+
{{#let (element this.componentTag) as |Tag|}}
13+
<Tag class={{this.classNames}} role={{if (eq this.componentTag "li") "listitem"}} ...attributes>{{yield}}</Tag>
14+
{{/let}}
15+
{{/if}}

Diff for: packages/components/src/components/hds/card/container.ts

+25-44
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,33 @@
55

66
import Component from '@glimmer/component';
77
import { assert } from '@ember/debug';
8+
89
import {
910
HdsCardBackgroundValues,
1011
HdsCardLevelValues,
1112
HdsCardOverflowValues,
13+
HdsCardTagValues,
1214
} from './types.ts';
15+
1316
import type {
1417
HdsCardBackground,
1518
HdsCardLevel,
1619
HdsCardOverflow,
20+
HdsCardTag,
1721
} from './types.ts';
1822

1923
export const DEFAULT_LEVEL = HdsCardLevelValues.Base;
2024
export const DEFAULT_BACKGROUND = HdsCardBackgroundValues.NeutralPrimary;
2125
export const DEFAULT_OVERFLOW = HdsCardOverflowValues.Visible;
26+
export const DEFAULT_TAG = HdsCardTagValues.Div;
2227
export const AVAILABLE_LEVELS: string[] = Object.values(HdsCardLevelValues);
2328
export const AVAILABLE_BACKGROUNDS: string[] = Object.values(
2429
HdsCardBackgroundValues
2530
);
2631
export const AVAILABLE_OVERFLOWS: string[] = Object.values(
2732
HdsCardOverflowValues
2833
);
34+
export const AVAILABLE_TAGS: string[] = Object.values(HdsCardTagValues);
2935

3036
export interface HdsCardContainerSignature {
3137
Args: {
@@ -35,22 +41,16 @@ export interface HdsCardContainerSignature {
3541
background?: HdsCardBackground;
3642
hasBorder?: boolean;
3743
overflow?: HdsCardOverflow;
44+
tag?: HdsCardTag;
3845
};
3946
Blocks: {
4047
default: [];
4148
};
42-
Element: HTMLDivElement;
49+
Element: HTMLElement;
4350
}
4451

4552
export default class HdsCardContainer extends Component<HdsCardContainerSignature> {
46-
/**
47-
* Sets the "elevation" level for the component
48-
* Accepted values: base, mid, high
49-
*
50-
* @param level
51-
* @type {HdsCardLevel}
52-
* @default 'base'
53-
*/
53+
// Sets the "elevation" level for the component
5454
get level(): HdsCardLevel {
5555
const { level = DEFAULT_LEVEL } = this.args;
5656

@@ -64,13 +64,7 @@ export default class HdsCardContainer extends Component<HdsCardContainerSignatur
6464
return level;
6565
}
6666

67-
/**
68-
* Sets the "elevation" level for the component on ":hover" state
69-
* Accepted values: base, mid, high
70-
*
71-
* @param levelHover
72-
* @type {HdsCardLevel}
73-
*/
67+
// Sets the "elevation" level for the component on ":hover" state
7468
get levelHover(): HdsCardLevel | undefined {
7569
const { levelHover } = this.args;
7670

@@ -86,13 +80,7 @@ export default class HdsCardContainer extends Component<HdsCardContainerSignatur
8680
return levelHover;
8781
}
8882

89-
/**
90-
* Sets the "elevation" level for the component on ":active" state
91-
* Accepted values: base, mid, high
92-
*
93-
* @param levelActive
94-
* @type {HdsCardLevel}
95-
*/
83+
// Sets the "elevation" level for the component on ":active" state
9684
get levelActive(): HdsCardLevel | undefined {
9785
const { levelActive } = this.args;
9886

@@ -108,14 +96,7 @@ export default class HdsCardContainer extends Component<HdsCardContainerSignatur
10896
return levelActive;
10997
}
11098

111-
/**
112-
* Sets the background for the component
113-
* Accepted values: neutral-primary, neutral-secondary
114-
*
115-
* @param background
116-
* @type {HdsCardBackground}
117-
* @default 'base'
118-
*/
99+
// Sets the background for the component
119100
get background(): HdsCardBackground {
120101
const { background = DEFAULT_BACKGROUND } = this.args;
121102

@@ -129,14 +110,7 @@ export default class HdsCardContainer extends Component<HdsCardContainerSignatur
129110
return background;
130111
}
131112

132-
/**
133-
* Sets the level for the card
134-
* Accepted values: visible, hidden
135-
*
136-
* @param overflow
137-
* @type {HdsCardOverflow}
138-
* @default 'visible'
139-
*/
113+
// Sets the level for the card
140114
get overflow(): HdsCardOverflow {
141115
const { overflow = DEFAULT_OVERFLOW } = this.args;
142116

@@ -150,11 +124,18 @@ export default class HdsCardContainer extends Component<HdsCardContainerSignatur
150124
return overflow;
151125
}
152126

153-
/**
154-
* Get the class names to apply to the component.
155-
* @method Card#classNames
156-
* @return {string} The "class" attribute to apply to the component.
157-
*/
127+
get componentTag(): HdsCardTag {
128+
const { tag = DEFAULT_TAG } = this.args;
129+
130+
assert(
131+
`@tag for "Hds::Card::Container" must be one of the following: ${AVAILABLE_TAGS.join(', ')}; received: ${tag}`,
132+
AVAILABLE_TAGS.includes(tag)
133+
);
134+
135+
return tag;
136+
}
137+
138+
// Get the class names to apply to the component.
158139
get classNames(): string {
159140
const classes = ['hds-card__container'];
160141

Diff for: packages/components/src/components/hds/card/types.ts

+7
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,10 @@ export enum HdsCardOverflowValues {
3131
export type HdsCardOverflow =
3232
| HdsCardOverflowValues.Hidden
3333
| HdsCardOverflowValues.Visible;
34+
35+
export enum HdsCardTagValues {
36+
Div = 'div',
37+
Li = 'li',
38+
}
39+
40+
export type HdsCardTag = `${HdsCardTagValues}`;

Diff for: showcase/app/styles/showcase-pages/card.scss

+6
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,10 @@ body.components-card {
2525
border-radius: 8px;
2626
}
2727
}
28+
29+
.shw-component-card-list {
30+
margin: 0;
31+
padding: 0;
32+
list-style-type: none;
33+
}
2834
}

Diff for: showcase/app/templates/components/card.hbs

+22
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
{{/each}}
5757
</Shw::Grid>
5858
</div>
59+
5960
<Shw::Text::H2>Background</Shw::Text::H2>
6061

6162
<div class="shw-component-card-wrapper">
@@ -71,6 +72,7 @@
7172
</div>
7273

7374
<Shw::Text::H2>Overflow</Shw::Text::H2>
75+
7476
<div class="shw-component-card-wrapper">
7577
<Shw::Grid @columns={{2}} @gap="2rem" {{style width="fit-content"}} as |SG|>
7678
<SG.Item>
@@ -92,4 +94,24 @@
9294
</Shw::Grid>
9395
</div>
9496

97+
<Shw::Text::H2>Tag</Shw::Text::H2>
98+
99+
<div class="shw-component-card-wrapper">
100+
<Shw::Grid @columns={{2}} @gap="2rem" {{style width="fit-content"}} as |SG|>
101+
<SG.Item @label="Card using default div tag">
102+
<Hds::Card::Container @level="mid" @hasBorder={{true}}>
103+
<Shw::Placeholder @text="div" @width="200" @height="200" @background="transparent" />
104+
</Hds::Card::Container>
105+
</SG.Item>
106+
107+
<SG.Item @label="Card using list item tag">
108+
<ul class="shw-component-card-list">
109+
<Hds::Card::Container @level="mid" @hasBorder={{true}} @tag="li">
110+
<Shw::Placeholder @text="li" @width="200" @height="200" @background="transparent" />
111+
</Hds::Card::Container>
112+
</ul>
113+
</SG.Item>
114+
</Shw::Grid>
115+
</div>
116+
95117
</section>

Diff for: showcase/tests/integration/components/hds/card/container-test.js

+34
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,26 @@ module('Integration | Component | hds/card/container', function (hooks) {
7979
.hasClass('hds-card__container--overflow-hidden');
8080
});
8181

82+
// TAG
83+
84+
test(`it should render a div if no @tag prop is declared and it should not have a role`, async function (assert) {
85+
await render(hbs`<Hds::Card::Container id="test-card-container" />`);
86+
assert
87+
.dom('#test-card-container')
88+
.hasTagName('div')
89+
.doesNotHaveAttribute('role');
90+
});
91+
92+
test(`it should render an li if specified in the @tag prop and it should have the correct role`, async function (assert) {
93+
await render(
94+
hbs`<Hds::Card::Container id="test-card-container" @tag="li" />`
95+
);
96+
assert
97+
.dom('#test-card-container')
98+
.hasTagName('li')
99+
.hasAttribute('role', 'listitem');
100+
});
101+
82102
// ASSERTIONS
83103

84104
test('it should throw an assertion if an incorrect value for @level is provided', async function (assert) {
@@ -117,4 +137,18 @@ module('Integration | Component | hds/card/container', function (hooks) {
117137
throw new Error(errorMessage);
118138
});
119139
});
140+
141+
// If a tag other than div or li is passed, it should throw an assertion
142+
test('it should throw an assertion if an incorrect value for @tag is provided', async function (assert) {
143+
const errorMessage =
144+
'@tag for "Hds::Card::Container" must be one of the following: div, li; received: section';
145+
assert.expect(2);
146+
setupOnerror(function (error) {
147+
assert.strictEqual(error.message, `Assertion Failed: ${errorMessage}`);
148+
});
149+
await render(hbs`<Hds::Card::Container @tag="section" />`);
150+
assert.throws(function () {
151+
throw new Error(errorMessage);
152+
});
153+
});
120154
});

0 commit comments

Comments
 (0)