Skip to content

Commit 019a1c9

Browse files
authored
feat(Masthead, Mainnav, SystemBanner): fluid prop to disable max-width [run-chromatic] (#540)
2 parents 8b37219 + cc40a6e commit 019a1c9

23 files changed

+370
-54
lines changed

.github/agents/dp.test-agent-v1.agent.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,37 @@ describe('Calculator', () => {
5656
- Follow existing test file naming: `*.test.ts`, `*.spec.ts`, or `*.test.js`
5757
- Place tests in `tests/`, `__tests__/`, or co-located with source files
5858

59+
**Lit Component Testing (Web Components):**
60+
When testing Lit components, focus on component-specific behavior, not Lit framework internals:
61+
62+
**DO TEST:**
63+
- Component-specific behavior (e.g., how props affect rendered output/CSS)
64+
- User interactions and event handling
65+
- DOM structure and rendered content
66+
- Component methods and custom logic
67+
68+
**DON'T TEST:**
69+
- Lit's reactive property system (property reflection, attribute syncing)
70+
- Property toggling (e.g., `element.prop = true; expect(element.hasAttribute('prop'))`)
71+
- Framework built-in functionality
72+
73+
```typescript
74+
// ✅ GOOD: Test component-specific behavior
75+
it("when fluid is true, .navbar has no max-width constraint", async () => {
76+
const el = await fixture<SgdsMainnav>(html`<sgds-mainnav fluid></sgds-mainnav>`);
77+
const navbar = el.shadowRoot?.querySelector(".navbar") as HTMLElement;
78+
expect(getComputedStyle(navbar).maxWidth).to.equal("none");
79+
});
80+
81+
// ❌ BAD: Testing Lit's reactive properties (out of scope)
82+
it("can toggle fluid property", async () => {
83+
const el = await fixture<SgdsMainnav>(html`<sgds-mainnav></sgds-mainnav>`);
84+
el.fluid = true;
85+
await el.updateComplete;
86+
expect(el.hasAttribute("fluid")).to.be.true; // Testing framework behavior
87+
});
88+
```
89+
5990
### Go (testing package)
6091

6192
Follow table-driven test patterns:

.github/copilot-instructions.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,27 @@ description: 'AI agent guide for contributing to and navigating the sgds-web-com
4141
4. Add tests in `test/`
4242
5. Document in `docs/`
4343

44+
## Storybook Story Conventions
45+
- **Basic stories**: Written in `stories/templates/<ComponentName>/basic.js`
46+
- Exports a `Template` function that can be reused
47+
- Exports `args` and `parameters` for the default story
48+
- **Additional stories**: Written in `stories/templates/<ComponentName>/additional.stories.js` and `additional.mdx`
49+
- **Important**: Files are concatenated by gulp, so `Template` from `basic.js` is available without importing
50+
- For simple prop variations, reuse `Template.bind({})` with different args instead of creating new templates
51+
- Example:
52+
```javascript
53+
// In additional.stories.js - no imports needed
54+
export const Fluid = {
55+
render: Template.bind({}),
56+
name: "Fluid",
57+
args: { fluid: true },
58+
parameters: { layout: "fullscreen" },
59+
tags: []
60+
};
61+
```
62+
- Write documentation in `additional.mdx` and reference stories with `<Canvas of={ComponentStories.StoryName}>`
63+
- **Build process**: `scripts/generateStories.mjs` concatenates templates into `stories/components/`
64+
4465
## References
4566
- [README.md](../README.md): Installation, usage, and framework integration
4667
- [docs/](../docs/): In-depth guides and API docs

playground/SystemBanner.html

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,38 @@
33
<link href="../src/themes/night.css" rel="stylesheet" type="text/css" />
44
<link href="../src/css/sgds.css" rel="stylesheet" type="text/css" />
55

6+
<style>
7+
.icon-color {
8+
color: var(--sgds-warning-color-fixed-light);
9+
}
10+
</style>
11+
<sgds-masthead></sgds-masthead>
12+
13+
<sgds-mainnav>
14+
<div slot="brand">SGDS</div>
15+
<div slot="end">End slot</div>
16+
</sgds-mainnav>
17+
<sgds-system-banner show dismissible>
18+
<sgds-system-banner-item>
19+
<sgds-icon slot="icon" name="exclamation-triangle-fill" size="md" class="icon-color"></sgds-icon>
20+
1 Etiam suscipit nisi eget porta cursus. Ut sit amet felis aliquet, pellentesque mi at, vulputate nunc. Vivamus ac
21+
facilisis tellus. Maecenas ac libero scelerisque tellus maximus accumsan a vehicula arcu. Aenean quis leo gravida,
22+
congue sapien eu, rhoncus Maecenas ac libero scelerisque tellus maximus accumsan a vehicula arcu. Aenean quis leo gravida,
23+
congue sapien eu, rhoncus
24+
<sgds-link size="sm" variant="light" slot="action"
25+
><a href="#">Action link<sgds-icon name="arrow-right" size="md"></sgds-icon></a
26+
></sgds-link>
27+
</sgds-system-banner-item>
28+
</sgds-system-banner>
29+
<sgds-footer></sgds-footer>
30+
631
<h3> long message only</h3>
732
<sgds-system-banner show>
833
<sgds-system-banner-item>
9-
<div>
1034
1 Etiam suscipit nisi eget porta cursus. Ut sit amet felis aliquet, pellentesque mi at, vulputate nunc. Vivamus ac
1135
facilisis tellus. Maecenas ac libero scelerisque tellus maximus accumsan a vehicula arcu. Aenean quis leo gravida,
1236
congue sapien eu, rhoncus Maecenas ac libero scelerisque tellus maximus accumsan a vehicula arcu. Aenean quis leo gravida,
1337
congue sapien eu, rhoncus
14-
</div>
1538
</sgds-system-banner-item>
1639
</sgds-system-banner>
1740
<br/>

playground/header.html

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<script type="module" src="../src/index.ts"></script>
2+
<link href="../src/themes/day.css" rel="stylesheet" type="text/css" />
3+
<link href="../src/themes/night.css" rel="stylesheet" type="text/css" />
4+
<link href="../src/css/sgds.css" rel="stylesheet" type="text/css" />
5+
6+
<style>
7+
body {
8+
margin: 0;
9+
}
10+
.content-placeholder {
11+
padding: 2rem;
12+
text-align: center;
13+
color: #868686;
14+
}
15+
</style>
16+
17+
<h1>Header Components Demo</h1>
18+
19+
<h2>Default</h2>
20+
<sgds-masthead></sgds-masthead>
21+
<sgds-mainnav>
22+
<img width="130" src="https://www.designsystem.tech.gov.sg/assets/img/logo-sgds.svg" slot="brand">
23+
<sgds-mainnav-item active>
24+
<a href="#">Home</a>
25+
</sgds-mainnav-item>
26+
<sgds-mainnav-item><a href="#">About</a></sgds-mainnav-item>
27+
<sgds-mainnav-item><a href="#">Services</a></sgds-mainnav-item>
28+
<sgds-mainnav-dropdown>
29+
<span slot="toggler">More</span>
30+
<sgds-dropdown-item><a href="#">Item 1</a></sgds-dropdown-item>
31+
<sgds-dropdown-item><a href="#">Item 2</a></sgds-dropdown-item>
32+
<sgds-dropdown-item><a href="#">Item 3</a></sgds-dropdown-item>
33+
</sgds-mainnav-dropdown>
34+
<sgds-button slot="end">Login</sgds-button>
35+
</sgds-mainnav>
36+
<sgds-system-banner show dismissible>
37+
<sgds-system-banner-item>
38+
<sgds-icon name="exclamation-circle" slot="icon"></sgds-icon>
39+
This is a system banner message without fluid prop
40+
</sgds-system-banner-item>
41+
<sgds-system-banner-item>
42+
<sgds-icon name="exclamation-circle" slot="icon"></sgds-icon>
43+
This is a system banner message without fluid prop
44+
</sgds-system-banner-item>
45+
<sgds-system-banner-item>
46+
<sgds-icon name="exclamation-circle" slot="icon"></sgds-icon>
47+
This is a system banner message without fluid prop
48+
</sgds-system-banner-item>
49+
</sgds-system-banner>
50+
<sgds-footer></sgds-footer>
51+
52+
<div class="content-placeholder">
53+
Page content goes here. Notice the header components have max-width constraint.
54+
</div>
55+
56+
<h2>With Fluid</h2>
57+
<sgds-masthead fluid></sgds-masthead>
58+
<sgds-mainnav fluid>
59+
<img width="130" src="https://www.designsystem.tech.gov.sg/assets/img/logo-sgds.svg" slot="brand">
60+
<sgds-mainnav-item active>
61+
<a href="#">Home</a>
62+
</sgds-mainnav-item>
63+
<sgds-mainnav-item><a href="#">About</a></sgds-mainnav-item>
64+
<sgds-mainnav-item><a href="#">Services</a></sgds-mainnav-item>
65+
<sgds-mainnav-dropdown>
66+
<span slot="toggler">More</span>
67+
<sgds-dropdown-item><a href="#">Item 1</a></sgds-dropdown-item>
68+
<sgds-dropdown-item><a href="#">Item 2</a></sgds-dropdown-item>
69+
<sgds-dropdown-item><a href="#">Item 3</a></sgds-dropdown-item>
70+
</sgds-mainnav-dropdown>
71+
<sgds-button slot="end">Login</sgds-button>
72+
</sgds-mainnav>
73+
<sgds-system-banner show fluid dismissible>
74+
<sgds-system-banner-item>
75+
<sgds-icon name="exclamation-circle" slot="icon"></sgds-icon>
76+
1This is a system banner message with fluid prop - stretches full width
77+
</sgds-system-banner-item>
78+
<sgds-system-banner-item>
79+
<sgds-icon name="exclamation-circle" slot="icon"></sgds-icon>
80+
2This is a system banner message with fluid prop - stretches full width
81+
</sgds-system-banner-item>
82+
</sgds-system-banner>
83+
<sgds-footer></sgds-footer>
84+
85+
<div class="content-placeholder">
86+
Page content goes here. Notice the header components stretch to full screen width.
87+
</div>

src/components/Mainnav/mainnav.css

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,15 @@ nav {
1717
position: relative;
1818
padding: 0px var(--sgds-mainnav-padding-x);
1919
width: 100%;
20-
max-width: var(--sgds-mainnav-max-width);
20+
max-width: var(--sgds-mainnav-max-width, 1440px);
2121
min-height: 80px;
2222
margin: auto;
2323
}
2424

25+
:host([fluid]) .navbar {
26+
max-width: none;
27+
}
28+
2529
@media screen and (max-width: 767px) {
2630
.navbar {
2731
padding: 0px var(--sgds-mainnav-mobile-padding-x);

src/components/Mainnav/sgds-mainnav.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ export class SgdsMainnav extends SgdsElement {
8181
@property({ type: String })
8282
expand: MainnavExpandSize = "lg";
8383

84+
/** When true, removes max-width constraint to allow content to stretch full screen width */
85+
@property({ type: Boolean, reflect: true })
86+
fluid = false;
87+
8488
/** @internal */
8589
@state()
8690
breakpointReached = false;

src/components/Masthead/masthead.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ a:hover {
4040
padding: 0.25rem var(--sgds-mainnav-padding-x, 2rem);
4141
}
4242

43+
:host([fluid]) .container {
44+
max-width: none;
45+
}
46+
4347
.sg-crest {
4448
width: 20px;
4549
height: 20px;

src/components/Masthead/sgds-masthead.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { html } from "lit";
2-
import { state } from "lit/decorators.js";
2+
import { property, state } from "lit/decorators.js";
33
import SgdsElement from "../../base/sgds-element";
44
import mastheadStyle from "./masthead.css";
55
import svgStyles from "../../styles/svg.css";
@@ -11,6 +11,10 @@ import anchorStyles from "../../styles/anchor.css";
1111
export class SgdsMasthead extends SgdsElement {
1212
static styles = [...SgdsElement.styles, svgStyles, anchorStyles, mastheadStyle];
1313

14+
/** When true, removes max-width constraint to allow content to stretch full screen width */
15+
@property({ type: Boolean, reflect: true })
16+
fluid = false;
17+
1418
/** @internal */
1519
@state()
1620
toggleVisibility = false;

src/components/SystemBanner/sgds-system-banner.ts

Lines changed: 42 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ export class SgdsSystemBanner extends SgdsElement {
3737
/** Enables a close button that allows the user to dismiss the alert. */
3838
@property({ type: Boolean, reflect: true }) dismissible = false;
3939

40+
/** When true, removes max-width constraint to allow content to stretch full screen width */
41+
@property({ type: Boolean, reflect: true }) fluid = false;
42+
4043
/** Closes the alert */
4144
public close() {
4245
this.show = false;
@@ -153,44 +156,46 @@ export class SgdsSystemBanner extends SgdsElement {
153156
}
154157
render() {
155158
return html`
156-
<div
157-
class="${classMap({
158-
banner: true
159-
})}"
160-
role="alert"
161-
aria-hidden=${this.show ? "false" : "true"}
162-
>
163-
<div class="content">
164-
<slot id="loop-slot"></slot>
159+
<div class="banner-wrapper">
160+
<div
161+
class="${classMap({
162+
banner: true
163+
})}"
164+
role="alert"
165+
aria-hidden=${this.show ? "false" : "true"}
166+
>
167+
<div class="content">
168+
<slot id="loop-slot"></slot>
169+
</div>
170+
${this.childCount > 1
171+
? html` <div class="pagination">
172+
<sgds-icon-button
173+
name="chevron-left"
174+
tone="fixed-light"
175+
variant="ghost"
176+
size="xs"
177+
@click=${this._prev}
178+
></sgds-icon-button>
179+
<span>${this._currentIndex + 1}/${this.childCount}</span>
180+
<sgds-icon-button
181+
name="chevron-right"
182+
tone="fixed-light"
183+
variant="ghost"
184+
size="xs"
185+
@click=${this._next}
186+
></sgds-icon-button>
187+
</div>`
188+
: nothing}
189+
${this.dismissible
190+
? html`
191+
<sgds-close-button
192+
aria-label="close the alert"
193+
@click=${this.close}
194+
tone="fixed-light"
195+
></sgds-close-button>
196+
`
197+
: nothing}
165198
</div>
166-
${this.childCount > 1
167-
? html` <div class="pagination">
168-
<sgds-icon-button
169-
name="chevron-left"
170-
tone="fixed-light"
171-
variant="ghost"
172-
size="xs"
173-
@click=${this._prev}
174-
></sgds-icon-button>
175-
<span>${this._currentIndex + 1}/${this.childCount}</span>
176-
<sgds-icon-button
177-
name="chevron-right"
178-
tone="fixed-light"
179-
variant="ghost"
180-
size="xs"
181-
@click=${this._next}
182-
></sgds-icon-button>
183-
</div>`
184-
: nothing}
185-
${this.dismissible
186-
? html`
187-
<sgds-close-button
188-
aria-label="close the alert"
189-
@click=${this.close}
190-
tone="fixed-light"
191-
></sgds-close-button>
192-
`
193-
: nothing}
194199
</div>
195200
`;
196201
}

src/components/SystemBanner/system-banner-item.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
}
1010

1111
/* Handles inline links and all texts */
12-
::slotted(*) {
12+
::slotted(:not(sgds-icon)) {
1313
font-size: var(--sgds-font-size-1, 14px) !important;
1414
line-height: var(--sgds-line-height-20, 20px) !important;
1515
color: var(--sgds-color-fixed-light) !important;

0 commit comments

Comments
 (0)