Skip to content

Commit aa2e771

Browse files
author
tong.hau
committed
feat(cards): add footer slot and deprecate link slot
1 parent a38def1 commit aa2e771

File tree

11 files changed

+167
-28
lines changed

11 files changed

+167
-28
lines changed

docs/INSTALLATION.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Install SGDS web components locally with the following command
88

99
```js
1010

11-
npm install @govtechsg/sgds-web-component@3.3.2-rc.0
11+
npm install @govtechsg/sgds-web-component@3.3.2
1212

1313
```
1414

@@ -65,14 +65,14 @@ This method registers all SGDS elements up front in the Custom Elements Registry
6565
6666
```js
6767
// Load global css file
68-
<link href='https://cdn.jsdelivr.net/npm/@govtechsg/sgds-web-component@3.3.2-rc.0/themes/day.css' rel='stylesheet' type='text/css' />
69-
<link href='https://cdn.jsdelivr.net/npm/@govtechsg/sgds-web-component@3.3.2-rc.0/css/sgds.css' rel='stylesheet' type='text/css' />
68+
<link href='https://cdn.jsdelivr.net/npm/@govtechsg/sgds-web-component@3.3.2/themes/day.css' rel='stylesheet' type='text/css' />
69+
<link href='https://cdn.jsdelivr.net/npm/@govtechsg/sgds-web-component@3.3.2/css/sgds.css' rel='stylesheet' type='text/css' />
7070

7171
// it is recommended to load a particular version when using cdn e.g. https://cdn.jsdelivr.net/npm/@govtechsg/sgds-web-component@1.0.2
72-
<script src="https://cdn.jsdelivr.net/npm/@govtechsg/sgds-web-component@3.3.2-rc.0" async></script>
72+
<script src="https://cdn.jsdelivr.net/npm/@govtechsg/sgds-web-component@3.3.2" async></script>
7373

7474
//or load a single component e.g. Masthead
75-
<script src="https://cdn.jsdelivr.net/npm/@govtechsg/sgds-web-component@3.3.2-rc.0/components/Masthead/index.umd.js" async></script>
75+
<script src="https://cdn.jsdelivr.net/npm/@govtechsg/sgds-web-component@3.3.2/components/Masthead/index.umd.js" async></script>
7676

7777
```
7878

src/base/card-element.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ export class CardElement extends SgdsElement {
1515
/** @internal */
1616
@query("a.card") card: HTMLAnchorElement;
1717

18-
/** Extends the link passed in slot[name="link"] to the entire card */
18+
/** Extends the link passed in the `link` slot to the entire card. Prefer using the `footer` slot for links going forward. The `link` slot is still supported for backward compatibility, but is **deprecated** and will be removed in a future release.
19+
*/
1920
@property({ type: Boolean, reflect: true }) stretchedLink = false;
2021

2122
/** Disables the card */

src/base/card.css

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
cursor: not-allowed;
33
}
44

