Skip to content

Commit 61aed39

Browse files
authored
Merge pull request #2497 from Akshat55/css-grid-performance
fix: Improve grid performance by preventing unnecessary change detection
2 parents b6edd11 + d6638b2 commit 61aed39

File tree

10 files changed

+733
-206
lines changed

10 files changed

+733
-206
lines changed

.storybook/preview.scss

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,147 @@ html {
55
height: 100%;
66
@include styles.theme(styles.$white);
77
}
8+
9+
/*
10+
* Story batch styling, we are wrapping the storybook decorator function to ensure
11+
* styles do not leak to other components
12+
*
13+
* To enable this in stories, pass a component wrapper decorator function in Meta as such:
14+
* ```
15+
* componentWrapperDecorator((story) => `<div class="css-grid-story">${story}</div>`)
16+
* ```
17+
*
18+
* Styling retrieved from:
19+
* https://github.com/carbon-design-system/carbon/tree/main/packages/react/src/components/Grid
20+
*/
21+
// Css Grid
22+
.css-grid-story {
23+
.cds--css-grid {
24+
background-color: styles.$blue-20;
25+
outline: 1px dashed styles.$blue-40;
26+
}
27+
28+
.cds--css-grid p {
29+
@include styles.type-style('code-02');
30+
}
31+
32+
.cds--css-grid p:first-of-type {
33+
margin-top: 0;
34+
}
35+
36+
.cds--css-grid.cds--css-grid--narrow {
37+
background-color: #d6f9f9;
38+
outline: 1px dashed styles.$green-40;
39+
}
40+
41+
.cds--css-grid.cds--css-grid--condensed {
42+
background-color: styles.$purple-10;
43+
outline: 1px dashed styles.$purple-40;
44+
}
45+
46+
.cds--subgrid {
47+
outline: 1px solid black;
48+
padding-top: 2rem;
49+
padding-bottom: 2rem;
50+
position: relative;
51+
background: #eef4ff;
52+
}
53+
54+
.cds--css-grid,
55+
.cds--subgrid--wide {
56+
--grid-mode-color: #97c1ff;
57+
}
58+
59+
.cds--css-grid--narrow,
60+
.cds--subgrid--narrow {
61+
--grid-mode-color: #20d5d2;
62+
background: styles.$green-10;
63+
}
64+
65+
.cds--css-grid--condensed,
66+
.cds--subgrid--condensed {
67+
--grid-mode-color: #bb8eff;
68+
background: styles.$purple-10;
69+
}
70+
71+
.cds--subgrid--narrow {
72+
background: #d6f9f9;
73+
}
74+
75+
.cds--subgrid--condensed {
76+
background: #f7f2ff;
77+
}
78+
79+
.cds--subgrid::before {
80+
@include styles.type-style('code-01');
81+
position: absolute;
82+
inset-block-start: 0;
83+
inset-inline-start: 0;
84+
display: block;
85+
content: 'subgrid';
86+
background: var(--grid-mode-color, #97c1ff);
87+
color: styles.$black;
88+
padding: 0.125rem 0.25rem;
89+
}
90+
91+
.cds--css-grid-column {
92+
--border-color: #97c1ff;
93+
94+
background: white;
95+
box-shadow: 0 0 0 1px var(--border-color);
96+
min-height: 80px;
97+
}
98+
99+
.cds--css-grid--narrow .cds--css-grid-column,
100+
.cds--subgrid--narrow .cds--css-grid-column {
101+
--border-color: #20d5d2;
102+
}
103+
104+
.cds--css-grid--condensed .cds--css-grid-column,
105+
.cds--subgrid--condensed .cds--css-grid-column {
106+
--border-color: #bb8eff;
107+
}
108+
}
109+
110+
// Flex Grid
111+
.flex-grid-story {
112+
.cds--grid [class*='col'] {
113+
background-color: styles.$blue-20;
114+
outline: 1px dashed styles.$blue-40;
115+
min-height: 80px;
116+
}
117+
118+
.inside {
119+
background-color: styles.$blue-10;
120+
height: 100%;
121+
width: inherit;
122+
}
123+
124+
.cds--grid--condensed,
125+
.cds--row--condensed {
126+
background-color: styles.$blue-20;
127+
color: styles.$gray-10;
128+
}
129+
130+
.cds--grid--condensed .inside,
131+
.cds--row--condensed .inside {
132+
background-color: styles.$teal-10;
133+
}
134+
135+
.cds--grid--condensed [class*='col'],
136+
.cds--row--condensed [class*='col'] {
137+
background-color: styles.$teal-20;
138+
outline: 1px dashed styles.$teal-40;
139+
}
140+
141+
.cds--grid--narrow .inside,
142+
.cds--row--narrow .inside {
143+
background-color: styles.$teal-10;
144+
}
145+
146+
.cds--grid--narrow [class*='col'],
147+
.cds--row--narrow [class*='col'] {
148+
background-color: styles.$teal-20;
149+
outline: 1px dashed styles.$teal-40;
150+
}
151+
}

src/grid/column.directive.ts

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import {
2+
Directive,
3+
HostBinding,
4+
Input,
5+
OnChanges,
6+
OnDestroy,
7+
OnInit,
8+
Optional
9+
} from "@angular/core";
10+
import { Subscription } from "rxjs";
11+
import { GridService } from "./grid.service";
12+
13+
@Directive({
14+
selector: "[cdsCol], [ibmCol]"
15+
})
16+
export class ColumnDirective implements OnInit, OnChanges, OnDestroy {
17+
@HostBinding("class")
18+
get columnClasses(): string {
19+
return this._columnClasses.join(" ");
20+
}
21+
set columnClasses(classes: string) {
22+
this._columnClasses = classes.split(" ");
23+
}
24+
25+
@Input() class = "";
26+
27+
/**
28+
* Defines columns width for specified breakpoint
29+
* Accepts the following formats:
30+
* - {[breakpoint]: number}
31+
* - {[breakpoint]: "auto"} - css only
32+
* - {[breakpoint]: {[start|end]: number}} - css only
33+
*
34+
* Example:
35+
* <div cdsCol [columnNumbers]={md: 3, lg: 4}></div>
36+
*/
37+
@Input() columnNumbers = {};
38+
39+
/**
40+
* Defines columns offset, which increases the left margin of the column.
41+
* This field will only work with flexbox grid.
42+
*
43+
* Accepts the following formats:
44+
* - {[breakpoint]: number}
45+
*
46+
* Example:
47+
* <div cdsCol [offsets]={md: 3, lg: 4}></div>
48+
*/
49+
@Input() offsets = {};
50+
51+
/**
52+
* Set to `true` to use css grid column hang class
53+
* This will only work when `isCss` property is set to true
54+
*
55+
* Useful when trying to align content across css grid/subgrid
56+
*/
57+
@Input() columnHang = false;
58+
59+
protected _columnClasses: string[] = [];
60+
61+
private isCssGrid = false;
62+
private subscription = new Subscription();
63+
64+
constructor(@Optional() private gridService: GridService) {}
65+
66+
ngOnInit() {
67+
if (this.gridService) {
68+
this.gridService.gridObservable.subscribe((isCssGrid: boolean) => {
69+
this.isCssGrid = isCssGrid;
70+
this.updateColumnClasses();
71+
});
72+
} else {
73+
this.updateColumnClasses();
74+
}
75+
}
76+
77+
ngOnChanges() {
78+
this.updateColumnClasses();
79+
}
80+
81+
/**
82+
* Unsubscribe from subscription
83+
*/
84+
ngOnDestroy() {
85+
this.subscription.unsubscribe();
86+
}
87+
88+
private updateColumnClasses() {
89+
try {
90+
this._columnClasses = [];
91+
const columnKeys = Object.keys(this.columnNumbers);
92+
93+
// Assign classes based on the type of grid used.
94+
if (this.isCssGrid) {
95+
// Default css grid class
96+
this._columnClasses.push("cds--css-grid-column");
97+
if (this.columnHang) {
98+
this._columnClasses.push("cds--grid-column-hang");
99+
}
100+
101+
columnKeys.forEach(key => {
102+
/**
103+
* Passing in `auto` to a breakpoint as such: {'md': 'auto'}
104+
* will assign the element which will automatically determine the width of the column
105+
* for the breakpoint passed
106+
*/
107+
if (this.columnNumbers[key] === "auto") {
108+
this._columnClasses.push(`cds--${key}:col-span-auto`);
109+
} else if (typeof this.columnNumbers[key] === "object") {
110+
/**
111+
* In css grid, objects can be passed to the keys in the following format:
112+
* {'md': {'start': 3}}
113+
*
114+
* These objects are used to position the column
115+
*/
116+
if (this.columnNumbers[key]["start"]) {
117+
// col-start is simular equivalent of flex offset
118+
this._columnClasses.push(`cds--${key}:col-start-${this.columnNumbers[key].start}`);
119+
}
120+
if (this.columnNumbers[key]["end"]) {
121+
this._columnClasses.push(`cds--${key}:col-end-${this.columnNumbers[key].end}`);
122+
}
123+
if (this.columnNumbers[key]["span"]) {
124+
this._columnClasses.push(`cds--${key}:col-span-${this.columnNumbers[key].span}`);
125+
}
126+
} else {
127+
this._columnClasses.push(`cds--${key}:col-span-${this.columnNumbers[key]}`);
128+
}
129+
});
130+
131+
Object.keys(this.offsets).forEach(key => {
132+
this._columnClasses.push(`cds--${key}:col-start${this.offsets[key] + 1}`);
133+
});
134+
} else {
135+
// Set column classes for flex grid
136+
if (columnKeys.length <= 0) {
137+
this._columnClasses.push("cds--col");
138+
}
139+
140+
columnKeys.forEach(key => {
141+
if (this.columnNumbers[key] === "nobreak") {
142+
this._columnClasses.push(`cds--col-${key}`);
143+
} else {
144+
this._columnClasses.push(`cds--col-${key}-${this.columnNumbers[key]}`);
145+
}
146+
});
147+
148+
Object.keys(this.offsets).forEach(key => {
149+
this._columnClasses.push(`cds--offset-${key}-${this.offsets[key]}`);
150+
});
151+
}
152+
} catch (err) {
153+
console.error(`Malformed \`offsets\` or \`columnNumbers\`: ${err}`);
154+
}
155+
156+
/**
157+
* Append the classes passed so they aren't overriden when we set the column classes
158+
* from host binding
159+
*/
160+
if (this.class) {
161+
this._columnClasses.push(this.class);
162+
}
163+
}
164+
}

0 commit comments

Comments
 (0)