Skip to content

Commit 215ab97

Browse files
committedMar 6, 2025
added Layout::Flex::Item yielded subcomponent, with support for @tag, @basis, @grow, and @shrink arguments
1 parent ea63d13 commit 215ab97

File tree

8 files changed

+279
-4
lines changed

8 files changed

+279
-4
lines changed
 

‎packages/components/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@
252252
"./components/hds/icon/index.js": "./dist/_app_/components/hds/icon/index.js",
253253
"./components/hds/interactive/index.js": "./dist/_app_/components/hds/interactive/index.js",
254254
"./components/hds/layout/flex/index.js": "./dist/_app_/components/hds/layout/flex/index.js",
255+
"./components/hds/layout/flex/item.js": "./dist/_app_/components/hds/layout/flex/item.js",
255256
"./components/hds/link/inline.js": "./dist/_app_/components/hds/link/inline.js",
256257
"./components/hds/link/standalone.js": "./dist/_app_/components/hds/link/standalone.js",
257258
"./components/hds/menu-primitive/index.js": "./dist/_app_/components/hds/menu-primitive/index.js",

‎packages/components/src/components/hds/layout/flex/index.hbs

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
SPDX-License-Identifier: MPL-2.0
44
}}
55
{{#if (eq this.componentTag "div")}}
6-
<div class={{this.classNames}} ...attributes>{{yield}}</div>
6+
<div class={{this.classNames}} ...attributes>{{yield (hash Item=(component "hds/layout/flex/item"))}}</div>
77
{{else}}
8-
{{#let (element this.componentTag) as |Tag|}}<Tag class={{this.classNames}} ...attributes>{{yield}}</Tag>{{/let}}
9-
{{/if}}
8+
{{#let (element this.componentTag) as |Tag|}}<Tag class={{this.classNames}} ...attributes>{{yield (hash Item=(component "hds/layout/flex/item"))}}</Tag>{{/let}}
9+
{{/if}}

‎packages/components/src/components/hds/layout/flex/index.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55

66
import Component from '@glimmer/component';
77

8+
import type { ComponentLike } from '@glint/template';
9+
10+
import type { HdsLayoutFlexItemSignature } from './item.ts';
11+
812
import {
913
HdsLayoutFlexDirectionValues,
1014
HdsLayoutFlexJustifyValues,
@@ -37,7 +41,11 @@ export interface HdsLayoutFlexSignature {
3741
isInline?: boolean;
3842
};
3943
Blocks: {
40-
default: [];
44+
default: [
45+
{
46+
Item?: ComponentLike<HdsLayoutFlexItemSignature>;
47+
}
48+
];
4149
};
4250
Element: AvailableElements;
4351
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{{!
2+
Copyright (c) HashiCorp, Inc.
3+
SPDX-License-Identifier: MPL-2.0
4+
}}
5+
{{#if (eq this.componentTag "div")}}
6+
<div class={{this.classNames}} {{style this.inlineStyles}} ...attributes>{{yield}}</div>
7+
{{else}}
8+
{{#let (element this.componentTag) as |Tag|}}<Tag class={{this.classNames}} {{style this.inlineStyles}} ...attributes>{{yield}}</Tag>{{/let}}
9+
{{/if}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* Copyright (c) HashiCorp, Inc.
3+
* SPDX-License-Identifier: MPL-2.0
4+
*/
5+
6+
import Component from '@glimmer/component';
7+
8+
// A list of all existing tag names in the HTMLElementTagNameMap interface
9+
type AvailableTagNames = keyof HTMLElementTagNameMap;
10+
// A union of all types in the HTMLElementTagNameMap interface
11+
type AvailableElements = HTMLElementTagNameMap[keyof HTMLElementTagNameMap];
12+
13+
export interface HdsLayoutFlexItemSignature {
14+
Args: {
15+
tag?: AvailableTagNames;
16+
basis?: string;
17+
grow?: boolean | number | string;
18+
shrink?: boolean | number | string;
19+
};
20+
Blocks: {
21+
default: [];
22+
};
23+
Element: AvailableElements;
24+
}
25+
26+
export default class HdsLayoutFlexItem extends Component<HdsLayoutFlexItemSignature> {
27+
get componentTag(): AvailableTagNames {
28+
return this.args.tag ?? 'div';
29+
}
30+
31+
get inlineStyles(): Record<string, unknown> {
32+
const inlineStyles: {
33+
'flex-basis'?: string;
34+
'flex-grow'?: string;
35+
'flex-shrink'?: string;
36+
} = {};
37+
38+
// we handle all cases of `basis` values via inline styles
39+
if (this.args.basis) {
40+
inlineStyles['flex-basis'] = this.args.basis;
41+
}
42+
43+
// we handle non-standard cases of `grow` values via inline styles
44+
if (typeof this.args.grow === 'number' && this.args.grow > 1) {
45+
// the `{{style}}` modifier accepts only strings
46+
inlineStyles['flex-grow'] = this.args.grow.toString();
47+
} else if (typeof this.args.grow === 'string') {
48+
inlineStyles['flex-grow'] = this.args.grow;
49+
}
50+
51+
// we handle non-standard cases of `shrink` values via inline styles
52+
if (typeof this.args.shrink === 'number' && this.args.shrink > 1) {
53+
// the `{{style}}` modifier accepts only strings
54+
inlineStyles['flex-shrink'] = this.args.shrink.toString();
55+
} else if (typeof this.args.shrink === 'string') {
56+
inlineStyles['flex-shrink'] = this.args.shrink;
57+
}
58+
59+
return inlineStyles;
60+
}
61+
62+
get classNames() {
63+
const classes = ['hds-layout-flex-item'];
64+
65+
// add a class based on the @grow argument (if set to `0/1` or `true/false`)
66+
if (this.args.grow === 0 || this.args.grow === false) {
67+
classes.push('hds-layout-flex-item--grow-0');
68+
} else if (this.args.grow === 1 || this.args.grow === true) {
69+
classes.push('hds-layout-flex-item--grow-1');
70+
}
71+
72+
// add a class based on the @shrink argument (if set to `0/1` or `true/false`)
73+
if (this.args.shrink === 0 || this.args.shrink === false) {
74+
classes.push('hds-layout-flex-item--shrink-0');
75+
} else if (this.args.shrink === 1 || this.args.shrink === true) {
76+
classes.push('hds-layout-flex-item--shrink-1');
77+
}
78+
79+
return classes.join(' ');
80+
}
81+
}

‎packages/components/src/styles/components/layout/flex.scss

+19
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,22 @@
8080
.hds-layout-flex--align-items-stretch {
8181
align-items: stretch;
8282
}
83+
84+
85+
// FLEX ITEM
86+
87+
.hds-layout-flex-item--grow-0 {
88+
flex-grow: 0;
89+
}
90+
91+
.hds-layout-flex-item--grow-1 {
92+
flex-grow: 1;
93+
}
94+
95+
.hds-layout-flex-item--shrink-0 {
96+
flex-shrink: 0;
97+
}
98+
99+
.hds-layout-flex-item--shrink-1 {
100+
flex-shrink: 1;
101+
}

‎packages/components/src/template-registry.ts

+3
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ import type HdsIconComponent from './components/hds/icon';
142142
import type HdsIconTileComponent from './components/hds/icon-tile';
143143
import type HdsInteractiveComponent from './components/hds/interactive';
144144
import type HdsLayoutFlexComponent from './components/hds/layout/flex';
145+
import type HdsLayoutFlexItemComponent from './components/hds/layout/flex/item';
145146
import type HdsLinkInlineComponent from './components/hds/link/inline';
146147
import type HdsLinkStandaloneComponent from './components/hds/link/standalone';
147148
import type HdsMenuPrimitiveComponent from './components/hds/menu-primitive';
@@ -698,6 +699,8 @@ export default interface HdsComponentsRegistry {
698699
// Layout Flex
699700
'Hds::Layout::Flex': typeof HdsLayoutFlexComponent;
700701
'hds/layout/flex': typeof HdsLayoutFlexComponent;
702+
'Hds::Layout::Flex::Item': typeof HdsLayoutFlexItemComponent;
703+
'hds/layout/flex/item': typeof HdsLayoutFlexItemComponent;
701704

702705
// Link Inline
703706
'Hds::Link::Inline': typeof HdsLinkInlineComponent;

‎showcase/app/templates/layouts/flex.hbs

+154
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,158 @@
145145
{{/let}}
146146
</Shw::Flex>
147147

148+
</section>
149+
150+
<Shw::Divider />
151+
152+
<section data-test-percy>
153+
154+
<Shw::Text::H2>Flex::Item</Shw::Text::H2>
155+
156+
<Shw::Grid @columns={{1}} as |SG|>
157+
<SG.Item @label="used directly or via yielded component">
158+
<Shw::Outliner>
159+
<Hds::Layout::Flex as |HLF|>
160+
<Shw::Placeholder @text="item #1" @height="40" @background="#e4c5f3" />
161+
<Hds::Layout::Flex::Item>
162+
<Shw::Placeholder @text="item #2 within Flex::Item" @height="40" @background="#e5ffd2" />
163+
</Hds::Layout::Flex::Item>
164+
<Shw::Placeholder @text="item #3" @height="40" @background="#d2f4ff" />
165+
<HLF.Item>
166+
<Shw::Placeholder @text="item #4 within HLF.Item" @height="40" @background="#fff8d2" />
167+
</HLF.Item>
168+
</Hds::Layout::Flex>
169+
</Shw::Outliner>
170+
</SG.Item>
171+
</Shw::Grid>
172+
173+
<Shw::Text::H3>Basis</Shw::Text::H3>
174+
175+
<Shw::Grid @columns={{1}} as |SG|>
176+
<SG.Item as |SGI|>
177+
<SGI.Label>with <code>size</code> values</SGI.Label>
178+
<Shw::Outliner>
179+
<Hds::Layout::Flex as |HLF|>
180+
<Hds::Layout::Flex::Item @basis="10em">
181+
<Shw::Placeholder @text="item #1 with basis=10em" @height="40" @background="#e4c5f3" />
182+
</Hds::Layout::Flex::Item>
183+
<Hds::Layout::Flex::Item @basis="200px">
184+
<Shw::Placeholder @text="item #2 with basis=200px" @height="40" @background="#e5ffd2" />
185+
</Hds::Layout::Flex::Item>
186+
<HLF.Item @basis="40%">
187+
<Shw::Placeholder @text="item #3 with basis=40%" @height="40" @background="#d2f4ff" />
188+
</HLF.Item>
189+
<HLF.Item @basis="auto">
190+
<Shw::Placeholder @text="item #4 with basis=auto" @height="40" @background="#fff8d2" />
191+
</HLF.Item>
192+
</Hds::Layout::Flex>
193+
</Shw::Outliner>
194+
</SG.Item>
195+
<SG.Item as |SGI|>
196+
<SGI.Label>with <code>keyword</code> values</SGI.Label>
197+
<Shw::Outliner>
198+
<Hds::Layout::Flex as |HLF|>
199+
<Hds::Layout::Flex::Item @basis="auto">
200+
<Shw::Placeholder @text="item #1 with basis=auto" @height="40" @background="#e4c5f3" />
201+
</Hds::Layout::Flex::Item>
202+
<Hds::Layout::Flex::Item @basis="content">
203+
<Shw::Placeholder @text="item #2 with basis=content" @height="40" @background="#e5ffd2" />
204+
</Hds::Layout::Flex::Item>
205+
<HLF.Item @basis="max-content">
206+
<Shw::Placeholder @text="item #3 with basis=max-content" @height="40" @background="#d2f4ff" />
207+
</HLF.Item>
208+
<HLF.Item @basis="fit-content">
209+
<Shw::Placeholder @text="item #4 with basis=fit-content" @height="40" @background="#fff8d2" />
210+
</HLF.Item>
211+
</Hds::Layout::Flex>
212+
</Shw::Outliner>
213+
</SG.Item>
214+
</Shw::Grid>
215+
216+
<Shw::Text::H3>Grow</Shw::Text::H3>
217+
218+
<Shw::Grid @columns={{1}} as |SG|>
219+
<SG.Item as |SGI|>
220+
<SGI.Label>with <code>0/1</code> and <code>true/false</code> values</SGI.Label>
221+
<Shw::Outliner>
222+
<Hds::Layout::Flex as |HLF|>
223+
<Hds::Layout::Flex::Item @grow={{0}}>
224+
<Shw::Placeholder @text="item #1 with grow=0" @height="40" @background="#e4c5f3" />
225+
</Hds::Layout::Flex::Item>
226+
<Hds::Layout::Flex::Item @grow={{1}}>
227+
<Shw::Placeholder @text="item #2 with grow=1" @height="40" @background="#e5ffd2" />
228+
</Hds::Layout::Flex::Item>
229+
<HLF.Item @grow={{false}}>
230+
<Shw::Placeholder @text="item #3 with grow=false" @height="40" @background="#d2f4ff" />
231+
</HLF.Item>
232+
<HLF.Item @grow={{true}}>
233+
<Shw::Placeholder @text="item #4 with grow=true" @height="40" @background="#fff8d2" />
234+
</HLF.Item>
235+
</Hds::Layout::Flex>
236+
</Shw::Outliner>
237+
</SG.Item>
238+
<SG.Item as |SGI|>
239+
<SGI.Label>with <code>numeric/string</code> values (to handle special cases)</SGI.Label>
240+
<Shw::Outliner>
241+
<Hds::Layout::Flex as |HLF|>
242+
<Hds::Layout::Flex::Item @grow={{1}}>
243+
<Shw::Placeholder @text="item #1 with grow=1" @height="40" @background="#e4c5f3" />
244+
</Hds::Layout::Flex::Item>
245+
<Hds::Layout::Flex::Item @grow={{2}}>
246+
<Shw::Placeholder @text="item #2 with grow=2" @height="40" @background="#e5ffd2" />
247+
</Hds::Layout::Flex::Item>
248+
<HLF.Item @grow="3">
249+
<Shw::Placeholder @text="item #3 with grow='3'" @height="40" @background="#d2f4ff" />
250+
</HLF.Item>
251+
<HLF.Item @grow="4">
252+
<Shw::Placeholder @text="item #4 with grow='4'" @height="40" @background="#fff8d2" />
253+
</HLF.Item>
254+
</Hds::Layout::Flex>
255+
</Shw::Outliner>
256+
</SG.Item>
257+
</Shw::Grid>
258+
259+
<Shw::Text::H3>Shrink</Shw::Text::H3>
260+
261+
<Shw::Grid @columns={{1}} as |SG|>
262+
<SG.Item as |SGI|>
263+
<SGI.Label>with <code>0/1</code> and <code>true/false</code> values</SGI.Label>
264+
<Shw::Outliner {{style max-width="600px"}}>
265+
<Hds::Layout::Flex as |HLF|>
266+
<Hds::Layout::Flex::Item @basis="300px" @shrink={{0}}>
267+
<Shw::Placeholder @text="item #1 with shrink=0" @height="40" @background="#e4c5f3" />
268+
</Hds::Layout::Flex::Item>
269+
<Hds::Layout::Flex::Item @basis="300px" @shrink={{1}}>
270+
<Shw::Placeholder @text="item #2 with shrink=1" @height="40" @background="#e5ffd2" />
271+
</Hds::Layout::Flex::Item>
272+
<HLF.Item @basis="300px" @shrink={{false}}>
273+
<Shw::Placeholder @text="item #3 with shrink=false" @height="40" @background="#d2f4ff" />
274+
</HLF.Item>
275+
<HLF.Item @basis="300px" @shrink={{true}}>
276+
<Shw::Placeholder @text="item #4 with shrink=true" @height="40" @background="#fff8d2" />
277+
</HLF.Item>
278+
</Hds::Layout::Flex>
279+
</Shw::Outliner>
280+
</SG.Item>
281+
<SG.Item as |SGI|>
282+
<SGI.Label>with <code>numeric/string</code> values (to handle special cases)</SGI.Label>
283+
<Shw::Outliner {{style max-width="600px"}}>
284+
<Hds::Layout::Flex as |HLF|>
285+
<Hds::Layout::Flex::Item @basis="300px" @shrink={{1}}>
286+
<Shw::Placeholder @text="item #1 with shrink=1" @height="40" @background="#e4c5f3" />
287+
</Hds::Layout::Flex::Item>
288+
<Hds::Layout::Flex::Item @basis="300px" @shrink={{2}}>
289+
<Shw::Placeholder @text="item #2 with shrink=2" @height="40" @background="#e5ffd2" />
290+
</Hds::Layout::Flex::Item>
291+
<HLF.Item @basis="300px" @shrink="3">
292+
<Shw::Placeholder @text="item #3 with shrink='3'" @height="40" @background="#d2f4ff" />
293+
</HLF.Item>
294+
<HLF.Item @basis="300px" @shrink="4">
295+
<Shw::Placeholder @text="item #4 with shrink='4'" @height="40" @background="#fff8d2" />
296+
</HLF.Item>
297+
</Hds::Layout::Flex>
298+
</Shw::Outliner>
299+
</SG.Item>
300+
</Shw::Grid>
301+
148302
</section>

0 commit comments

Comments
 (0)