-
Notifications
You must be signed in to change notification settings - Fork 49
LayoutGrid - Add responsive column layout feature (HDS-5692)
#3438
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c6033d6
949eda6
dd98f3d
ddea1dd
c078d14
d026d32
5ab2788
025fbec
c896031
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,11 +24,19 @@ export const ALIGNS: HdsLayoutGridAligns[] = Object.values( | |
| export const DEFAULT_GAP = HdsLayoutGridGapValues.Zero; | ||
| export const GAPS: HdsLayoutGridGaps[] = Object.values(HdsLayoutGridGapValues); | ||
|
|
||
| type ResponsiveColumnWidths = { | ||
| sm?: string; | ||
| md?: string; | ||
| lg?: string; | ||
KristinLBradley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| xl?: string; | ||
| xxl?: string; | ||
| }; | ||
|
|
||
| export interface HdsLayoutGridSignature { | ||
| Args: { | ||
| tag?: AvailableTagNames; | ||
| columnMinWidth?: string; | ||
| columnWidth?: string; | ||
| columnWidth?: string | ResponsiveColumnWidths; | ||
| align?: HdsLayoutGridAligns; | ||
| gap?: HdsLayoutGridGaps | [HdsLayoutGridGaps, HdsLayoutGridGaps]; | ||
| }; | ||
|
|
@@ -89,21 +97,32 @@ export default class HdsLayoutGrid extends Component<HdsLayoutGridSignature> { | |
| /* | ||
| LOGIC: | ||
|
|
||
| If neither columnMinWidth nor columnWidth are passed in: | ||
| 1) we do not set --hds-layout-grid-column-min-width (defaults to 0px) | ||
| 2) We set --hds-layout-grid-column-fill-type to "auto-fit" (results in a more fluid layout) | ||
|
|
||
| If columnMinWidth is passed in: | ||
| 1) we set --hds-layout-grid-column-min-width to the passed in value | ||
| 2) We use the fallback value of "auto-fit" for --hds-layout-grid-column-fill-type (reults in a more fluid layout) | ||
| 2) We set --hds-layout-grid-column-fill-type to "auto-fit" (results in a more fluid layout) | ||
|
|
||
| If columnWidth is passed in: | ||
| 1) we set --hds-layout-grid-column-min-width to the passed in value | ||
| 2) we set --hds-layout-grid-column-fill-type to "auto-fill" (results in a more fixed layout) | ||
| 2) We use the fallback value of "auto-fill" for --hds-layout-grid-column-fill-type (results in a more fixed layout) | ||
|
|
||
| If both columnMinWidth & columnWidth are passed in: | ||
| 1) We throw an error, as it doesn't make sense in the context of a CSS grid layout (too complex to determine which to use & desired behavior) | ||
| * We throw an error, as it doesn't make sense in the context of a CSS grid layout (too complex to determine which to use & desired behavior) | ||
| */ | ||
| get inlineStyles(): Record<string, unknown> { | ||
| const inlineStyles: { | ||
| '--hds-layout-grid-column-min-width'?: string; | ||
| '--hds-layout-grid-column-fill-type'?: string; | ||
|
|
||
| // responsive | ||
| '--hds-layout-grid-column-width-sm'?: string; | ||
| '--hds-layout-grid-column-width-md'?: string; | ||
| '--hds-layout-grid-column-width-lg'?: string; | ||
| '--hds-layout-grid-column-width-xl'?: string; | ||
| '--hds-layout-grid-column-width-xxl'?: string; | ||
| } = {}; | ||
|
|
||
| // if both columnMinWidth and columnWidth are passed in, we throw an error | ||
|
|
@@ -115,10 +134,40 @@ export default class HdsLayoutGrid extends Component<HdsLayoutGridSignature> { | |
| if (this.args.columnMinWidth) { | ||
| inlineStyles['--hds-layout-grid-column-min-width'] = | ||
| this.args.columnMinWidth; | ||
| inlineStyles['--hds-layout-grid-column-fill-type'] = 'auto-fit'; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [question] usually we use CSS variables in JS to pass down to the CSS dynamic values set by the consumers; in this case, we already know the two possible values ( |
||
| } else if (this.args.columnWidth) { | ||
| inlineStyles['--hds-layout-grid-column-min-width'] = | ||
| this.args.columnWidth; | ||
| inlineStyles['--hds-layout-grid-column-fill-type'] = 'auto-fill'; | ||
| if (typeof this.args.columnWidth === 'string') { | ||
| inlineStyles['--hds-layout-grid-column-min-width'] = | ||
| this.args.columnWidth; | ||
| // Note: We use the default "auto-fill" in the CSS for column-fill-type | ||
| } | ||
| } else { | ||
| // neither columnMinWidth nor columnWidth are set | ||
| inlineStyles['--hds-layout-grid-column-fill-type'] = 'auto-fit'; | ||
| } | ||
|
|
||
| // Responsize column widths | ||
| if (typeof this.args.columnWidth === 'object') { | ||
| if (this.args.columnWidth.sm) { | ||
| inlineStyles['--hds-layout-grid-column-width-sm'] = | ||
| this.args.columnWidth.sm; | ||
| } | ||
| if (this.args.columnWidth.md) { | ||
| inlineStyles['--hds-layout-grid-column-width-md'] = | ||
| this.args.columnWidth.md; | ||
| } | ||
| if (this.args.columnWidth.lg) { | ||
| inlineStyles['--hds-layout-grid-column-width-lg'] = | ||
| this.args.columnWidth.lg; | ||
| } | ||
| if (this.args.columnWidth.xl) { | ||
| inlineStyles['--hds-layout-grid-column-width-xl'] = | ||
| this.args.columnWidth.xl; | ||
| } | ||
| if (this.args.columnWidth.xxl) { | ||
| inlineStyles['--hds-layout-grid-column-width-xxl'] = | ||
| this.args.columnWidth.xxl; | ||
| } | ||
| } | ||
|
|
||
| return inlineStyles; | ||
|
|
@@ -143,6 +192,26 @@ export default class HdsLayoutGrid extends Component<HdsLayoutGridSignature> { | |
| } | ||
| } | ||
|
|
||
| // add classes based on responsive width arguments | ||
| // If an object is passed in for the columnWidth arg, set the respective CSS classes | ||
| if (typeof this.args.columnWidth === 'object') { | ||
| if (this.args.columnWidth.sm) { | ||
| classes.push('hds-layout-grid--column-width-sm'); | ||
| } | ||
| if (this.args.columnWidth.md) { | ||
| classes.push('hds-layout-grid--column-width-md'); | ||
| } | ||
| if (this.args.columnWidth.lg) { | ||
| classes.push('hds-layout-grid--column-width-lg'); | ||
| } | ||
| if (this.args.columnWidth.xl) { | ||
| classes.push('hds-layout-grid--column-width-xl'); | ||
| } | ||
| if (this.args.columnWidth.xxl) { | ||
| classes.push('hds-layout-grid--column-width-xxl'); | ||
| } | ||
| } | ||
|
|
||
| return classes.join(' '); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,17 +7,24 @@ | |
| // LAYOUT > GRID | ||
| // | ||
|
|
||
| @use "../../mixins/breakpoints" as *; | ||
|
|
||
| .hds-layout-grid { | ||
| // Note: "Unitless 0" <length>s arenβt supported in math functions so we use 0px as a default value within calc() | ||
| // https://drafts.csswg.org/css-values/#calc-type-checking | ||
| // We initialize the variable here to prevent inheritance issues in nested grids | ||
|
|
||
| // We initialize the variables here to prevent inheritance issues in nested grids | ||
| --hds-layout-grid-column-min-width: 0px; | ||
| // Auto-fit vs Auto-fill: | ||
| // * auto-fit = fluid layout, stretches child items to fill available space in a row (collapses any empty column tracks) | ||
| // * auto-fill = fixed layout, preserves empty column tracks in a row (does not stretch child items to fill empty space) | ||
| --hds-layout-grid-column-fill-type: auto-fill; | ||
|
|
||
| display: grid; | ||
|
|
||
| // The column gap value is subtracted from the column-min-width to prevent overflow & simplify API for consumers | ||
| grid-template-columns: repeat( | ||
| var(--hds-layout-grid-column-fill-type, auto-fit), | ||
| var(--hds-layout-grid-column-fill-type), | ||
| minmax(calc(var(--hds-layout-grid-column-min-width) - var(--hds-layout-grid-column-gap)), 1fr) | ||
| ); | ||
|
|
||
|
|
@@ -27,6 +34,59 @@ | |
| gap: var(--hds-layout-grid-row-gap) var(--hds-layout-grid-column-gap); | ||
| } | ||
|
|
||
| // responsive column widths (sm, md, lg) | ||
|
|
||
| // Note: We repeat the calculation using the view specific variables as | ||
| // resetting just the variable values themselves doesn't work as the inline styles take precedence | ||
|
|
||
| // "sm" view (mobile devices) | ||
| .hds-layout-grid--column-width-sm { | ||
| grid-template-columns: repeat( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see comment above about the fact that, since this is mobile-first, if the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll experiment with this more. This is part of why I had the concept of a kind of "default" size in the original implementation. |
||
| var(--hds-layout-grid-column-fill-type), | ||
| minmax(calc(var(--hds-layout-grid-column-width-sm) - var(--hds-layout-grid-column-gap)), 1fr) | ||
| ); | ||
| } | ||
|
|
||
| // "md" view, 768px and above (tablets and small laptops) | ||
| .hds-layout-grid--column-width-md { | ||
KristinLBradley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| @include hds-breakpoint-above("md") { | ||
| grid-template-columns: repeat( | ||
| var(--hds-layout-grid-column-fill-type), | ||
| minmax(calc(var(--hds-layout-grid-column-width-md) - var(--hds-layout-grid-column-gap)), 1fr) | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| // "lg" view, 1088px and above (large laptops and desktops) | ||
| .hds-layout-grid--column-width-lg { | ||
| @include hds-breakpoint-above("lg") { | ||
| grid-template-columns: repeat( | ||
| var(--hds-layout-grid-column-fill-type), | ||
| minmax(calc(var(--hds-layout-grid-column-width-lg) - var(--hds-layout-grid-column-gap)), 1fr) | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| // "xl" view, 1440px and above (extra large desktops) | ||
| .hds-layout-grid--column-width-xl { | ||
| @include hds-breakpoint-above("xl") { | ||
| grid-template-columns: repeat( | ||
| var(--hds-layout-grid-column-fill-type), | ||
| minmax(calc(var(--hds-layout-grid-column-width-xl) - var(--hds-layout-grid-column-gap)), 1fr) | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| // "xxl" view, 1920px and above (extra extra large desktops) | ||
| .hds-layout-grid--column-width-xxl { | ||
| @include hds-breakpoint-above("xxl") { | ||
| grid-template-columns: repeat( | ||
| var(--hds-layout-grid-column-fill-type), | ||
| minmax(calc(var(--hds-layout-grid-column-width-xxl) - var(--hds-layout-grid-column-gap)), 1fr) | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| // align | ||
|
|
||
| .hds-layout-grid--align-items-start { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,26 +2,43 @@ import type { TemplateOnlyComponent } from '@ember/component/template-only'; | |
|
|
||
| import ShwTextH2 from 'showcase/components/shw/text/h2'; | ||
| import ShwTextH3 from 'showcase/components/shw/text/h3'; | ||
| import ShwTextH4 from 'showcase/components/shw/text/h4'; | ||
| import ShwTextBody from 'showcase/components/shw/text/body'; | ||
| import ShwGrid from 'showcase/components/shw/grid'; | ||
| import ShwDivider from 'showcase/components/shw/divider'; | ||
|
|
||
| import CodeFragmentWithPlaceholderItems from '../code-fragments/with-placeholder-items'; | ||
|
|
||
| import { hash } from '@ember/helper'; | ||
|
|
||
| const SubSectionWidthManagement: TemplateOnlyComponent = <template> | ||
| <ShwTextH2>Column width management </ShwTextH2> | ||
| <ShwTextH2>Column width management</ShwTextH2> | ||
|
|
||
| <ShwTextH3>Column min width</ShwTextH3> | ||
| <ShwTextH3>No column min width or column width set (fluid grid layout)</ShwTextH3> | ||
|
|
||
| <ShwGrid | ||
| @columns={{1}} | ||
| @gap="1.5rem" | ||
| class="shw-layout-grid-example-tint-flex-items" | ||
| as |SG| | ||
| > | ||
| <SG.Item @label="No min width columns (min-width defaults to 0px)"> | ||
| <SG.Item @label="min-width defaults to 0px"> | ||
| <CodeFragmentWithPlaceholderItems /> | ||
| </SG.Item> | ||
| </ShwGrid> | ||
|
|
||
| <ShwDivider @level={{2}} /> | ||
|
|
||
| <ShwTextH3>Column min width (fluid grid layout)</ShwTextH3> | ||
|
|
||
| <ShwTextH4>With same number of items or more than column tracks</ShwTextH4> | ||
|
|
||
| <ShwGrid | ||
| @columns={{1}} | ||
| @gap="1.5rem" | ||
| class="shw-layout-grid-example-tint-flex-items" | ||
| as |SG| | ||
| > | ||
| <SG.Item @label="250px min width columns"> | ||
| <CodeFragmentWithPlaceholderItems @columnMinWidth="250px" /> | ||
| </SG.Item> | ||
|
|
@@ -33,7 +50,16 @@ const SubSectionWidthManagement: TemplateOnlyComponent = <template> | |
| <SG.Item @label="33.33% min width columns w/ 4 items"> | ||
| <CodeFragmentWithPlaceholderItems @columnMinWidth="33.33%" /> | ||
| </SG.Item> | ||
| </ShwGrid> | ||
|
|
||
| <ShwTextH4>With fewer items than column tracks</ShwTextH4> | ||
|
|
||
| <ShwGrid | ||
| @columns={{1}} | ||
| @gap="1.5rem" | ||
| class="shw-layout-grid-example-tint-flex-items" | ||
| as |SG| | ||
| > | ||
| <SG.Item @label="33.33% min width columns w/ 2 items"> | ||
| <CodeFragmentWithPlaceholderItems | ||
| @columnCount={{2}} | ||
|
|
@@ -51,18 +77,33 @@ const SubSectionWidthManagement: TemplateOnlyComponent = <template> | |
|
|
||
| <ShwDivider @level={{2}} /> | ||
|
|
||
| <ShwTextH3>Column width</ShwTextH3> | ||
| <ShwTextH3>Column width (fixed grid layout)</ShwTextH3> | ||
|
|
||
| <ShwTextH4>With same number of items or more than column tracks</ShwTextH4> | ||
|
|
||
| <ShwGrid | ||
| @columns={{1}} | ||
| @gap="1.5rem" | ||
| class="shw-layout-grid-example-tint-flex-items" | ||
| as |SG| | ||
| > | ||
| <SG.Item @label="33.33% width columns"> | ||
| <SG.Item @label="25% width columns w/ 4 items"> | ||
| <CodeFragmentWithPlaceholderItems @columnWidth="25%" /> | ||
| </SG.Item> | ||
|
|
||
| <SG.Item @label="33.33% width columns w/ 4 items"> | ||
| <CodeFragmentWithPlaceholderItems @columnWidth="33.33%" /> | ||
| </SG.Item> | ||
| </ShwGrid> | ||
|
|
||
| <ShwTextH4>With fewer items than column tracks</ShwTextH4> | ||
|
|
||
| <ShwGrid | ||
| @columns={{1}} | ||
| @gap="1.5rem" | ||
| class="shw-layout-grid-example-tint-flex-items" | ||
| as |SG| | ||
| > | ||
| <SG.Item @label="33.33% width columns w/ 2 items"> | ||
| <CodeFragmentWithPlaceholderItems | ||
| @columnCount={{2}} | ||
|
|
@@ -76,6 +117,79 @@ const SubSectionWidthManagement: TemplateOnlyComponent = <template> | |
| @columnWidth="33.33%" | ||
| /> | ||
| </SG.Item> | ||
|
|
||
| <SG.Item @label="50% width columns w/ 4 items"> | ||
| <CodeFragmentWithPlaceholderItems @columnCount={{4}} @columnWidth="50%" /> | ||
| </SG.Item> | ||
| </ShwGrid> | ||
|
|
||
| <ShwDivider @level={{2}} /> | ||
|
|
||
| <ShwTextH4>With responsive column widths</ShwTextH4> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do you think we should have these in a "frameless" page so we can test them? the downside would be that we can't take Percy snapshots of them so they would not be covered for visual regression testing alternatively, we could create a shared code snippet, to be used here but also in a frameless page, and we could put a link to it like I'm doing here: https://hds-showcase-git-project-solar-phase-1-main-fe-1ffc6c-hashicorp.vercel.app/foundations/theming#demo (see the link under the "demo" section) |
||
|
|
||
| <ShwTextBody>With same number of items or more than column tracks</ShwTextBody> | ||
|
|
||
| <ShwGrid | ||
| @columns={{1}} | ||
| @gap="1.5rem" | ||
| class="shw-layout-grid-example-tint-flex-items" | ||
| as |SG| | ||
| > | ||
| <SG.Item | ||
| @label="sm view = 1 column, md view = 2 columns, lg view = 3 columns, xl view = 4 columns, xxl view = 5 columns" | ||
| > | ||
| <CodeFragmentWithPlaceholderItems | ||
| @columnCount={{5}} | ||
| @columnWidth={{hash sm="100%" md="50%" lg="33.33%" xl="25%" xxl="20%"}} | ||
| /> | ||
| </SG.Item> | ||
|
|
||
| <SG.Item @label="sm view = 1 column, md & all other views = 3 columns"> | ||
| <CodeFragmentWithPlaceholderItems | ||
| @columnCount={{5}} | ||
| @columnWidth={{hash sm="100%" md="33.33%"}} | ||
| /> | ||
| </SG.Item> | ||
|
|
||
| <SG.Item | ||
| @label="sm view = 1 column, md & all other views except xxl = 3 columns, xxl = 5 columns" | ||
| > | ||
| <CodeFragmentWithPlaceholderItems | ||
| @columnCount={{5}} | ||
| @columnWidth={{hash sm="100%" md="33.33%" xxl="20%"}} | ||
| /> | ||
| </SG.Item> | ||
|
|
||
| <SG.Item | ||
| @label="sm view & all other views except xxl = 2 columns, xxl = 5 columns" | ||
| > | ||
| <CodeFragmentWithPlaceholderItems | ||
| @columnCount={{5}} | ||
| @columnWidth={{hash sm="50%" xxl="20%"}} | ||
| /> | ||
| </SG.Item> | ||
| </ShwGrid> | ||
|
|
||
| <ShwTextBody>With fewer items than column tracks</ShwTextBody> | ||
|
|
||
| <ShwGrid | ||
| @columns={{1}} | ||
| @gap="1.5rem" | ||
| class="shw-layout-grid-example-tint-flex-items" | ||
| as |SG| | ||
| > | ||
| <SG.Item @label="sm=50% md=33.33% lg=25% xl=20% xxl=16.67%"> | ||
| <CodeFragmentWithPlaceholderItems | ||
| @columnCount={{1}} | ||
| @columnWidth={{hash | ||
| sm="50%" | ||
| md="33.33%" | ||
| lg="25%" | ||
| xl="20%" | ||
| xxl="16.67%" | ||
| }} | ||
| /> | ||
| </SG.Item> | ||
| </ShwGrid> | ||
|
|
||
| <ShwDivider @level={{2}} /> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[issue?] should this be required, with the current mobile-first approach? otherwise, with this code
this happens:

alternatively we could use somehow the width/min-width arguments, but I suspect it will make the API more complex
see what you find out, what works better