Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 76 additions & 7 deletions packages/components/src/components/hds/layout/grid/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Contributor

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

<CodeFragmentWithPlaceholderItems
  @columnCount={{5}}
  @columnWidth={{hash lg="33.33%" xl="25%" xxl="20%"}}
/>

this happens:
Image

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

md?: string;
lg?: string;
xl?: string;
xxl?: string;
};

export interface HdsLayoutGridSignature {
Args: {
tag?: AvailableTagNames;
columnMinWidth?: string;
columnWidth?: string;
columnWidth?: string | ResponsiveColumnWidths;
align?: HdsLayoutGridAligns;
gap?: HdsLayoutGridGaps | [HdsLayoutGridGaps, HdsLayoutGridGaps];
};
Expand Down Expand Up @@ -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
Expand All @@ -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';
Copy link
Contributor

Choose a reason for hiding this comment

The 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 (auto-fit and auto-fill) and the conditions for them, so could we simply use CSS classes assigned in the get classNames() getter below, using this same logic?

} 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;
Expand All @@ -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(' ');
}
}
64 changes: 62 additions & 2 deletions packages/components/src/styles/components/layout/grid.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);

Expand All @@ -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(
Copy link
Contributor

Choose a reason for hiding this comment

The 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 sm is not set then there is no intrinsic width for the columns at this size

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 {
@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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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}}
Expand All @@ -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}}
Expand All @@ -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>
Copy link
Contributor

Choose a reason for hiding this comment

The 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}} />
Expand Down