Skip to content

Commit 0cf246e

Browse files
authored
feat(create-flows): add narrow tearsheet and full page web component examples (#8417)
* feat(create-flows): add narrow ts and fullpage examples * chore: add missing license
1 parent 2642458 commit 0cf246e

File tree

10 files changed

+390
-5
lines changed

10 files changed

+390
-5
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//
2+
// Copyright IBM Corp. 2025
3+
//
4+
// This source code is licensed under the Apache-2.0 license found in the
5+
// LICENSE file in the root directory of this source tree.
6+
//
7+
8+
@use '@carbon/styles/scss/theme';
9+
@use '@carbon/styles/scss/spacing';
10+
@use '@carbon/styles/scss/type';
11+
/* stylelint-disable-next-line selector-type-no-unknown */
12+
:host(step-full-page) {
13+
display: grid;
14+
block-size: 100vh;
15+
color: theme.$text-primary;
16+
grid-template-rows: minmax(auto, 100%);
17+
18+
.step-full-page-with-util {
19+
display: flex;
20+
overflow: hidden;
21+
padding: 0;
22+
margin: 0;
23+
max-block-size: 100%;
24+
}
25+
26+
.create-full-page-header {
27+
padding-block-end: spacing.$spacing-06;
28+
29+
h2 {
30+
@include type.type-style('heading-04');
31+
}
32+
}
33+
34+
.create-full-page-wrapper {
35+
display: flex;
36+
overflow: auto;
37+
flex-direction: column;
38+
flex-grow: 1;
39+
justify-content: space-between;
40+
background-color: theme.$layer-01;
41+
block-size: calc(100% - spacing.$spacing-06);
42+
color: theme.$text-primary;
43+
overflow-x: hidden;
44+
padding-block-start: spacing.$spacing-06;
45+
}
46+
47+
.create-influencer {
48+
box-sizing: border-box;
49+
padding: spacing.$spacing-06 spacing.$spacing-07;
50+
border-inline-end: 1px solid theme.$border-subtle;
51+
inline-size: 256px;
52+
min-inline-size: 256px;
53+
}
54+
55+
.create-full-page-subtitle {
56+
@include type.type-style('heading-compact-01');
57+
}
58+
59+
.create-full-page-description {
60+
@include type.type-style('body-01');
61+
}
62+
63+
.create-full-page-layer {
64+
inline-size: 100%;
65+
}
66+
67+
.create-full-page-actions {
68+
display: grid;
69+
block-size: 64px;
70+
border-block-start: 1px solid theme.$border-subtle;
71+
grid-auto-rows: minmax(auto, auto);
72+
grid-template-columns: repeat(6, 1fr);
73+
74+
@include type.type-style('body-compact-01');
75+
}
76+
77+
.create-full-page-action__cancel {
78+
grid-column: span 4;
79+
}
80+
81+
::part(grid) {
82+
max-inline-size: 100%;
83+
}
84+
}
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
/**
2+
* Copyright IBM Corp. 2025, 2025
3+
*
4+
* This source code is licensed under the Apache-2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import { LitElement, PropertyValues, html, nothing } from 'lit';
9+
import { customElement, property, state } from 'lit/decorators.js';
10+
import { SignalWatcher } from '@lit-labs/signals';
11+
import styles from './step-full-page.scss?lit';
12+
import { StepInstance } from '../step-flow-signal';
13+
import '@carbon/web-components/es/components/progress-indicator/index.js';
14+
import '@carbon/web-components/es/components/stack/index.js';
15+
import '@carbon/web-components/es/components/code-snippet/index.js';
16+
import '@carbon/web-components/es/components/button/index.js';
17+
import '@carbon/web-components/es/components/grid/index';
18+
import '../step-group';
19+
import {
20+
registerFocusableContainers,
21+
trapFocus,
22+
} from '../../../utilities/manageFocusTrap/manageFocusTrap';
23+
24+
interface FormStateType {
25+
email?: string;
26+
city?: string;
27+
state?: string;
28+
}
29+
30+
@customElement('step-full-page')
31+
export class StepFullPage extends SignalWatcher(LitElement) {
32+
@property({ type: Boolean })
33+
narrow: boolean = false;
34+
@state()
35+
private _open: boolean = false;
36+
37+
@state()
38+
private _email: string = '';
39+
40+
@state()
41+
private _city: string = '';
42+
43+
@state()
44+
private _state: string = '';
45+
46+
private _trapFocusAPI: { cleanup: () => void } | null = null;
47+
48+
private _handleCancelButton() {
49+
this._open = false;
50+
this._stepInfo.reset();
51+
}
52+
53+
private _handleBackButton() {
54+
const { currentStep } = this._stepInfo.data;
55+
if (currentStep === 0) {
56+
return;
57+
}
58+
return this._stepInfo.handlePrevious();
59+
}
60+
61+
private _handleNextButton() {
62+
const { currentStep, totalSteps } = this._stepInfo.data;
63+
if (currentStep + 1 === totalSteps) {
64+
this._open = false;
65+
this._stepInfo.reset();
66+
return;
67+
}
68+
return this._stepInfo.handleNext();
69+
}
70+
71+
private _handleEmailInput(e) {
72+
const savedFormState = structuredClone(
73+
this._stepInfo.data.formState
74+
) as FormStateType;
75+
savedFormState.email = e.target.value;
76+
this._stepInfo.updateFormState = savedFormState;
77+
}
78+
79+
private _handleCityInput(e) {
80+
const savedFormState = structuredClone(
81+
this._stepInfo.data.formState
82+
) as FormStateType;
83+
savedFormState.city = e.target.value;
84+
this._stepInfo.updateFormState = savedFormState;
85+
}
86+
87+
private _handleStateInput(e) {
88+
const savedFormState = structuredClone(
89+
this._stepInfo.data.formState
90+
) as FormStateType;
91+
savedFormState.state = e.target.value;
92+
this._stepInfo.updateFormState = savedFormState;
93+
}
94+
95+
private _stepInfo = new StepInstance();
96+
97+
connectedCallback(): void {
98+
super.connectedCallback();
99+
this._stepInfo.updateTotalStepCount = 3;
100+
}
101+
102+
disconnectedCallback(): void {
103+
this._trapFocusAPI?.cleanup();
104+
}
105+
106+
protected firstUpdated(_changedProperties: PropertyValues): void {
107+
registerFocusableContainers(
108+
this.shadowRoot?.querySelector('step-full-page')
109+
);
110+
}
111+
112+
updated(changedProps: Map<string | number | symbol, unknown>) {
113+
if (changedProps.has('_open')) {
114+
const isOpen = this._open;
115+
if (isOpen) {
116+
// `focusableContainers` holds the containers where we can query DOM elements.
117+
// Our strategy here is to let child/slotted components register their containers,
118+
// which are then passed to `trapFocus`. This allows the utility to query elements
119+
// directly without being blocked by shadow DOM boundaries.
120+
121+
this._trapFocusAPI = trapFocus();
122+
}
123+
}
124+
}
125+
126+
render() {
127+
const { formState, totalSteps, currentStep } = this._stepInfo.data;
128+
129+
return html` <div class=${'step-full-page-with-util'} ?open=${this._open}>
130+
<!-- slotted influencer -->
131+
<div class="create-influencer" slot="influencer">
132+
<cds-progress-indicator
133+
vertical
134+
class=${`custom-step-util__dummy-content-block`}
135+
>
136+
<cds-progress-step
137+
label="First step"
138+
state=${currentStep + 1 === 1 ? 'current' : 'complete'}
139+
></cds-progress-step>
140+
<cds-progress-step
141+
label="Second step"
142+
state=${currentStep + 1 === 2
143+
? 'current'
144+
: currentStep + 1 < 2
145+
? 'incomplete'
146+
: 'complete'}
147+
></cds-progress-step>
148+
<cds-progress-step
149+
label="Third step"
150+
state=${currentStep + 1 === 3
151+
? 'current'
152+
: currentStep + 1 < 3
153+
? 'incomplete'
154+
: 'complete'}
155+
></cds-progress-step>
156+
</cds-progress-indicator>
157+
</div>
158+
<cds-layer level="1" class="create-full-page-layer">
159+
<div class="create-full-page-wrapper">
160+
<div class="upper-step-content">
161+
<cds-grid class="sb-grid">
162+
<cds-column lg="8" sm="4">
163+
<div class="create-full-page-header">
164+
<!-- slotted header label -->
165+
<!-- <span slot="label">Optional label for context</span> -->
166+
167+
<!-- slotted header title -->
168+
<h2>Step ${currentStep + 1}</h2>
169+
<p class="create-full-page-subtitle">
170+
Optional subtitle to display on a step
171+
</p>
172+
173+
<!-- slotted header description -->
174+
<p class="create-full-page-description">
175+
Optional description to display on a step
176+
</p>
177+
</div>
178+
</cds-column>
179+
</cds-grid>
180+
181+
<!-- default slotted content -->
182+
<step-group>
183+
<cds-grid class="sb-grid">
184+
<cds-column lg="8" sm="4">
185+
${currentStep + 1 === 1
186+
? html`<div>
187+
<cds-stack gap="6" orientation="horizontal">
188+
<cds-text-input
189+
label="Email"
190+
id="tearsheet-story-text-input-a"
191+
value=${this._email}
192+
@input="${this._handleEmailInput}"
193+
></cds-text-input>
194+
</cds-stack>
195+
</div>`
196+
: nothing}
197+
${currentStep + 1 === 2
198+
? html`<div>
199+
<cds-stack gap="6" orientation="horizontal">
200+
<cds-text-input
201+
label="City"
202+
id="tearsheet-story-text-input-city"
203+
value=${this._city}
204+
@input="${this._handleCityInput}"
205+
></cds-text-input>
206+
<cds-text-input
207+
label="State"
208+
id="tearsheet-story-text-input-state"
209+
value=${this._state}
210+
@input="${this._handleStateInput}"
211+
></cds-text-input>
212+
</cds-stack>
213+
</div>`
214+
: nothing}
215+
${currentStep + 1 === 3
216+
? html`<div>
217+
<!-- //cspell: disable -->
218+
<cds-code-snippet
219+
type="multi"
220+
copy-text=""
221+
maxcollapsednumberofrows="15"
222+
maxexpandednumberofrows=""
223+
mincollapsednumberofrows="3"
224+
minexpandednumberofrows=""
225+
show-less-text="Show less"
226+
show-more-text="Show more"
227+
feedback=""
228+
feedback-timeout="0"
229+
tooltip-content="Copy to clipboard"
230+
>
231+
${JSON.stringify(formState, null, 2)}
232+
</cds-code-snippet>
233+
<!-- //cspell: enable -->
234+
</div>`
235+
: nothing}
236+
</cds-column>
237+
</cds-grid>
238+
</step-group>
239+
</div>
240+
<div class="create-full-page-actions">
241+
<cds-button
242+
slot="actions"
243+
kind=${'ghost'}
244+
class="create-full-page-action__cancel"
245+
@click=${this._handleCancelButton}
246+
>
247+
Cancel
248+
</cds-button>
249+
<cds-button
250+
slot="actions"
251+
kind=${'secondary'}
252+
@click=${this._handleBackButton}
253+
>
254+
Back
255+
</cds-button>
256+
<cds-button slot="actions" @click=${this._handleNextButton}>
257+
${currentStep + 1 < totalSteps ? 'Next' : 'Submit'}
258+
</cds-button>
259+
</div>
260+
</div>
261+
</cds-layer>
262+
</div>`;
263+
}
264+
static styles = styles;
265+
}
266+
267+
declare global {
268+
interface HTMLElementTagNameMap {
269+
'step-full-page': StepFullPage;
270+
}
271+
}
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66
*/
77

88
import { LitElement, PropertyValues, html, nothing } from 'lit';
9-
import { customElement, state } from 'lit/decorators.js';
9+
import { customElement, property, state } from 'lit/decorators.js';
1010
import { SignalWatcher } from '@lit-labs/signals';
1111
import styles from '../story-styles.scss?lit';
1212
import { StepInstance } from '../step-flow-signal';
1313
import '@carbon/web-components/es/components/progress-indicator/index.js';
1414
import '@carbon/web-components/es/components/stack/index.js';
1515
import '@carbon/web-components/es/components/code-snippet/index.js';
1616
import '../step-group';
17-
import '../../tearsheet/index.js';
17+
import '../../../components/tearsheet/index.js';
1818
import {
1919
registerFocusableContainers,
2020
trapFocus,
@@ -28,6 +28,8 @@ interface FormStateType {
2828

2929
@customElement('step-tearsheet')
3030
export class StepTearsheet extends SignalWatcher(LitElement) {
31+
@property({ type: Boolean })
32+
narrow: boolean = false;
3133
@state()
3234
private _open: boolean = false;
3335

@@ -138,7 +140,7 @@ export class StepTearsheet extends SignalWatcher(LitElement) {
138140
class=${'step-tearsheet-with-util'}
139141
selector-initial-focus=${'#tearsheet-story-text-input-a'}
140142
?open=${this._open}
141-
width=${'wide'}
143+
width=${this.narrow ? 'narrow' : 'wide'}
142144
influencer-placement=${'left'}
143145
prevent-close-on-click-outside
144146
>

0 commit comments

Comments
 (0)