tide-design-system 2.5.0 → 2.5.3
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/.storybook/main.ts +2 -0
- package/README.md +3 -1
- package/dist/css/reset.css +5 -1
- package/dist/css/utilities-base.css +6 -6
- package/dist/css/utilities-responsive.css +24 -24
- package/dist/css/variables.css +3 -0
- package/dist/style.css +1 -1
- package/dist/tide-design-system.cjs +2 -2
- package/dist/tide-design-system.esm.d.ts +51 -9
- package/dist/tide-design-system.esm.js +1621 -1481
- package/dist/utilities/validation.ts +1 -1
- package/docs/assets/full-bleed.gif +0 -0
- package/docs/assets/layout-grid-default.webp +0 -0
- package/docs/assets/layout-grid-fluid.webp +0 -0
- package/docs/assets/layout-grid.webp +0 -0
- package/docs/configuation.md +47 -0
- package/docs/grid-layout.md +83 -0
- package/index.ts +4 -0
- package/package.json +1 -1
- package/src/assets/css/reset.css +5 -1
- package/src/assets/css/utilities-base.css +6 -6
- package/src/assets/css/utilities-responsive.css +24 -24
- package/src/assets/css/variables.css +3 -0
- package/src/components/TideAlert.vue +1 -1
- package/src/components/TideCarousel.vue +104 -40
- package/src/components/TideInputSelect.vue +1 -1
- package/src/components/TideInputSelectDeprecated.vue +1 -1
- package/src/components/TideInputText.vue +2 -2
- package/src/components/TideInputTextDeprecated.vue +2 -2
- package/src/components/TideInputTextarea.vue +2 -2
- package/src/components/TideInputTextareaDeprecated.vue +2 -2
- package/src/components/TideLink.vue +6 -0
- package/src/components/TideMenuItem.vue +1 -1
- package/src/components/TideModal.vue +1 -1
- package/src/components/TideRating.vue +93 -0
- package/src/components/TideSheet.vue +1 -1
- package/src/components/TideTabs.vue +58 -0
- package/src/stories/TideCarousel.stories.ts +47 -25
- package/src/stories/TideRating.stories.ts +120 -0
- package/src/stories/TideTabs.stories.ts +115 -0
- package/src/types/Formatted.ts +1 -1
- package/src/utilities/validation.ts +1 -1
- package/tests/utilities-format.spec.ts +40 -0
|
@@ -62,7 +62,7 @@ export const getFieldValidationResult = ({
|
|
|
62
62
|
|
|
63
63
|
// custom validator prop errors from have second highest precedence
|
|
64
64
|
if (validators) {
|
|
65
|
-
const validation = validateProperty(value.value, validators);
|
|
65
|
+
const validation = validateProperty(value.value ?? '', validators);
|
|
66
66
|
|
|
67
67
|
if (!validation.valid) {
|
|
68
68
|
return validation;
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# TIDE Configuration
|
|
2
|
+
|
|
3
|
+
You can configure global TIDE behavior using `provideTideConfig()`. Place it in a top-level file such as `app.vue` (anywhere Vue’s `provide()` works). See Vue’s docs on [provide/inject](https://vuejs.org/guide/components/provide-inject) for more details.
|
|
4
|
+
|
|
5
|
+
## `provideTideConfig()`
|
|
6
|
+
|
|
7
|
+
``` ts
|
|
8
|
+
import { provideTideConfig } from 'tide-design-system';
|
|
9
|
+
|
|
10
|
+
provideTideConfig({
|
|
11
|
+
linkComponent: 'a', // default
|
|
12
|
+
});
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Config values
|
|
16
|
+
|
|
17
|
+
| Key | Type | Default | Description |
|
|
18
|
+
| --- | --- | --- | --- |
|
|
19
|
+
| `linkComponent` | `'a' \| Component` | `'a'` | Replaces the root element used by link-based TIDE components. |
|
|
20
|
+
|
|
21
|
+
## `linkComponent` (TIDE 2.5+)
|
|
22
|
+
|
|
23
|
+
Use this to replace all `<a>` elements in TIDE components with a custom component. Useful for frameworks like Nuxt.
|
|
24
|
+
|
|
25
|
+
### Nuxt Example (using `NuxtLink`)
|
|
26
|
+
|
|
27
|
+
``` vue
|
|
28
|
+
<!-- app.vue -->
|
|
29
|
+
<script setup lang="ts">
|
|
30
|
+
import { provideTideConfig } from 'tide-design-system';
|
|
31
|
+
import { NuxtLink } from '#components';
|
|
32
|
+
|
|
33
|
+
provideTideConfig({
|
|
34
|
+
linkComponent: NuxtLink,
|
|
35
|
+
});
|
|
36
|
+
</script>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This updates all TIDE components that render links, including:
|
|
40
|
+
|
|
41
|
+
- TideLink
|
|
42
|
+
- TideButton (`:element="ELEMENT.LINK"`)
|
|
43
|
+
- TideCard (`:type="TYPE_CARD.ACTION"` with `href`)
|
|
44
|
+
- TideBreadCrumbs
|
|
45
|
+
- And others
|
|
46
|
+
|
|
47
|
+
All these will now render using `<NuxtLink />` automatically.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# TIDE Layout Grid Utility
|
|
2
|
+
|
|
3
|
+
TIDE provides a lightweight CSS Grid for consistent page alignment, gutters, and max-width, with minimal markup. It separates **layout** (page width, gutters) from **component** concerns (spacing, internal layout).
|
|
4
|
+
|
|
5
|
+
<img src="./assets/layout-grid.webp" width="937" height="420" /><br>
|
|
6
|
+
|
|
7
|
+
## Core Concepts
|
|
8
|
+
|
|
9
|
+
### Three-Column Grid
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
| gutter | content | gutter |
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Direct children are stacked in the **center content column** by default.
|
|
16
|
+
|
|
17
|
+
### Responsive Max Width
|
|
18
|
+
|
|
19
|
+
| Variant | Max content width | Class |
|
|
20
|
+
| ------- | ----------------- | ------------- |
|
|
21
|
+
| Default | 1232px | – |
|
|
22
|
+
| XL | 1920px | `CSS.GRID.XL` |
|
|
23
|
+
|
|
24
|
+
``` vue
|
|
25
|
+
<div :class="[CSS.GRID.LAYOUT, CSS.GRID.XL]">...</div>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Responsive Gutters
|
|
29
|
+
|
|
30
|
+
| Breakpoint | Min gutter |
|
|
31
|
+
| ---------- | ------------------ |
|
|
32
|
+
| XS | `--tide-spacing-1` |
|
|
33
|
+
| XS - SM | `--tide-spacing-2` |
|
|
34
|
+
| SM - MD | `--tide-spacing-4` |
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
### Page Layout Context
|
|
41
|
+
|
|
42
|
+
``` vue
|
|
43
|
+
<div :class="[CSS.GRID.LAYOUT]">
|
|
44
|
+
<section><ArticleIntro /></section>
|
|
45
|
+
<section><ArticleBody /></section>
|
|
46
|
+
</div>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Direct children are **centered automatically**, like so:
|
|
50
|
+
|
|
51
|
+
<img src="./assets/layout-grid-default.webp" width="937" height="420" /><br>
|
|
52
|
+
|
|
53
|
+
### Full-Width / Edge-to-Edge Sections
|
|
54
|
+
|
|
55
|
+
Use `CSS.GRID.FLUID` to break out of the content column:
|
|
56
|
+
|
|
57
|
+
``` vue
|
|
58
|
+
<div :class="[CSS.GRID.LAYOUT]">
|
|
59
|
+
<section><ArticleIntro /></section>
|
|
60
|
+
|
|
61
|
+
<section :class="[CSS.GRID.FLUID]">
|
|
62
|
+
<HeroBanner />
|
|
63
|
+
</section>
|
|
64
|
+
|
|
65
|
+
<section><ArticleBody /></section>
|
|
66
|
+
</div>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The code in this example would result in a layout like this:
|
|
70
|
+
|
|
71
|
+
<img src="./assets/layout-grid-fluid.webp" width="937" height="420" /><br>
|
|
72
|
+
|
|
73
|
+
Notice the `<HeroBanner />` is no longer confined to the center column, and instead breaks out and stretches **edge-to-edge**. This section is now considered **fluid**.
|
|
74
|
+
|
|
75
|
+
Common use cases for fluid sections:
|
|
76
|
+
|
|
77
|
+
- Header/footer
|
|
78
|
+
- Full-bleed carousels
|
|
79
|
+
- Edge-to-edge hero sections
|
|
80
|
+
- Sticky bars
|
|
81
|
+
- Page sections with toned backgrounds
|
|
82
|
+
|
|
83
|
+
<img src="./assets/full-bleed.gif" width="127" height="277" /><br>
|
package/index.ts
CHANGED
|
@@ -35,9 +35,11 @@ import TideMenuItem from '@/components/TideMenuItem.vue';
|
|
|
35
35
|
import TideModal from '@/components/TideModal.vue';
|
|
36
36
|
import TidePagination from '@/components/TidePagination.vue';
|
|
37
37
|
import TidePopover from '@/components/TidePopover.vue';
|
|
38
|
+
import TideRating from '@/components/TideRating.vue';
|
|
38
39
|
import TideSeoLinks from '@/components/TideSeoLinks.vue';
|
|
39
40
|
import TideSheet from '@/components/TideSheet.vue';
|
|
40
41
|
import TideSwitch from '@/components/TideSwitch.vue';
|
|
42
|
+
import TideTabs from '@/components/TideTabs.vue';
|
|
41
43
|
import { provideTideConfig } from '@/composables/useTideConfig';
|
|
42
44
|
import { useTideForm } from '@/composables/useTideForm';
|
|
43
45
|
import { ALERT } from '@/types/Alert';
|
|
@@ -182,7 +184,9 @@ export {
|
|
|
182
184
|
TideModal,
|
|
183
185
|
TidePagination,
|
|
184
186
|
TidePopover,
|
|
187
|
+
TideRating,
|
|
185
188
|
TideSeoLinks,
|
|
186
189
|
TideSheet,
|
|
187
190
|
TideSwitch,
|
|
191
|
+
TideTabs,
|
|
188
192
|
};
|
package/package.json
CHANGED
package/src/assets/css/reset.css
CHANGED
|
@@ -7,6 +7,7 @@ body {
|
|
|
7
7
|
font-size: var(--tide-font-16);
|
|
8
8
|
font-weight: 400;
|
|
9
9
|
line-height: 1.4;
|
|
10
|
+
text-wrap: pretty;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
/* Cannot be applied to body tag in marketplace repo due to data-css-scope */
|
|
@@ -39,13 +40,16 @@ h2 {font-size: var(--tide-font-24);} /* 24px */
|
|
|
39
40
|
h3 {font-size: var(--tide-font-20);} /* 20px */
|
|
40
41
|
h4 {font-size: var(--tide-font-16);} /* 16px */
|
|
41
42
|
h5 {font-size: var(--tide-font-16);} /* 16px */
|
|
43
|
+
h6 {font-size: var(--tide-font-16);} /* 16px */
|
|
42
44
|
|
|
43
45
|
h1,
|
|
44
46
|
h2,
|
|
45
47
|
h3,
|
|
46
48
|
h4,
|
|
47
|
-
h5
|
|
49
|
+
h5,
|
|
50
|
+
h6 {
|
|
48
51
|
font-weight: 700;
|
|
52
|
+
text-wrap: balance;
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
img,
|
|
@@ -294,12 +294,12 @@
|
|
|
294
294
|
.tide-transparent-400 {background-color: var(--tide-transparent-400);}
|
|
295
295
|
|
|
296
296
|
/* Typographic roles */
|
|
297
|
-
.tide-typography-display-1 {font-size: var(--tide-font-32); font-weight: 700;}
|
|
298
|
-
.tide-typography-headline-1 {font-size: var(--tide-font-24); font-weight: 700;}
|
|
299
|
-
.tide-typography-headline-2 {font-size: var(--tide-font-20); font-weight: 700;}
|
|
300
|
-
.tide-typography-headline-3 {font-size: var(--tide-font-16); font-weight: 700;}
|
|
301
|
-
.tide-typography-title-1 {font-size: var(--tide-font-20); font-weight: 600;}
|
|
302
|
-
.tide-typography-title-2 {font-size: var(--tide-font-18); font-weight: 600;}
|
|
297
|
+
.tide-typography-display-1 {font-size: var(--tide-font-32); font-weight: 700; text-wrap: balance;}
|
|
298
|
+
.tide-typography-headline-1 {font-size: var(--tide-font-24); font-weight: 700; text-wrap: balance;}
|
|
299
|
+
.tide-typography-headline-2 {font-size: var(--tide-font-20); font-weight: 700; text-wrap: balance;}
|
|
300
|
+
.tide-typography-headline-3 {font-size: var(--tide-font-16); font-weight: 700; text-wrap: balance;}
|
|
301
|
+
.tide-typography-title-1 {font-size: var(--tide-font-20); font-weight: 600; text-wrap: balance;}
|
|
302
|
+
.tide-typography-title-2 {font-size: var(--tide-font-18); font-weight: 600; text-wrap: balance;}
|
|
303
303
|
.tide-typography-body-1 {font-size: var(--tide-font-16); font-weight: 400;}
|
|
304
304
|
.tide-typography-body-2 {font-size: var(--tide-font-14); font-weight: 400;}
|
|
305
305
|
.tide-typography-label-1 {font-size: var(--tide-font-16); font-weight: 500;}
|
|
@@ -295,12 +295,12 @@
|
|
|
295
295
|
.sm-tide-transparent-400 {background-color: var(--tide-transparent-400);}
|
|
296
296
|
|
|
297
297
|
/* Typographic roles */
|
|
298
|
-
.sm-tide-typography-display-1 {font-size: var(--tide-font-32); font-weight: 700;}
|
|
299
|
-
.sm-tide-typography-headline-1 {font-size: var(--tide-font-24); font-weight: 700;}
|
|
300
|
-
.sm-tide-typography-headline-2 {font-size: var(--tide-font-20); font-weight: 700;}
|
|
301
|
-
.sm-tide-typography-headline-3 {font-size: var(--tide-font-16); font-weight: 700;}
|
|
302
|
-
.sm-tide-typography-title-1 {font-size: var(--tide-font-20); font-weight: 600;}
|
|
303
|
-
.sm-tide-typography-title-2 {font-size: var(--tide-font-18); font-weight: 600;}
|
|
298
|
+
.sm-tide-typography-display-1 {font-size: var(--tide-font-32); font-weight: 700; text-wrap: balance;}
|
|
299
|
+
.sm-tide-typography-headline-1 {font-size: var(--tide-font-24); font-weight: 700; text-wrap: balance;}
|
|
300
|
+
.sm-tide-typography-headline-2 {font-size: var(--tide-font-20); font-weight: 700; text-wrap: balance;}
|
|
301
|
+
.sm-tide-typography-headline-3 {font-size: var(--tide-font-16); font-weight: 700; text-wrap: balance;}
|
|
302
|
+
.sm-tide-typography-title-1 {font-size: var(--tide-font-20); font-weight: 600; text-wrap: balance;}
|
|
303
|
+
.sm-tide-typography-title-2 {font-size: var(--tide-font-18); font-weight: 600; text-wrap: balance;}
|
|
304
304
|
.sm-tide-typography-body-1 {font-size: var(--tide-font-16); font-weight: 400;}
|
|
305
305
|
.sm-tide-typography-body-2 {font-size: var(--tide-font-14); font-weight: 400;}
|
|
306
306
|
.sm-tide-typography-label-1 {font-size: var(--tide-font-16); font-weight: 500;}
|
|
@@ -843,12 +843,12 @@
|
|
|
843
843
|
.md-tide-transparent-400 {background-color: var(--tide-transparent-400);}
|
|
844
844
|
|
|
845
845
|
/* Typographic roles */
|
|
846
|
-
.md-tide-typography-display-1 {font-size: var(--tide-font-32); font-weight: 700;}
|
|
847
|
-
.md-tide-typography-headline-1 {font-size: var(--tide-font-24); font-weight: 700;}
|
|
848
|
-
.md-tide-typography-headline-2 {font-size: var(--tide-font-20); font-weight: 700;}
|
|
849
|
-
.md-tide-typography-headline-3 {font-size: var(--tide-font-16); font-weight: 700;}
|
|
850
|
-
.md-tide-typography-title-1 {font-size: var(--tide-font-20); font-weight: 600;}
|
|
851
|
-
.md-tide-typography-title-2 {font-size: var(--tide-font-18); font-weight: 600;}
|
|
846
|
+
.md-tide-typography-display-1 {font-size: var(--tide-font-32); font-weight: 700; text-wrap: balance;}
|
|
847
|
+
.md-tide-typography-headline-1 {font-size: var(--tide-font-24); font-weight: 700; text-wrap: balance;}
|
|
848
|
+
.md-tide-typography-headline-2 {font-size: var(--tide-font-20); font-weight: 700; text-wrap: balance;}
|
|
849
|
+
.md-tide-typography-headline-3 {font-size: var(--tide-font-16); font-weight: 700; text-wrap: balance;}
|
|
850
|
+
.md-tide-typography-title-1 {font-size: var(--tide-font-20); font-weight: 600; text-wrap: balance;}
|
|
851
|
+
.md-tide-typography-title-2 {font-size: var(--tide-font-18); font-weight: 600; text-wrap: balance;}
|
|
852
852
|
.md-tide-typography-body-1 {font-size: var(--tide-font-16); font-weight: 400;}
|
|
853
853
|
.md-tide-typography-body-2 {font-size: var(--tide-font-14); font-weight: 400;}
|
|
854
854
|
.md-tide-typography-label-1 {font-size: var(--tide-font-16); font-weight: 500;}
|
|
@@ -1391,12 +1391,12 @@
|
|
|
1391
1391
|
.lg-tide-transparent-400 {background-color: var(--tide-transparent-400);}
|
|
1392
1392
|
|
|
1393
1393
|
/* Typographic roles */
|
|
1394
|
-
.lg-tide-typography-display-1 {font-size: var(--tide-font-32); font-weight: 700;}
|
|
1395
|
-
.lg-tide-typography-headline-1 {font-size: var(--tide-font-24); font-weight: 700;}
|
|
1396
|
-
.lg-tide-typography-headline-2 {font-size: var(--tide-font-20); font-weight: 700;}
|
|
1397
|
-
.lg-tide-typography-headline-3 {font-size: var(--tide-font-16); font-weight: 700;}
|
|
1398
|
-
.lg-tide-typography-title-1 {font-size: var(--tide-font-20); font-weight: 600;}
|
|
1399
|
-
.lg-tide-typography-title-2 {font-size: var(--tide-font-18); font-weight: 600;}
|
|
1394
|
+
.lg-tide-typography-display-1 {font-size: var(--tide-font-32); font-weight: 700; text-wrap: balance;}
|
|
1395
|
+
.lg-tide-typography-headline-1 {font-size: var(--tide-font-24); font-weight: 700; text-wrap: balance;}
|
|
1396
|
+
.lg-tide-typography-headline-2 {font-size: var(--tide-font-20); font-weight: 700; text-wrap: balance;}
|
|
1397
|
+
.lg-tide-typography-headline-3 {font-size: var(--tide-font-16); font-weight: 700; text-wrap: balance;}
|
|
1398
|
+
.lg-tide-typography-title-1 {font-size: var(--tide-font-20); font-weight: 600; text-wrap: balance;}
|
|
1399
|
+
.lg-tide-typography-title-2 {font-size: var(--tide-font-18); font-weight: 600; text-wrap: balance;}
|
|
1400
1400
|
.lg-tide-typography-body-1 {font-size: var(--tide-font-16); font-weight: 400;}
|
|
1401
1401
|
.lg-tide-typography-body-2 {font-size: var(--tide-font-14); font-weight: 400;}
|
|
1402
1402
|
.lg-tide-typography-label-1 {font-size: var(--tide-font-16); font-weight: 500;}
|
|
@@ -1939,12 +1939,12 @@
|
|
|
1939
1939
|
.xl-tide-transparent-400 {background-color: var(--tide-transparent-400);}
|
|
1940
1940
|
|
|
1941
1941
|
/* Typographic roles */
|
|
1942
|
-
.xl-tide-typography-display-1 {font-size: var(--tide-font-32); font-weight: 700;}
|
|
1943
|
-
.xl-tide-typography-headline-1 {font-size: var(--tide-font-24); font-weight: 700;}
|
|
1944
|
-
.xl-tide-typography-headline-2 {font-size: var(--tide-font-20); font-weight: 700;}
|
|
1945
|
-
.xl-tide-typography-headline-3 {font-size: var(--tide-font-16); font-weight: 700;}
|
|
1946
|
-
.xl-tide-typography-title-1 {font-size: var(--tide-font-20); font-weight: 600;}
|
|
1947
|
-
.xl-tide-typography-title-2 {font-size: var(--tide-font-18); font-weight: 600;}
|
|
1942
|
+
.xl-tide-typography-display-1 {font-size: var(--tide-font-32); font-weight: 700; text-wrap: balance;}
|
|
1943
|
+
.xl-tide-typography-headline-1 {font-size: var(--tide-font-24); font-weight: 700; text-wrap: balance;}
|
|
1944
|
+
.xl-tide-typography-headline-2 {font-size: var(--tide-font-20); font-weight: 700; text-wrap: balance;}
|
|
1945
|
+
.xl-tide-typography-headline-3 {font-size: var(--tide-font-16); font-weight: 700; text-wrap: balance;}
|
|
1946
|
+
.xl-tide-typography-title-1 {font-size: var(--tide-font-20); font-weight: 600; text-wrap: balance;}
|
|
1947
|
+
.xl-tide-typography-title-2 {font-size: var(--tide-font-18); font-weight: 600; text-wrap: balance;}
|
|
1948
1948
|
.xl-tide-typography-body-1 {font-size: var(--tide-font-16); font-weight: 400;}
|
|
1949
1949
|
.xl-tide-typography-body-2 {font-size: var(--tide-font-14); font-weight: 400;}
|
|
1950
1950
|
.xl-tide-typography-label-1 {font-size: var(--tide-font-16); font-weight: 500;}
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
const emit = defineEmits<Emits>();
|
|
36
36
|
|
|
37
|
+
const carouselRef = ref<HTMLDivElement | null>(null);
|
|
37
38
|
const containerRef = ref<HTMLDivElement | null>(null);
|
|
38
39
|
const contentWidth = ref<number>(0);
|
|
39
40
|
const currentPageIndex = ref<number>(0);
|
|
@@ -46,26 +47,25 @@
|
|
|
46
47
|
const slides = ref<HTMLElement[]>([]);
|
|
47
48
|
const slidesInView = ref<number[]>([]);
|
|
48
49
|
const slotObserver = ref<MutationObserver | null>(null);
|
|
50
|
+
const touchStart = ref<Touch | undefined>(undefined);
|
|
51
|
+
const slidesInViewCount = ref<number>(1);
|
|
49
52
|
|
|
50
53
|
const currentPage = computed(() => currentPageIndex.value + 1);
|
|
51
54
|
const dotContainerWidth = computed(() => dotCountVisible.value * dotWidth + (dotCountVisible.value - 1) * dotGap);
|
|
52
55
|
const dotCountVisible = computed(() => (props.maxDots > totalPages.value ? totalPages.value : props.maxDots));
|
|
53
|
-
const lastPageIndex = computed(() => totalPages.value - 1);
|
|
54
56
|
const totalPages = computed(() => {
|
|
55
57
|
if (!slides.value.length) return 0;
|
|
56
58
|
if (!props.isScrollByPage) return slides.value.length;
|
|
57
59
|
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
const quotient = Math.round(contentNoGap / frameWidth.value);
|
|
61
|
-
const remainder = contentNoGap % frameWidth.value;
|
|
60
|
+
const quotient = Math.floor(slides.value.length / slidesInViewCount.value);
|
|
61
|
+
const remainder = slides.value.length % slidesInViewCount.value;
|
|
62
62
|
|
|
63
63
|
return remainder ? quotient + 1 : quotient;
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
const cardGap: number = 16;
|
|
67
66
|
const dotGap: number = 8;
|
|
68
67
|
const dotWidth: number = 8;
|
|
68
|
+
let scrollTimeout: number | undefined;
|
|
69
69
|
|
|
70
70
|
const getDotClass = (dotIndex: number) => {
|
|
71
71
|
let className = '';
|
|
@@ -76,17 +76,64 @@
|
|
|
76
76
|
return className;
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
+
const getIsElementWithinContainer = (element: HTMLElement, container: HTMLElement) => {
|
|
80
|
+
const containerRect = container.getBoundingClientRect();
|
|
81
|
+
const containerRight = containerRect.left + containerRect.width;
|
|
82
|
+
const elementRect = element.getBoundingClientRect();
|
|
83
|
+
const elementRight = elementRect.left + elementRect.width;
|
|
84
|
+
|
|
85
|
+
return elementRight <= containerRight && elementRect.left >= containerRect.left;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const handleTouchEnd = (event: TouchEvent) => {
|
|
89
|
+
if (!touchStart.value) return;
|
|
90
|
+
|
|
91
|
+
const touchEnd = event.changedTouches[0];
|
|
92
|
+
const deltaX = touchStart.value.clientX - touchEnd.clientX;
|
|
93
|
+
const deltaY = touchEnd.clientY - touchStart.value.clientY;
|
|
94
|
+
|
|
95
|
+
if (Math.abs(deltaX) > Math.abs(deltaY)) scrollByDelta(deltaX);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const handleTouchStart = (event: TouchEvent) => {
|
|
99
|
+
touchStart.value = event.touches[0];
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const handleWheel = (event: WheelEvent) => {
|
|
103
|
+
const isShiftKeyDown = event.shiftKey;
|
|
104
|
+
const isWheel = Math.abs(event.deltaY) >= 80;
|
|
105
|
+
|
|
106
|
+
if (isWheel && !isShiftKeyDown) return;
|
|
107
|
+
if (event.shiftKey) event.preventDefault();
|
|
108
|
+
|
|
109
|
+
clearTimeout(scrollTimeout);
|
|
110
|
+
|
|
111
|
+
scrollTimeout = window.setTimeout(() => {
|
|
112
|
+
if (isWheel) {
|
|
113
|
+
scrollByDelta(event.deltaY);
|
|
114
|
+
} else {
|
|
115
|
+
const offset = slides.value[slidesInView.value[0]]?.offsetLeft || 0;
|
|
116
|
+
|
|
117
|
+
scrollToOffset(offset);
|
|
118
|
+
}
|
|
119
|
+
}, 100);
|
|
120
|
+
};
|
|
121
|
+
|
|
79
122
|
const measureDom = () => {
|
|
80
123
|
if (!containerRef.value) return;
|
|
81
124
|
|
|
82
125
|
contentWidth.value = containerRef.value.scrollWidth;
|
|
83
126
|
frameWidth.value = containerRef.value.clientWidth;
|
|
84
127
|
showButtons.value = contentWidth.value > frameWidth.value;
|
|
128
|
+
|
|
129
|
+
slidesInViewCount.value = slides.value.filter((slide) =>
|
|
130
|
+
getIsElementWithinContainer(slide, containerRef.value as HTMLElement)
|
|
131
|
+
).length;
|
|
85
132
|
};
|
|
86
133
|
|
|
87
134
|
const observeSlides = () => {
|
|
88
135
|
const options = {
|
|
89
|
-
root:
|
|
136
|
+
root: carouselRef?.value,
|
|
90
137
|
rootMargin: '0px 1px 0px 0px',
|
|
91
138
|
threshold: 1,
|
|
92
139
|
};
|
|
@@ -131,12 +178,20 @@
|
|
|
131
178
|
if (containerRef.value) slotObserver.value.observe(containerRef.value, { childList: true });
|
|
132
179
|
};
|
|
133
180
|
|
|
181
|
+
const scrollByDelta = (delta: number) => {
|
|
182
|
+
const isScrollingLeft = delta < 0;
|
|
183
|
+
|
|
184
|
+
if (isScrollingLeft) {
|
|
185
|
+
props.isScrollByPage ? showPreviousPage() : showPreviousSlide();
|
|
186
|
+
} else {
|
|
187
|
+
props.isScrollByPage ? showNextPage() : showNextSlide();
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
134
191
|
const scrollToOffset = (target: number) => {
|
|
135
192
|
if (containerRef.value === null) return;
|
|
136
193
|
|
|
137
|
-
const
|
|
138
|
-
const placement = (target / lastOffset) * lastPageIndex.value;
|
|
139
|
-
const isScrollingLeft = placement <= currentPageIndex.value;
|
|
194
|
+
const isScrollingLeft = target <= containerRef.value.scrollLeft;
|
|
140
195
|
|
|
141
196
|
currentPageIndex.value = isScrollingLeft ? currentPageIndex.value - 1 : currentPageIndex.value + 1;
|
|
142
197
|
|
|
@@ -148,19 +203,21 @@
|
|
|
148
203
|
};
|
|
149
204
|
|
|
150
205
|
const showNextPage = () => {
|
|
151
|
-
if (slidesInView.value.length === 0) return;
|
|
206
|
+
if (slidesInView.value.length === 0 || isLastSlide.value) return;
|
|
152
207
|
|
|
153
208
|
const nextSlide: number = slidesInView.value[slidesInView.value.length - 1] + 1;
|
|
209
|
+
const offset = slides.value[nextSlide]?.offsetLeft;
|
|
154
210
|
|
|
155
|
-
scrollToOffset(
|
|
211
|
+
scrollToOffset(offset);
|
|
156
212
|
};
|
|
157
213
|
|
|
158
214
|
const showPreviousPage = () => {
|
|
159
|
-
if (slidesInView.value.length === 0) return;
|
|
215
|
+
if (slidesInView.value.length === 0 || isFirstSlide.value) return;
|
|
160
216
|
|
|
161
217
|
const previousSlide: number = slidesInView.value[0] - slidesInView.value.length;
|
|
218
|
+
const offset = slides.value[previousSlide]?.offsetLeft || 0;
|
|
162
219
|
|
|
163
|
-
scrollToOffset(
|
|
220
|
+
scrollToOffset(offset);
|
|
164
221
|
};
|
|
165
222
|
|
|
166
223
|
const showNextSlide = () => {
|
|
@@ -284,51 +341,58 @@
|
|
|
284
341
|
CSS.SNAP.ON,
|
|
285
342
|
]"
|
|
286
343
|
ref="containerRef"
|
|
344
|
+
@touchend="handleTouchEnd"
|
|
345
|
+
@touchstart.passive="handleTouchStart"
|
|
346
|
+
@wheel="handleWheel"
|
|
287
347
|
>
|
|
288
348
|
<slot />
|
|
289
349
|
</ul>
|
|
290
350
|
|
|
291
351
|
<div
|
|
292
352
|
:class="[
|
|
293
|
-
'tide-carousel-dots-container',
|
|
294
353
|
CSS.POSITION.ABSOLUTE,
|
|
295
354
|
CSS.POSITIONING.BOTTOM,
|
|
296
355
|
CSS.DISPLAY.FLEX,
|
|
297
356
|
CSS.AXIS1.CENTER,
|
|
298
357
|
CSS.MARGIN.BOTTOM.HALF,
|
|
299
358
|
CSS.WIDTH.FULL,
|
|
359
|
+
CSS.POINTER_EVENTS.OFF,
|
|
300
360
|
]"
|
|
301
|
-
|
|
302
|
-
width: `${dotContainerWidth}px`,
|
|
303
|
-
}"
|
|
304
|
-
v-if="isScrollByPage && hasDots && totalPages > 1"
|
|
361
|
+
v-if="hasDots && totalPages > 1"
|
|
305
362
|
>
|
|
306
363
|
<div
|
|
307
|
-
:class="[
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
CSS.AXIS2.CENTER,
|
|
312
|
-
CSS.GAP.HALF,
|
|
313
|
-
CSS.OVERFLOW.X.SCROLL,
|
|
314
|
-
CSS.POINTER_EVENTS.OFF,
|
|
315
|
-
CSS.SCROLLBAR.OFF,
|
|
316
|
-
]"
|
|
317
|
-
ref="dotsRef"
|
|
364
|
+
:class="['tide-carousel-dots-container']"
|
|
365
|
+
:style="{
|
|
366
|
+
width: `${dotContainerWidth}px`,
|
|
367
|
+
}"
|
|
318
368
|
>
|
|
319
369
|
<div
|
|
320
370
|
:class="[
|
|
321
|
-
'tide-carousel-
|
|
322
|
-
CSS.FLEX
|
|
323
|
-
CSS.
|
|
324
|
-
CSS.
|
|
325
|
-
CSS.
|
|
326
|
-
CSS.
|
|
327
|
-
|
|
371
|
+
'tide-carousel-dots',
|
|
372
|
+
CSS.DISPLAY.FLEX,
|
|
373
|
+
CSS.AXIS1.START,
|
|
374
|
+
CSS.AXIS2.CENTER,
|
|
375
|
+
CSS.GAP.HALF,
|
|
376
|
+
CSS.OVERFLOW.X.SCROLL,
|
|
377
|
+
CSS.POINTER_EVENTS.OFF,
|
|
378
|
+
CSS.SCROLLBAR.OFF,
|
|
328
379
|
]"
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
380
|
+
ref="dotsRef"
|
|
381
|
+
>
|
|
382
|
+
<div
|
|
383
|
+
:class="[
|
|
384
|
+
'tide-carousel-dot',
|
|
385
|
+
CSS.FLEX.SHRINK.OFF,
|
|
386
|
+
CSS.HEIGHT.ZERO,
|
|
387
|
+
CSS.WIDTH.ZERO,
|
|
388
|
+
CSS.BORDER.RADIUS.FULL,
|
|
389
|
+
CSS.BG.SURFACE.DEFAULT,
|
|
390
|
+
getDotClass(index),
|
|
391
|
+
]"
|
|
392
|
+
:key="index"
|
|
393
|
+
v-for="(_, index) in totalPages"
|
|
394
|
+
/>
|
|
395
|
+
</div>
|
|
332
396
|
</div>
|
|
333
397
|
</div>
|
|
334
398
|
|
|
@@ -320,7 +320,7 @@
|
|
|
320
320
|
|
|
321
321
|
.tide-input-text:focus-within .tide-input-text-field {
|
|
322
322
|
--tide-input-outline-width: var(--tide-border-width-2);
|
|
323
|
-
outline-color: var(--tide-
|
|
323
|
+
outline-color: var(--tide-border-high);
|
|
324
324
|
}
|
|
325
325
|
|
|
326
326
|
.tide-input-text input {
|
|
@@ -331,6 +331,6 @@
|
|
|
331
331
|
--tide-input-outline-width: var(--tide-border-width-1);
|
|
332
332
|
outline: var(--tide-input-outline-width) solid var(--tide-border);
|
|
333
333
|
outline-offset: calc(var(--tide-input-outline-width) * -1);
|
|
334
|
-
color: var(--tide-surface
|
|
334
|
+
color: var(--tide-on-surface);
|
|
335
335
|
}
|
|
336
336
|
</style>
|
|
@@ -311,7 +311,7 @@
|
|
|
311
311
|
|
|
312
312
|
.tide-input-text:focus-within .tide-input-text-field {
|
|
313
313
|
--tide-input-outline-width: var(--tide-border-width-2);
|
|
314
|
-
outline-color: var(--tide-
|
|
314
|
+
outline-color: var(--tide-border-high);
|
|
315
315
|
}
|
|
316
316
|
|
|
317
317
|
.tide-input-text input {
|
|
@@ -322,6 +322,6 @@
|
|
|
322
322
|
--tide-input-outline-width: var(--tide-border-width-1);
|
|
323
323
|
outline: var(--tide-input-outline-width) solid var(--tide-border);
|
|
324
324
|
outline-offset: calc(var(--tide-input-outline-width) * -1);
|
|
325
|
-
color: var(--tide-surface
|
|
325
|
+
color: var(--tide-on-surface);
|
|
326
326
|
}
|
|
327
327
|
</style>
|
|
@@ -189,7 +189,7 @@
|
|
|
189
189
|
|
|
190
190
|
.tide-input-textarea:focus-within .tide-input-textarea-field {
|
|
191
191
|
--tide-input-outline-width: var(--tide-border-width-2);
|
|
192
|
-
outline-color: var(--tide-
|
|
192
|
+
outline-color: var(--tide-border-high);
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
.tide-input-textarea textarea {
|
|
@@ -200,6 +200,6 @@
|
|
|
200
200
|
--tide-input-outline-width: var(--tide-border-width-1);
|
|
201
201
|
outline: var(--tide-input-outline-width) solid var(--tide-border);
|
|
202
202
|
outline-offset: calc(var(--tide-input-outline-width) * -1);
|
|
203
|
-
color: var(--tide-surface
|
|
203
|
+
color: var(--tide-on-surface);
|
|
204
204
|
}
|
|
205
205
|
</style>
|
|
@@ -182,7 +182,7 @@
|
|
|
182
182
|
|
|
183
183
|
.tide-input-textarea:focus-within .tide-input-textarea-field {
|
|
184
184
|
--tide-input-outline-width: var(--tide-border-width-2);
|
|
185
|
-
outline-color: var(--tide-
|
|
185
|
+
outline-color: var(--tide-border-high);
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
.tide-input-textarea textarea {
|
|
@@ -193,6 +193,6 @@
|
|
|
193
193
|
--tide-input-outline-width: var(--tide-border-width-1);
|
|
194
194
|
outline: var(--tide-input-outline-width) solid var(--tide-border);
|
|
195
195
|
outline-offset: calc(var(--tide-input-outline-width) * -1);
|
|
196
|
-
color: var(--tide-surface
|
|
196
|
+
color: var(--tide-on-surface);
|
|
197
197
|
}
|
|
198
198
|
</style>
|