5-
:host([stretchedLink]) slot[name="link"] {
5+
:host([stretchedLink]) slot[name="footer"]::slotted(sgds-link),
6+
:host([stretchedLink]) slot[name="footer"]::slotted(a),
7+
:host([stretchedLink]) slot[name="link"]::slotted(sgds-link),
8+
:host([stretchedLink]) slot[name="link"]::slotted(a) {
69
display: none;
710
}
811

@@ -212,12 +215,14 @@ slot[name="image"]::slotted(img) {
212215
border-top-right-radius: calc(var(--sgds-border-radius-md) - var(--sgds-border-width-1));
213216
}
214217

218+
slot[name="footer"]::slotted(*),
215219
slot[name="link"]::slotted(*) {
216220
margin-top: auto;
217221
padding-top: var(--sgds-padding-sm);
218-
font-weight: 700;
222+
width: fit-content;
219223
}
220224

225+
slot[name="footer"]::slotted(a),
221226
slot[name="link"]::slotted(a) {
222227
display: inline-flex;
223228
gap: var(--sgds-gap-2-xs);
@@ -228,17 +233,21 @@ slot[name="link"]::slotted(a) {
228233

229234
slot[name="title"]::slotted(a:hover),
230235
slot[name="title"]::slotted(a:focus),
236+
slot[name="footer"]::slotted(a:hover),
237+
slot[name="footer"]::slotted(a:focus),
231238
slot[name="link"]::slotted(a:hover),
232239
slot[name="link"]::slotted(a:focus) {
233240
color: var(--sgds-link-color-emphasis);
234241
}
235242

236243
slot[name="title"]::slotted(a:focus),
244+
slot[name="footer"]::slotted(a:focus),
237245
slot[name="link"]::slotted(a:focus) {
238246
outline: 0;
239247
}
240248

241249
slot[name="title"]::slotted(a:focus-visible),
250+
slot[name="footer"]::slotted(a:focus-visible),
242251
slot[name="link"]::slotted(a:focus-visible) {
243252
box-shadow: var(--sgds-box-shadow-focus);
244253
}

src/components/Card/sgds-card.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,15 @@ import cardStyle from "./card.css";
1919
* @slot title - The title of the card
2020
* @slot description - The paragrapher text of the card
2121
* @slot lower - Accepts any additional content to be displayed below the card description, such as badges, metadata, or supplementary information.
22-
* @slot link - Accepts an anchor element. Only a single element is allowed to be passed in.
22+
* @slot footer - Footer area of the card. Accepts links, actions, or any custom content.
23+
* @slot link - (@deprecated) Deprecated since 3.3.2 in favour of `footer` slot.
24+
* Legacy slot for anchor elements. Use `footer` instead.
2325
*/
2426
export class SgdsCard extends CardElement {
2527
static styles = [...CardElement.styles, cardStyle];
2628

29+
@queryAssignedElements({ slot: "footer" })
30+
private footerNode!: HTMLElement[];
2731
@queryAssignedElements({ slot: "link" })
2832
private linkNode!: HTMLAnchorElement[] | SgdsLink[];
2933

@@ -33,16 +37,30 @@ export class SgdsCard extends CardElement {
3337
/** Controls how the image is sized and aligned within the card. Available options: `default`, `padding around`, `aspect ratio` */
3438
@property({ type: String, reflect: true }) imageAdjustment: CardImageAdjustment = "default";
3539

36-
private get linkSlotItems(): HTMLAnchorElement {
40+
private get linkSlotItems(): HTMLAnchorElement | null {
41+
if (!this.linkNode || this.linkNode.length === 0) return null;
3742
const element = this.linkNode[0] as HTMLElement;
3843
return (element.querySelector("a") || element) as HTMLAnchorElement;
3944
}
4045

46+
private get footerSlotItems(): HTMLAnchorElement | null {
47+
if (!this.footerNode || this.footerNode.length === 0) return null;
48+
const element = this.footerNode[0] as HTMLElement;
49+
return (element.querySelector("a") || element) as HTMLAnchorElement;
50+
}
51+
4152
private readonly hasSlotController = new HasSlotController(this, "image", "icon", "menu");
4253

4354
protected firstUpdated() {
4455
if (this.stretchedLink) {
45-
this.card.setAttribute("href", this.linkSlotItems.href);
56+
const footerHref = this.footerSlotItems?.href;
57+
const linkHref = this.linkSlotItems?.href;
58+
59+
if (footerHref) {
60+
this.card.setAttribute("href", footerHref);
61+
} else if (linkHref) {
62+
this.card.setAttribute("href", linkHref);
63+
}
4664
}
4765
}
4866

@@ -97,7 +115,9 @@ export class SgdsCard extends CardElement {
97115
</div>
98116
<slot name="description"></slot>
99117
<slot name="lower"></slot>
100-
<slot name="link" @slotchange=${this.handleLinkSlotChange}></slot>
118+
<slot name="footer">
119+
<slot name="link" @slotchange=${this.handleLinkSlotChange}></slot>
120+
</slot>
101121
</div>
102122
</${tag}>
103123
`;

src/components/IconCard/sgds-icon-card.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ import IconCardStyle from "./icon-card.css";
1515
* @slot title - The title of the card
1616
* @slot description - The paragrapher text of the card
1717
* @slot lower - Accepts any additional content to be displayed below the card description, such as badges, metadata, or supplementary information.
18-
* @slot link - Accepts an anchor element. Only a single element is allowed to be passed in.
18+
* @slot footer - Footer area of the card. Accepts links, actions, or any custom content.
19+
* @slot link - (@deprecated) Deprecated since 3.3.2 in favour of `footer` slot.
20+
* Legacy slot for anchor elements. Use `footer` instead.
1921
*/
2022
export class SgdsIconCard extends CardElement {
2123
static styles = [...CardElement.styles, IconCardStyle];
@@ -26,17 +28,26 @@ export class SgdsIconCard extends CardElement {
2628
/** @internal */
2729
@queryAssignedNodes({ slot: "upper", flatten: true })
2830
_upperNode!: Array<Node>;
31+
@queryAssignedElements({ slot: "footer" })
32+
private footerNode!: HTMLElement[];
2933
@queryAssignedElements({ slot: "link" })
3034
private linkNode!: HTMLAnchorElement[] | SgdsLink[];
3135

3236
/** Removes the card's internal padding when set to true. */
3337
@property({ type: Boolean, reflect: true }) noPadding = false;
3438

35-
private get linkSlotItems(): HTMLAnchorElement {
39+
private get linkSlotItems(): HTMLAnchorElement | null {
40+
if (!this.linkNode || this.linkNode.length === 0) return null;
3641
const element = this.linkNode[0] as HTMLElement;
3742
return (element.querySelector("a") || element) as HTMLAnchorElement;
3843
}
3944

45+
private get footerSlotItems(): HTMLAnchorElement | null {
46+
if (!this.footerNode || this.footerNode.length === 0) return null;
47+
const element = this.footerNode[0] as HTMLElement;
48+
return (element.querySelector("a") || element) as HTMLAnchorElement;
49+
}
50+
4051
protected firstUpdated() {
4152
if (this._iconNode.length === 0) {
4253
if ((this.orientation === "vertical" && this._upperNode.length === 0) || this.orientation === "horizontal") {
@@ -49,7 +60,14 @@ export class SgdsIconCard extends CardElement {
4960
}
5061

5162
if (this.stretchedLink) {
52-
this.card.setAttribute("href", this.linkSlotItems.href);
63+
const footerHref = this.footerSlotItems?.href;
64+
const linkHref = this.linkSlotItems?.href;
65+
66+
if (footerHref) {
67+
this.card.setAttribute("href", footerHref);
68+
} else if (linkHref) {
69+
this.card.setAttribute("href", linkHref);
70+
}
5371
}
5472
}
5573

@@ -81,7 +99,9 @@ export class SgdsIconCard extends CardElement {
8199
</div>
82100
<slot name="description"></slot>
83101
<slot name="lower"></slot>
84-
<slot name="link" @slotchange=${this.handleLinkSlotChange}></slot>
102+
<slot name="footer">
103+
<slot name="link" @slotchange=${this.handleLinkSlotChange}></slot>
104+
</slot>
85105
</div>
86106
</${tag}>
87107
`;

src/components/ImageCard/sgds-image-card.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,18 @@ import imageCardStyle from "./image-card.css";
1818
* @slot title - The title of the card
1919
* @slot description - The paragrapher text of the card
2020
* @slot lower - Accepts any additional content to be displayed below the card description, such as badges, metadata, or supplementary information.
21-
* @slot link - Accepts an anchor element. Only a single element is allowed to be passed in.
21+
* @slot footer - Footer area of the card. Accepts links, actions, or any custom content.
22+
* @slot link - (@deprecated) Deprecated since 3.3.2 in favour of `footer` slot.
23+
* Legacy slot for anchor elements. Use `footer` instead.
2224
*/
2325
export class SgdsImageCard extends CardElement {
2426
static styles = [...CardElement.styles, imageCardStyle];
2527

2628
/** @internal */
2729
@queryAssignedNodes({ slot: "image", flatten: true })
2830
_imageNode!: Array<Node>;
31+
@queryAssignedElements({ slot: "footer" })
32+
private footerNode!: HTMLElement[];
2933
@queryAssignedElements({ slot: "link" })
3034
private linkNode!: HTMLAnchorElement[] | SgdsLink[];
3135

@@ -38,11 +42,18 @@ export class SgdsImageCard extends CardElement {
3842
/** Controls how the image is sized and aligned within the card. Available options: `default`, `padding around`, `aspect ratio` */
3943
@property({ type: String, reflect: true }) imageAdjustment: CardImageAdjustment = "default";
4044

41-
private get linkSlotItems(): HTMLAnchorElement {
45+
private get linkSlotItems(): HTMLAnchorElement | null {
46+
if (!this.linkNode || this.linkNode.length === 0) return null;
4247
const element = this.linkNode[0] as HTMLElement;
4348
return (element.querySelector("a") || element) as HTMLAnchorElement;
4449
}
4550

51+
private get footerSlotItems(): HTMLAnchorElement | null {
52+
if (!this.footerNode || this.footerNode.length === 0) return null;
53+
const element = this.footerNode[0] as HTMLElement;
54+
return (element.querySelector("a") || element) as HTMLAnchorElement;
55+
}
56+
4657
protected firstUpdated() {
4758
if (this._imageNode.length === 0) {
4859
const image = this.shadowRoot.querySelector(".card-image") as HTMLDivElement;
@@ -52,7 +63,14 @@ export class SgdsImageCard extends CardElement {
5263
}
5364

5465
if (this.stretchedLink) {
55-
this.card.setAttribute("href", this.linkSlotItems.href);
66+
const footerHref = this.footerSlotItems?.href;
67+
const linkHref = this.linkSlotItems?.href;
68+
69+
if (footerHref) {
70+
this.card.setAttribute("href", footerHref);
71+
} else if (linkHref) {
72+
this.card.setAttribute("href", linkHref);
73+
}
5674
}
5775
}
5876

@@ -92,7 +110,9 @@ export class SgdsImageCard extends CardElement {
92110
</div>
93111
<slot name="description"></slot>
94112
<slot name="lower"></slot>
95-
<slot name="link" @slotchange=${this.handleLinkSlotChange}></slot>
113+
<slot name="footer">
114+
<slot name="link" @slotchange=${this.handleLinkSlotChange}></slot>
115+
</slot>
96116
</div>
97117
</${tag}>
98118
`;

src/components/ThumbnailCard/sgds-thumbnail-card.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ import thumbnailCardStyle from "./thumbnail-card.css";
1515
* @slot title - The title of the card
1616
* @slot description - The paragrapher text of the card
1717
* @slot lower - Accepts any additional content to be displayed below the card description, such as badges, metadata, or supplementary information.
18-
* @slot link - Accepts an anchor element. Only a single element is allowed to be passed in.
18+
* @slot footer - Footer area of the card. Accepts links, actions, or any custom content.
19+
* @slot link - (@deprecated) Deprecated since 3.3.2 in favour of `footer` slot.
20+
* Legacy slot for anchor elements. Use `footer` instead.
1921
*/
2022
export class SgdsThumbnailCard extends CardElement {
2123
static styles = [...CardElement.styles, thumbnailCardStyle];
@@ -26,17 +28,26 @@ export class SgdsThumbnailCard extends CardElement {
2628
/** @internal */
2729
@queryAssignedNodes({ slot: "upper", flatten: true })
2830
_upperNode!: Array<Node>;
31+
@queryAssignedElements({ slot: "footer" })
32+
private footerNode!: HTMLElement[];
2933
@queryAssignedElements({ slot: "link" })
3034
private linkNode!: HTMLAnchorElement[] | SgdsLink[];
3135

3236
/** Removes the card's internal padding when set to true. */
3337
@property({ type: Boolean, reflect: true }) noPadding = false;
3438

35-
private get linkSlotItems(): HTMLAnchorElement {
39+
private get linkSlotItems(): HTMLAnchorElement | null {
40+
if (!this.linkNode || this.linkNode.length === 0) return null;
3641
const element = this.linkNode[0] as HTMLElement;
3742
return (element.querySelector("a") || element) as HTMLAnchorElement;
3843
}
3944

45+
private get footerSlotItems(): HTMLAnchorElement | null {
46+
if (!this.footerNode || this.footerNode.length === 0) return null;
47+
const element = this.footerNode[0] as HTMLElement;
48+
return (element.querySelector("a") || element) as HTMLAnchorElement;
49+
}
50+
4051
protected firstUpdated() {
4152
if (this._thumbnailNode.length === 0) {
4253
if ((this.orientation === "vertical" && this._upperNode.length === 0) || this.orientation === "horizontal") {
@@ -49,7 +60,14 @@ export class SgdsThumbnailCard extends CardElement {
4960
}
5061

5162
if (this.stretchedLink) {
52-
this.card.setAttribute("href", this.linkSlotItems.href);
63+
const footerHref = this.footerSlotItems?.href;
64+
const linkHref = this.linkSlotItems?.href;
65+
66+
if (footerHref) {
67+
this.card.setAttribute("href", footerHref);
68+
} else if (linkHref) {
69+
this.card.setAttribute("href", linkHref);
70+
}
5371
}
5472
}
5573

@@ -80,7 +98,9 @@ export class SgdsThumbnailCard extends CardElement {
8098
</div>
8199
<slot name="description"></slot>
82100
<slot name="lower"></slot>
83-
<slot name="link" @slotchange=${this.handleLinkSlotChange}></slot>
101+
<slot name="footer">
102+
<slot name="link" @slotchange=${this.handleLinkSlotChange}></slot>
103+
</slot>
84104
</div>
85105
</${tag}>
86106
`;

0 commit comments

Comments
 (0)