@orangesk/orange-design-system 2.0.0-beta.43 → 2.0.0-beta.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/components/BodyBanner/style.css +1 -1
- package/build/components/BodyBanner/style.css.map +1 -1
- package/build/components/Breadcrumbs/style.css +1 -1
- package/build/components/Breadcrumbs/style.css.map +1 -1
- package/build/components/Carousel/style.css +1 -1
- package/build/components/Carousel/style.css.map +1 -1
- package/build/components/Expander/style.css +1 -1
- package/build/components/Expander/style.css.map +1 -1
- package/build/components/Footer/style.css +1 -1
- package/build/components/Footer/style.css.map +1 -1
- package/build/components/Grid/style.css +1 -1
- package/build/components/Grid/style.css.map +1 -1
- package/build/components/Megamenu/style.css +1 -1
- package/build/components/Megamenu/style.css.map +1 -1
- package/build/components/Tag/style.css +1 -1
- package/build/components/Tag/style.css.map +1 -1
- package/build/components/index.js +1 -1
- package/build/components/index.js.map +1 -1
- package/build/components/tsconfig.tsbuildinfo +1 -1
- package/build/components/types/index.d.ts +4 -2
- package/build/components/types/src/components/Carousel/Carousel.static.d.ts +7 -0
- package/build/components/types/src/components/Expander/Expander.d.ts +2 -0
- package/build/components/types/src/components/Grid/Grid.d.ts +2 -2
- package/build/lib/base.css +1 -1
- package/build/lib/base.css.map +1 -1
- package/build/lib/components.css +1 -1
- package/build/lib/components.css.map +1 -1
- package/build/lib/footer.css +1 -1
- package/build/lib/footer.css.map +1 -1
- package/build/lib/megamenu.css +1 -1
- package/build/lib/megamenu.css.map +1 -1
- package/build/lib/scripts.js +1 -1
- package/build/lib/scripts.js.map +1 -1
- package/build/lib/style.css +1 -1
- package/build/lib/style.css.map +1 -1
- package/build/search-index.json +2 -2
- package/package.json +6 -6
- package/src/components/BodyBanner/styles/mixins.scss +2 -12
- package/src/components/Carousel/Carousel.static.ts +109 -57
- package/src/components/Carousel/styles/mixins.scss +1 -1
- package/src/components/Carousel/tests/Carousel.static.test.jsx +91 -5
- package/src/components/Expander/Expander.tsx +4 -0
- package/src/components/Expander/styles/style.scss +12 -0
- package/src/components/Expander/tests/Expander.conformance.test.jsx +4 -0
- package/src/components/Expander/tests/Expander.unit.test.jsx +9 -0
- package/src/components/Grid/Grid.tsx +5 -2
- package/src/components/Grid/styles/config.scss +3 -2
- package/src/components/Grid/tests/Grid.unit.test.jsx +40 -10
- package/src/components/Tag/styles/config.scss +5 -1
- package/src/components/Tag/styles/mixins.scss +2 -1
- package/src/styles/base/globals.scss +1 -0
- package/src/styles/export/base.js +1 -1
- package/src/styles/tokens/base.scss +1 -1
package/build/search-index.json
CHANGED
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
83
|
"href": "/components/expander",
|
|
84
|
-
"content": "import React from \"react\"; Expander The Expander component is used to hide and show content. It can be used to reduce the amount of content visible on the page by default, making it easier for users to find what they are looking for. Basic Usage {This is the content that will be hidden until the user clicks the summary text.} Changing the summary text Add the summaryOpened prop to change the text displayed when the content is visible. {This is the content that will be hidden until the user clicks the summary text.} Custom Icon Use the renderSummary prop to customize the summary icon. Icon is rotating thanks to data-summary-icon attribute, which needs to be present on the icon. Custom Summary text Use the renderSummary and renderSummaryOpened props to customize the summary text. Icons do not rotate in this case. Toggle Group Use the toggleGroup prop to synchronize multiple expanders together. When one expander in the group is toggled, all others with the same toggleGroup value will toggle together. Used mostly in Product cards. Full Width Use the isFullWidth prop to make the expander take the full width of its container. Accessibility The Expander component uses the native <details> and <summary> HTML elements, which provide built-in accessibility support. Screen readers automatically announce the expanded/collapsed state without requiring manual ARIA attributes. The native <details> element ensures: - Keyboard navigation works automatically (Enter/Space to toggle) - Screen readers announce the state changes - Focus management is handled by the browser Important: Ensure that the summary prop provides a clear and concise description of the content that will be revealed. Use summaryOpened to provide appropriate text for the expanded state to improve user experience. API React Props | Prop | Type | Default | Description | | --------------------- | ------------------------------------------- |
|
|
84
|
+
"content": "import React from \"react\"; Expander The Expander component is used to hide and show content. It can be used to reduce the amount of content visible on the page by default, making it easier for users to find what they are looking for. Basic Usage {This is the content that will be hidden until the user clicks the summary text.} Changing the summary text Add the summaryOpened prop to change the text displayed when the content is visible. {This is the content that will be hidden until the user clicks the summary text.} Custom Icon Use the renderSummary prop to customize the summary icon. Icon is rotating thanks to data-summary-icon attribute, which needs to be present on the icon. Custom Summary text Use the renderSummary and renderSummaryOpened props to customize the summary text. Icons do not rotate in this case. Toggle Group Use the toggleGroup prop to synchronize multiple expanders together. When one expander in the group is toggled, all others with the same toggleGroup value will toggle together. Used mostly in Product cards. Full Width Use the isFullWidth prop to make the expander take the full width of its container. Content placement Use the placement prop with value top to render opened content above trigger. Accessibility The Expander component uses the native <details> and <summary> HTML elements, which provide built-in accessibility support. Screen readers automatically announce the expanded/collapsed state without requiring manual ARIA attributes. The native <details> element ensures: - Keyboard navigation works automatically (Enter/Space to toggle) - Screen readers announce the state changes - Focus management is handled by the browser Important: Ensure that the summary prop provides a clear and concise description of the content that will be revealed. Use summaryOpened to provide appropriate text for the expanded state to improve user experience. API React Props | Prop | Type | Default | Description | | --------------------- | ------------------------------------------- | ---------- | -------------------------------------------------------- | | summary | string | - | Trigger text when collapsed | | summaryOpened | string | - | Trigger text when expanded | | renderSummary | (props: ExpanderProps) => React.ReactNode | - | Custom summary renderer for collapsed state | | renderSummaryOpened | (props: ExpanderProps) => React.ReactNode | - | Custom summary renderer for expanded state | | isFullWidth | boolean | - | Expander takes full width of its container | | placement | \"top\" \\| \"bottom\" | \"bottom\" | Position of trigger relative to content when opened | | toggleGroup | string | - | Group identifier for syncing multiple expanders together | | className | string | - | Additional CSS classes | | children | React.ReactNode | - | Content to show/hide | JS module reference All elements with data-expander are initialized automatically. Custom initialization example: Expander Methods | Method | Type | Description | | --------- | ------ | ---------------------------------------------------------- | | toggle | func | Toggle the expander open/closed state | | destroy | func | Destroy the instance - removes all listeners | | update | func | Re-initialize the component (useful after content changes) |"
|
|
85
85
|
},
|
|
86
86
|
{
|
|
87
87
|
"href": "/components/feature-accordion",
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
},
|
|
166
166
|
{
|
|
167
167
|
"href": "/components/grid",
|
|
168
|
-
"content": "import spaces from \"@/styles/export/space.js\"; Grid The main function of the grid is to distribute items to rows and columns. Our grid has a maximum of 12 columns. Each column can have a custom size defined by breakpoints. Breakpoints Grid uses the mobile-first definition of breakpoints, which means that smallest breakpoint defines default size. Columns You can have a maximum of 12 columns in one row. A default column takes all the available space. <code>(default) 12</code> <code>6</code> <hr /> <code>auto</code> <code>4</code> <code>6</code> <hr /> <code>fill</code> <code>4</code> <code>6</code> <hr /> <code>shrink</code> <code>6</code> Resizing There are two ways of resizing a column. 1. .gridcol--[size] where size is a number 1-12 or one of [shrink/fill/auto] 2. .gridcol--[breakpoint]-[size] where breakpoint is used as a minimum display width to change the column size > XS breakpoint size > > There is no need to specify the xs breakpoint size because it's a default by definition of mobile-first philosophy. What is shrink/fill/auto? - shrink column takes the least amount of space available - fill column takes all the space available and does not shrink to accomodate other columns - auto takes all available space, also tries to shrink to fit into one row. This is the same as .gridcol. <code>(default) 12</code> <code>4</code> <code>shrink</code> <code>fill</code> <code>auto</code> Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Available combinations You can use combinations of different size types for one column at once. Available types are [1-12/shrink/fill/auto] for every breakpoint. <code>(default) 12, sm-9, md-6, lg-fill</code> <code>(default) 12, sm-3, md-6, lg-fill</code> Justify content height Grid with justified content height uses flexbox with an attempt to equalize height of column children <div className=\"surface-subtle\"> <p>Children of these columns are the same height</p> </div> <div className=\"surface-subtle\"> <p>The most high column dictates the height of the row</p> </div> <div className=\"surface-subtle\"> <p>Subsequent rows remain unaffected</p> </div> Container alignment Horizontal Vertical Children alignment Vertical Column gap size Add larger space between grid columns, use smaller gap, or remove them completely. There are two ways of adding a column gap: 1. .grid-column-gap--[size] where size is one of [none/small/default/large] 2. .grid-column-gap--[breakpoint]-[size] where breakpoint is used as a minimum display width to change the gap size {[\"large\", \"default\", \"small\", \"none\"].map((size, index) => ( <div key={index.toString()}> <h3 className=\"bold\">Column gap size {JSON.stringify(size)}</h3> {[1, 2, 3].map((key) => ( <code>4</code> ))} </div> ))} Row gap size Row gap helps to maintain space between grid rows and fixes unwanted spacing problems on the bottom of grid's last row elements by removing their content margins. There are two ways of adding a row gap: 1. .grid-row-gap--[size] where size is one of [small/medium/large/xlarge] 2. .grid-row-gap--[breakpoint]-[size] where breakpoint is used as a minimum display width to change the gap size {[\"small\", \"medium\", \"large\", \"xlarge\"].map((size, index, array) => ( <h3 className=\"mb-2\">Row gap size {JSON.stringify(size)}</h3> {[1, 2, 3, 4, 5, 6].map((key) => ( <code>6</code> ))} ))} Advanced Examples Complex Layout with Alignment <code>top aligned</code> <code>auto middle</code> <code>fill bottom</code> <code>stretch</code> Semantic HTML Tags <code>main content</code> <code>sidebar</code> All Column Sizes (1-12) {Array.from({ length: 12 }, (, i) => ( <code>{i + 1}</code> ))} API Grid React Props | Name | Type | Default | Description | | --------------- |
|
|
168
|
+
"content": "import spaces from \"@/styles/export/space.js\"; Grid The main function of the grid is to distribute items to rows and columns. Our grid has a maximum of 12 columns. Each column can have a custom size defined by breakpoints. Breakpoints Grid uses the mobile-first definition of breakpoints, which means that smallest breakpoint defines default size. Columns You can have a maximum of 12 columns in one row. A default column takes all the available space. <code>(default) 12</code> <code>6</code> <hr /> <code>auto</code> <code>4</code> <code>6</code> <hr /> <code>fill</code> <code>4</code> <code>6</code> <hr /> <code>shrink</code> <code>6</code> Resizing There are two ways of resizing a column. 1. .gridcol--[size] where size is a number 1-12 or one of [shrink/fill/auto] 2. .gridcol--[breakpoint]-[size] where breakpoint is used as a minimum display width to change the column size > XS breakpoint size > > There is no need to specify the xs breakpoint size because it's a default by definition of mobile-first philosophy. What is shrink/fill/auto? - shrink column takes the least amount of space available - fill column takes all the space available and does not shrink to accomodate other columns - auto takes all available space, also tries to shrink to fit into one row. This is the same as .gridcol. <code>(default) 12</code> <code>4</code> <code>shrink</code> <code>fill</code> <code>auto</code> Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Available combinations You can use combinations of different size types for one column at once. Available types are [1-12/shrink/fill/auto] for every breakpoint. <code>(default) 12, sm-9, md-6, lg-fill</code> <code>(default) 12, sm-3, md-6, lg-fill</code> Justify content height Grid with justified content height uses flexbox with an attempt to equalize height of column children <div className=\"surface-subtle\"> <p>Children of these columns are the same height</p> </div> <div className=\"surface-subtle\"> <p>The most high column dictates the height of the row</p> </div> <div className=\"surface-subtle\"> <p>Subsequent rows remain unaffected</p> </div> Container alignment Horizontal Vertical Children alignment Vertical Column gap size Add larger space between grid columns, use smaller gap, or remove them completely. There are two ways of adding a column gap: 1. .grid-column-gap--[size] where size is one of [none/small/default/large/xlarge] 2. .grid-column-gap--[breakpoint]-[size] where breakpoint is used as a minimum display width to change the gap size {[\"xlarge\", \"large\", \"default\", \"small\", \"none\"].map((size, index) => ( <div key={index.toString()}> <h3 className=\"bold\">Column gap size {JSON.stringify(size)}</h3> {[1, 2, 3].map((key) => ( <code>4</code> ))} </div> ))} <h3 className=\"mb-2\"> Responsive column gap size {'{\"xs\":\"small\",\"md\":\"xlarge\"}'} </h3> {[1, 2, 3].map((key) => ( <code>4</code> ))} Row gap size Row gap helps to maintain space between grid rows and fixes unwanted spacing problems on the bottom of grid's last row elements by removing their content margins. There are two ways of adding a row gap: 1. .grid-row-gap--[size] where size is one of [none/xsmall/small/medium/large/xlarge] 2. .grid-row-gap--[breakpoint]-[size] where breakpoint is used as a minimum display width to change the gap size {[\"none\", \"xsmall\", \"small\", \"medium\", \"large\", \"xlarge\"].map( (size, index, array) => ( <h3 className=\"mb-2\">Row gap size {JSON.stringify(size)}</h3> {[1, 2, 3, 4, 5, 6].map((key) => ( <code>6</code> ))} ), )} <h3 className=\"mb-2\"> Responsive row gap size {'{\"xs\":\"medium\",\"md\":\"xlarge\"}'} </h3> {[1, 2, 3, 4, 5, 6].map((key) => ( <code>6</code> ))} Advanced Examples Complex Layout with Alignment <code>top aligned</code> <code>auto middle</code> <code>fill bottom</code> <code>stretch</code> Semantic HTML Tags <code>main content</code> <code>sidebar</code> All Column Sizes (1-12) {Array.from({ length: 12 }, (, i) => ( <code>{i + 1}</code> ))} API Grid React Props | Name | Type | Default | Description | | --------------- | ----------------------------------------------------------------------------------------- | ------- | ------------------------------------------ | | vAlign | \"stretch\" \\| \"start\" \\| \"end\" \\| \"center\" | - | Vertical alignment of grid items. | | hAlign | \"end\" \\| \"center\" \\| \"space-around\" \\| \"space-between\" | - | Horizontal alignment of grid items. | | tag | React.ElementType | \"div\" | Rendered HTML element. | | justifyHeight | boolean | false | Whether grid should justify height. | | rowGapSize | \"none\" \\| \"xsmall\" \\| \"small\" \\| \"medium\" \\| \"large\" \\| \"xlarge\" \\| Record<string, ...> | - | Row gap size, can be responsive object. | | columnGapSize | \"none\" \\| \"small\" \\| \"default\" \\| \"large\" \\| \"xlarge\" \\| Record<string, ...> | - | Column gap size, can be responsive object. | | className | string | - | Additional CSS classes. | GridCol React Props | Name | Type | Default | Description | | ----------- | ------------------- | ------- | --------------------------------------------- | | size | ResponsiveSize | - | Column size (1-12), can be responsive object. | | offset | ResponsiveSize | - | Column offset, can be responsive object. | | tag | React.ElementType | \"div\" | Rendered HTML element. | | className | string | - | Additional CSS classes. |"
|
|
169
169
|
},
|
|
170
170
|
{
|
|
171
171
|
"href": "/components/hero",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orangesk/orange-design-system",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.44",
|
|
4
4
|
"private": false,
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.x"
|
|
@@ -109,10 +109,10 @@
|
|
|
109
109
|
"@types/react-dom": "19.2.3",
|
|
110
110
|
"@types/wnumb": "^1.2.3",
|
|
111
111
|
"@vitejs/plugin-react": "6.0.1",
|
|
112
|
-
"@vitest/browser": "^4.1.
|
|
113
|
-
"@vitest/browser-playwright": "^4.1.
|
|
114
|
-
"@vitest/coverage-v8": "^4.1.
|
|
115
|
-
"@vitest/ui": "^4.1.
|
|
112
|
+
"@vitest/browser": "^4.1.5",
|
|
113
|
+
"@vitest/browser-playwright": "^4.1.5",
|
|
114
|
+
"@vitest/coverage-v8": "^4.1.5",
|
|
115
|
+
"@vitest/ui": "^4.1.5",
|
|
116
116
|
"canvas": "^3.2.3",
|
|
117
117
|
"fs-extra": "^11.3.4",
|
|
118
118
|
"glob": "13.0.6",
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
"sass": "^1.99.0",
|
|
131
131
|
"svg-sprite": "^2.0.4",
|
|
132
132
|
"typescript": "6.0.3",
|
|
133
|
-
"vitest": "^4.1.
|
|
133
|
+
"vitest": "^4.1.5",
|
|
134
134
|
"vitest-axe": "^0.1.0",
|
|
135
135
|
"vitest-browser-react": "^2.2.0"
|
|
136
136
|
},
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
width: 100%;
|
|
43
43
|
display: flex;
|
|
44
44
|
padding: space.get("large");
|
|
45
|
-
gap: convert.to-rem(
|
|
45
|
+
gap: convert.to-rem(60px);
|
|
46
46
|
|
|
47
47
|
> div:not(.body-banner__button) {
|
|
48
48
|
flex: 1;
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
@include breakpoint.get("lg", "down") {
|
|
57
57
|
display: flex;
|
|
58
58
|
flex-direction: column;
|
|
59
|
+
gap: convert.to-rem(30px);
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
@include breakpoint.get("sm", "down") {
|
|
@@ -82,13 +83,7 @@
|
|
|
82
83
|
@mixin button {
|
|
83
84
|
display: flex;
|
|
84
85
|
align-items: center;
|
|
85
|
-
padding: 0 0 0 space.get("xlarge");
|
|
86
86
|
margin: 0 !important;
|
|
87
|
-
|
|
88
|
-
@include breakpoint.get("lg", "down") {
|
|
89
|
-
padding: 0;
|
|
90
|
-
margin-top: space.get("small") !important;
|
|
91
|
-
}
|
|
92
87
|
}
|
|
93
88
|
|
|
94
89
|
@mixin large {
|
|
@@ -113,9 +108,4 @@
|
|
|
113
108
|
display: block;
|
|
114
109
|
}
|
|
115
110
|
}
|
|
116
|
-
|
|
117
|
-
.body-banner__button {
|
|
118
|
-
padding: 0;
|
|
119
|
-
margin-top: space.get("small") !important;
|
|
120
|
-
}
|
|
121
111
|
}
|
|
@@ -61,6 +61,7 @@ export const defaultConfig: SwiperOptions = {
|
|
|
61
61
|
},
|
|
62
62
|
a11y: {
|
|
63
63
|
enabled: true,
|
|
64
|
+
scrollOnFocus: false,
|
|
64
65
|
prevSlideMessage: "Predchádzajúci snímok",
|
|
65
66
|
nextSlideMessage: "Nasledujúci snímok",
|
|
66
67
|
paginationBulletMessage: "Prejsť na snímok {index}",
|
|
@@ -97,6 +98,76 @@ export default class Carousel {
|
|
|
97
98
|
private resizeRafId?: number;
|
|
98
99
|
private boundWindowResizeHandler: () => void;
|
|
99
100
|
private bleedResizeHandler?: () => void;
|
|
101
|
+
private static readonly OVERFLOW_EPSILON_PX = 1;
|
|
102
|
+
|
|
103
|
+
private getViewportWrapper(): HTMLElement | null {
|
|
104
|
+
return this.element.querySelector(
|
|
105
|
+
`.${CLASS_VIEWPORT_WRAPPER}`,
|
|
106
|
+
) as HTMLElement | null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private getSlidesPerView(): number {
|
|
110
|
+
return Number(this.instance?.params?.slidesPerView) || 1;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private setScrollbarVisible(visible: boolean): void {
|
|
114
|
+
const scrollbarEl = this.element.querySelector(
|
|
115
|
+
SELECTOR_SCROLLBAR,
|
|
116
|
+
) as HTMLElement | null;
|
|
117
|
+
if (scrollbarEl) {
|
|
118
|
+
scrollbarEl.style.display = visible ? "" : "none";
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private getSlidesContentWidth(): number {
|
|
123
|
+
if (!this.instance) {
|
|
124
|
+
return this.track?.scrollWidth || 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const slides = Array.from(this.instance.slides) as HTMLElement[];
|
|
128
|
+
const widthsSum = slides.reduce((sum, slide) => sum + slide.offsetWidth, 0);
|
|
129
|
+
const spaceBetween = Number(this.instance.params?.spaceBetween) || 0;
|
|
130
|
+
const gaps = Math.max(slides.length - 1, 0) * spaceBetween;
|
|
131
|
+
const measuredWidth = widthsSum + gaps;
|
|
132
|
+
|
|
133
|
+
// Fallback for early lifecycle/test DOM where slides can report 0 width.
|
|
134
|
+
if (widthsSum <= 0 || measuredWidth <= 0) {
|
|
135
|
+
return this.track?.scrollWidth || 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return measuredWidth;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private getOverflowWidth(viewportWrapper?: HTMLElement): number {
|
|
142
|
+
const isBleedRight = this.element.classList.contains(CLASS_BLEED_RIGHT);
|
|
143
|
+
const containerWidth = isBleedRight
|
|
144
|
+
? viewportWrapper?.clientWidth || 0
|
|
145
|
+
: this.viewport?.clientWidth || 0;
|
|
146
|
+
|
|
147
|
+
if (containerWidth <= 0) {
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const contentWidth = this.getSlidesContentWidth();
|
|
152
|
+
|
|
153
|
+
return contentWidth - containerWidth;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private hasScrollableContent(viewportWrapper?: HTMLElement): boolean {
|
|
157
|
+
if (!this.instance || !this.instance.params) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const overflowWidth = this.getOverflowWidth(viewportWrapper);
|
|
162
|
+
if (overflowWidth !== 0) {
|
|
163
|
+
return overflowWidth > Carousel.OVERFLOW_EPSILON_PX;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Fallback for hidden/zero-width initialization.
|
|
167
|
+
const slidesCount = this.instance.slides.length;
|
|
168
|
+
const slidesPerView = this.getSlidesPerView();
|
|
169
|
+
return slidesCount > slidesPerView;
|
|
170
|
+
}
|
|
100
171
|
|
|
101
172
|
constructor(element: HTMLElement, config?: Partial<SwiperOptions>) {
|
|
102
173
|
this.element = element;
|
|
@@ -131,8 +202,12 @@ export default class Carousel {
|
|
|
131
202
|
this.getCustomOptions();
|
|
132
203
|
}
|
|
133
204
|
|
|
205
|
+
const isBleedRight = this.element.classList.contains(CLASS_BLEED_RIGHT);
|
|
206
|
+
|
|
134
207
|
this.instance = new Swiper(this.viewport, {
|
|
135
208
|
...this.config,
|
|
209
|
+
// Swiper watchOverflow can mis-detect with slides offsets; manage bleed-right overflow ourselves.
|
|
210
|
+
watchOverflow: isBleedRight ? false : this.config.watchOverflow,
|
|
136
211
|
enabled: false,
|
|
137
212
|
modules: [Navigation, Pagination, Scrollbar, A11y, Keyboard, Mousewheel],
|
|
138
213
|
on: {
|
|
@@ -237,9 +312,7 @@ export default class Carousel {
|
|
|
237
312
|
*/
|
|
238
313
|
fixBleedRightScrollbar() {
|
|
239
314
|
const updateScrollbar = () => {
|
|
240
|
-
const viewportWrapper = this.
|
|
241
|
-
`.${CLASS_VIEWPORT_WRAPPER}`,
|
|
242
|
-
) as HTMLElement;
|
|
315
|
+
const viewportWrapper = this.getViewportWrapper();
|
|
243
316
|
const scrollbar = this.instance?.scrollbar;
|
|
244
317
|
const swiper = this.instance;
|
|
245
318
|
|
|
@@ -247,26 +320,18 @@ export default class Carousel {
|
|
|
247
320
|
return;
|
|
248
321
|
}
|
|
249
322
|
|
|
250
|
-
const
|
|
251
|
-
const maxTranslate = swiper.maxTranslate();
|
|
252
|
-
const translateRange = Math.abs(maxTranslate - minTranslate);
|
|
323
|
+
const hasScrollableContent = this.hasScrollableContent(viewportWrapper);
|
|
253
324
|
|
|
254
|
-
if (
|
|
255
|
-
|
|
256
|
-
scrollbar.el.style.display = "none";
|
|
257
|
-
}
|
|
325
|
+
if (!hasScrollableContent) {
|
|
326
|
+
this.setScrollbarVisible(false);
|
|
258
327
|
return;
|
|
259
328
|
}
|
|
260
329
|
|
|
261
|
-
|
|
262
|
-
scrollbar.el.style.display = "";
|
|
263
|
-
}
|
|
330
|
+
this.setScrollbarVisible(true);
|
|
264
331
|
|
|
265
332
|
const scrollbarWidth = scrollbar.el.offsetWidth;
|
|
266
333
|
const viewportWidth = viewportWrapper.clientWidth;
|
|
267
|
-
const
|
|
268
|
-
const offsetAfter = Number(swiper.params.slidesOffsetAfter) || 0;
|
|
269
|
-
const contentWidth = this.track.scrollWidth + offsetBefore + offsetAfter;
|
|
334
|
+
const contentWidth = this.getSlidesContentWidth();
|
|
270
335
|
|
|
271
336
|
const visibleRatio =
|
|
272
337
|
contentWidth > 0 ? Math.min(viewportWidth / contentWidth, 1) : 1;
|
|
@@ -348,21 +413,18 @@ export default class Carousel {
|
|
|
348
413
|
private updateCarouselEnabledState(): void {
|
|
349
414
|
if (!this.instance || !this.instance.params) return;
|
|
350
415
|
|
|
351
|
-
const
|
|
352
|
-
const
|
|
416
|
+
const viewportWrapper = this.getViewportWrapper();
|
|
417
|
+
const hasScrollableContent = this.hasScrollableContent(
|
|
418
|
+
viewportWrapper || undefined,
|
|
419
|
+
);
|
|
353
420
|
|
|
354
|
-
if (
|
|
421
|
+
if (hasScrollableContent) {
|
|
355
422
|
this.instance.enable();
|
|
356
423
|
} else {
|
|
357
424
|
this.instance.disable();
|
|
358
425
|
}
|
|
359
426
|
|
|
360
|
-
|
|
361
|
-
SELECTOR_SCROLLBAR,
|
|
362
|
-
) as HTMLElement | null;
|
|
363
|
-
if (scrollbarEl) {
|
|
364
|
-
scrollbarEl.style.display = this.instance.enabled ? "" : "none";
|
|
365
|
-
}
|
|
427
|
+
this.setScrollbarVisible(this.instance.enabled);
|
|
366
428
|
}
|
|
367
429
|
|
|
368
430
|
private applyBleedInsets(viewportWrapper: HTMLElement): void {
|
|
@@ -370,42 +432,34 @@ export default class Carousel {
|
|
|
370
432
|
return;
|
|
371
433
|
}
|
|
372
434
|
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const rect = viewportWrapper.getBoundingClientRect();
|
|
379
|
-
const insetLeft = Math.max(Math.round(rect.left), 0);
|
|
380
|
-
const insetRight = Math.max(Math.round(viewportWidth - rect.right), 0);
|
|
381
|
-
const insetAfter = insetRight;
|
|
382
|
-
|
|
383
|
-
const slidesPerView = Number(this.instance.params.slidesPerView) || 1;
|
|
435
|
+
const projectedContentWidth = this.getSlidesContentWidth();
|
|
436
|
+
const projectedOverflowWidth =
|
|
437
|
+
projectedContentWidth - viewportWrapper.clientWidth;
|
|
438
|
+
const slidesPerView = this.getSlidesPerView();
|
|
384
439
|
const slidesCount = this.instance.slides.length;
|
|
385
|
-
const
|
|
386
|
-
|
|
440
|
+
const hasIntrinsicOverflow =
|
|
441
|
+
projectedContentWidth > 0
|
|
442
|
+
? projectedOverflowWidth > Carousel.OVERFLOW_EPSILON_PX
|
|
443
|
+
: slidesCount > slidesPerView;
|
|
444
|
+
const shouldDisableBleed = !hasIntrinsicOverflow;
|
|
387
445
|
|
|
388
446
|
this.element.classList.toggle(CLASS_BLEED_RIGHT, !shouldDisableBleed);
|
|
389
447
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
this.instance.params.slidesOffsetBefore = insetLeft;
|
|
397
|
-
this.instance.params.slidesOffsetAfter = insetAfter;
|
|
398
|
-
this.element.style.setProperty(
|
|
399
|
-
"--carousel-bleed-viewport-width",
|
|
400
|
-
`${Math.round(viewportWidth)}px`,
|
|
401
|
-
);
|
|
402
|
-
this.element.style.setProperty(
|
|
403
|
-
"--carousel-bleed-margin-left",
|
|
404
|
-
`${-insetLeft}px`,
|
|
405
|
-
);
|
|
448
|
+
const baseWidth = shouldDisableBleed
|
|
449
|
+
? undefined
|
|
450
|
+
: viewportWrapper.clientWidth;
|
|
451
|
+
this.instance.params.width = baseWidth;
|
|
452
|
+
if (this.instance.originalParams) {
|
|
453
|
+
this.instance.originalParams.width = baseWidth;
|
|
406
454
|
}
|
|
407
455
|
|
|
456
|
+
this.instance.params.slidesOffsetBefore = 0;
|
|
457
|
+
this.instance.params.slidesOffsetAfter = 0;
|
|
458
|
+
this.element.style.setProperty("--carousel-bleed-viewport-width", "100%");
|
|
459
|
+
this.element.style.setProperty("--carousel-bleed-margin-left", "0px");
|
|
460
|
+
|
|
408
461
|
this.instance.update();
|
|
462
|
+
this.updateCarouselEnabledState();
|
|
409
463
|
}
|
|
410
464
|
|
|
411
465
|
/**
|
|
@@ -418,9 +472,7 @@ export default class Carousel {
|
|
|
418
472
|
requestAnimationFrame(() => {
|
|
419
473
|
if (!this.instance) return;
|
|
420
474
|
|
|
421
|
-
const viewportWrapper = this.
|
|
422
|
-
`.${CLASS_VIEWPORT_WRAPPER}`,
|
|
423
|
-
) as HTMLElement;
|
|
475
|
+
const viewportWrapper = this.getViewportWrapper();
|
|
424
476
|
if (!viewportWrapper) return;
|
|
425
477
|
|
|
426
478
|
const updateBleedState = () => {
|
|
@@ -124,6 +124,12 @@ describe("Carousel Static - External Controls", () => {
|
|
|
124
124
|
});
|
|
125
125
|
|
|
126
126
|
describe("External Controls Initialization", () => {
|
|
127
|
+
it("should disable a11y scrollOnFocus to prevent focus-triggered slide jumps", () => {
|
|
128
|
+
const swiperConfig = Swiper.mock.calls[0]?.[1];
|
|
129
|
+
|
|
130
|
+
expect(swiperConfig?.a11y?.scrollOnFocus).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
|
|
127
133
|
it("should find and initialize external controls", () => {
|
|
128
134
|
const prevButton = document.getElementById("prev-btn");
|
|
129
135
|
const nextButton = document.getElementById("next-btn");
|
|
@@ -691,7 +697,76 @@ describe("Carousel Static - Auto-Disable Feature", () => {
|
|
|
691
697
|
});
|
|
692
698
|
});
|
|
693
699
|
|
|
694
|
-
it("should
|
|
700
|
+
it("should disable bleed-right when slides fit wrapper despite right inset", () => {
|
|
701
|
+
const originalInnerWidth = window.innerWidth;
|
|
702
|
+
Object.defineProperty(window, "innerWidth", {
|
|
703
|
+
configurable: true,
|
|
704
|
+
writable: true,
|
|
705
|
+
value: 1200,
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
mockSwiperInstance.slides = [
|
|
709
|
+
document.createElement("div"),
|
|
710
|
+
document.createElement("div"),
|
|
711
|
+
];
|
|
712
|
+
mockSwiperInstance.params.slidesPerView = 2;
|
|
713
|
+
|
|
714
|
+
document.body.innerHTML = `
|
|
715
|
+
<div class="carousel carousel--bleed-right" data-carousel-id="test-carousel" id="test-carousel">
|
|
716
|
+
<div class="carousel__viewport-wrapper">
|
|
717
|
+
<div class="carousel__viewport">
|
|
718
|
+
<div class="carousel__track">
|
|
719
|
+
<div class="carousel__slide">Slide 1</div>
|
|
720
|
+
<div class="carousel__slide">Slide 2</div>
|
|
721
|
+
</div>
|
|
722
|
+
</div>
|
|
723
|
+
</div>
|
|
724
|
+
<div class="carousel__pagination"></div>
|
|
725
|
+
</div>
|
|
726
|
+
`;
|
|
727
|
+
|
|
728
|
+
carouselElement = document.querySelector(".carousel");
|
|
729
|
+
const viewportWrapper = carouselElement.querySelector(
|
|
730
|
+
".carousel__viewport-wrapper",
|
|
731
|
+
);
|
|
732
|
+
const track = carouselElement.querySelector(".carousel__track");
|
|
733
|
+
|
|
734
|
+
Object.defineProperty(viewportWrapper, "clientWidth", {
|
|
735
|
+
configurable: true,
|
|
736
|
+
value: 900,
|
|
737
|
+
});
|
|
738
|
+
Object.defineProperty(track, "scrollWidth", {
|
|
739
|
+
configurable: true,
|
|
740
|
+
value: 900,
|
|
741
|
+
});
|
|
742
|
+
Object.defineProperty(viewportWrapper, "getBoundingClientRect", {
|
|
743
|
+
configurable: true,
|
|
744
|
+
value: () => ({
|
|
745
|
+
top: 0,
|
|
746
|
+
right: 1000,
|
|
747
|
+
bottom: 200,
|
|
748
|
+
left: 100,
|
|
749
|
+
width: 900,
|
|
750
|
+
height: 200,
|
|
751
|
+
x: 100,
|
|
752
|
+
y: 0,
|
|
753
|
+
toJSON: () => ({}),
|
|
754
|
+
}),
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
carouselInstance = new Carousel(carouselElement);
|
|
758
|
+
|
|
759
|
+
expect(mockSwiperInstance.params.slidesOffsetBefore).toBe(0);
|
|
760
|
+
expect(mockSwiperInstance.params.slidesOffsetAfter).toBe(0);
|
|
761
|
+
|
|
762
|
+
Object.defineProperty(window, "innerWidth", {
|
|
763
|
+
configurable: true,
|
|
764
|
+
writable: true,
|
|
765
|
+
value: originalInnerWidth,
|
|
766
|
+
});
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
it("should keep wrapper-based bleed geometry without offsets", () => {
|
|
695
770
|
const originalInnerWidth = window.innerWidth;
|
|
696
771
|
Object.defineProperty(window, "innerWidth", {
|
|
697
772
|
configurable: true,
|
|
@@ -726,6 +801,16 @@ describe("Carousel Static - Auto-Disable Feature", () => {
|
|
|
726
801
|
const viewportWrapper = carouselElement.querySelector(
|
|
727
802
|
".carousel__viewport-wrapper",
|
|
728
803
|
);
|
|
804
|
+
const track = carouselElement.querySelector(".carousel__track");
|
|
805
|
+
|
|
806
|
+
Object.defineProperty(viewportWrapper, "clientWidth", {
|
|
807
|
+
configurable: true,
|
|
808
|
+
value: 800,
|
|
809
|
+
});
|
|
810
|
+
Object.defineProperty(track, "scrollWidth", {
|
|
811
|
+
configurable: true,
|
|
812
|
+
value: 1200,
|
|
813
|
+
});
|
|
729
814
|
|
|
730
815
|
Object.defineProperty(viewportWrapper, "getBoundingClientRect", {
|
|
731
816
|
configurable: true,
|
|
@@ -744,16 +829,17 @@ describe("Carousel Static - Auto-Disable Feature", () => {
|
|
|
744
829
|
|
|
745
830
|
carouselInstance = new Carousel(carouselElement);
|
|
746
831
|
|
|
747
|
-
expect(mockSwiperInstance.params.slidesOffsetBefore).toBe(
|
|
748
|
-
expect(mockSwiperInstance.params.slidesOffsetAfter).toBe(
|
|
832
|
+
expect(mockSwiperInstance.params.slidesOffsetBefore).toBe(0);
|
|
833
|
+
expect(mockSwiperInstance.params.slidesOffsetAfter).toBe(0);
|
|
834
|
+
expect(mockSwiperInstance.params.width).toBe(800);
|
|
749
835
|
expect(
|
|
750
836
|
carouselElement.style.getPropertyValue(
|
|
751
837
|
"--carousel-bleed-viewport-width",
|
|
752
838
|
),
|
|
753
|
-
).toBe("
|
|
839
|
+
).toBe("100%");
|
|
754
840
|
expect(
|
|
755
841
|
carouselElement.style.getPropertyValue("--carousel-bleed-margin-left"),
|
|
756
|
-
).toBe("
|
|
842
|
+
).toBe("0px");
|
|
757
843
|
|
|
758
844
|
Object.defineProperty(window, "innerWidth", {
|
|
759
845
|
configurable: true,
|
|
@@ -25,6 +25,8 @@ interface ExpanderProps {
|
|
|
25
25
|
children?: React.ReactNode;
|
|
26
26
|
/** Expander takes full width of its container */
|
|
27
27
|
isFullWidth?: boolean;
|
|
28
|
+
/** Position of trigger relative to content when opened */
|
|
29
|
+
placement?: "top" | "bottom";
|
|
28
30
|
/** Group identifier for syncing multiple expanders together */
|
|
29
31
|
toggleGroup?: string;
|
|
30
32
|
/** Initial open state */
|
|
@@ -40,6 +42,7 @@ export const Expander: React.FC<ExpanderProps> = (props) => {
|
|
|
40
42
|
renderSummary,
|
|
41
43
|
renderSummaryOpened,
|
|
42
44
|
isFullWidth,
|
|
45
|
+
placement = "bottom",
|
|
43
46
|
toggleGroup,
|
|
44
47
|
...other
|
|
45
48
|
} = props;
|
|
@@ -49,6 +52,7 @@ export const Expander: React.FC<ExpanderProps> = (props) => {
|
|
|
49
52
|
CLASS_ROOT,
|
|
50
53
|
{
|
|
51
54
|
[`${CLASS_ROOT}--fullwidth`]: isFullWidth,
|
|
55
|
+
[`${CLASS_ROOT}--placement-top`]: placement === "top",
|
|
52
56
|
},
|
|
53
57
|
className,
|
|
54
58
|
);
|
|
@@ -51,6 +51,18 @@
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
&--placement-top {
|
|
55
|
+
&[open] {
|
|
56
|
+
display: flex;
|
|
57
|
+
flex-direction: column-reverse;
|
|
58
|
+
|
|
59
|
+
summary {
|
|
60
|
+
margin-top: space.get("large");
|
|
61
|
+
margin-bottom: 0;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
54
66
|
&[open] {
|
|
55
67
|
svg[data-summary-icon] {
|
|
56
68
|
transform: rotate(180deg) !important;
|
|
@@ -184,4 +184,13 @@ describe("Expander", () => {
|
|
|
184
184
|
);
|
|
185
185
|
expect(getByTestId("test-id")).toHaveClass("expander--fullwidth");
|
|
186
186
|
});
|
|
187
|
+
|
|
188
|
+
it("applies placement-top class when placement prop is top", () => {
|
|
189
|
+
const { getByTestId } = render(
|
|
190
|
+
<Expander data-testid="test-id" summary="Show details" placement="top">
|
|
191
|
+
Content
|
|
192
|
+
</Expander>,
|
|
193
|
+
);
|
|
194
|
+
expect(getByTestId("test-id")).toHaveClass("expander--placement-top");
|
|
195
|
+
});
|
|
187
196
|
});
|
|
@@ -5,17 +5,20 @@ import { genResponsiveClasses } from "../../utils";
|
|
|
5
5
|
export type GridVAlign = "stretch" | "start" | "end" | "center";
|
|
6
6
|
export type GridHAlign = "end" | "center" | "space-around" | "space-between";
|
|
7
7
|
export type GridRowGapSize =
|
|
8
|
+
| "none"
|
|
9
|
+
| "xsmall"
|
|
8
10
|
| "small"
|
|
9
11
|
| "medium"
|
|
10
12
|
| "large"
|
|
11
13
|
| "xlarge"
|
|
12
|
-
| Record<string, "small" | "medium" | "large" | "xlarge">;
|
|
14
|
+
| Record<string, "none" | "xsmall" | "small" | "medium" | "large" | "xlarge">;
|
|
13
15
|
export type GridColumnGapSize =
|
|
14
16
|
| "none"
|
|
15
17
|
| "small"
|
|
16
18
|
| "default"
|
|
17
19
|
| "large"
|
|
18
|
-
|
|
|
20
|
+
| "xlarge"
|
|
21
|
+
| Record<string, "none" | "small" | "default" | "large" | "xlarge">;
|
|
19
22
|
|
|
20
23
|
export interface GridProps extends React.HTMLAttributes<HTMLElement> {
|
|
21
24
|
vAlign?: GridVAlign;
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
$grid-base: 12 !default;
|
|
4
4
|
|
|
5
|
-
$grid-row-gap-sizes: ("small", "medium", "large", "xlarge");
|
|
6
|
-
$grid-column-gap-sizes: ("none", "small", "default", "large");
|
|
5
|
+
$grid-row-gap-sizes: ("none", "xsmall", "small", "medium", "large", "xlarge");
|
|
6
|
+
$grid-column-gap-sizes: ("none", "small", "default", "large", "xlarge");
|
|
7
7
|
|
|
8
8
|
$column-gap: (
|
|
9
9
|
none: 0,
|
|
10
10
|
small: space.get("xsmall"),
|
|
11
11
|
default: space.get("small"),
|
|
12
12
|
large: space.get("medium"),
|
|
13
|
+
xlarge: space.get("large"),
|
|
13
14
|
);
|