tide-design-system 2.4.5 → 2.5.0

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.
Files changed (66) hide show
  1. package/.storybook/main.ts +1 -0
  2. package/dist/css/reset.css +1 -1
  3. package/dist/css/utilities-responsive.css +0 -546
  4. package/dist/style.css +1 -1
  5. package/dist/tide-design-system.cjs +2 -2
  6. package/dist/tide-design-system.esm.d.ts +171 -73
  7. package/dist/tide-design-system.esm.js +1725 -1594
  8. package/dist/utilities/storybook.ts +6 -2
  9. package/dist/utilities/validation.ts +1 -1
  10. package/docs/upgrading.md +79 -0
  11. package/index.ts +8 -5
  12. package/package.json +1 -1
  13. package/src/assets/css/reset.css +1 -1
  14. package/src/assets/css/utilities-responsive.css +0 -546
  15. package/src/components/InternalBaseLink.vue +11 -0
  16. package/src/components/TideAccordionItem.vue +21 -13
  17. package/src/components/TideBreadCrumbs.vue +3 -2
  18. package/src/components/TideButton.vue +17 -4
  19. package/src/components/TideButtonIcon.vue +15 -2
  20. package/src/components/TideButtonPagination.vue +16 -16
  21. package/src/components/TideButtonSegmented.vue +15 -15
  22. package/src/components/TideCard.vue +12 -2
  23. package/src/components/TideCarousel.vue +10 -5
  24. package/src/components/TideChipAction.vue +7 -1
  25. package/src/components/TideChipFilter.vue +14 -7
  26. package/src/components/TideChipInput.vue +1 -0
  27. package/src/components/TideIcon.vue +8 -9
  28. package/src/components/TideImage.vue +9 -9
  29. package/src/components/TideInputCheckbox.vue +0 -1
  30. package/src/components/TideInputText.vue +2 -0
  31. package/src/components/TideInputTextDeprecated.vue +2 -0
  32. package/src/components/TideInputTextarea.vue +2 -2
  33. package/src/components/TideLink.vue +7 -1
  34. package/src/components/TideMenuItem.vue +83 -0
  35. package/src/components/TideModal.vue +120 -101
  36. package/src/components/TidePagination.vue +17 -19
  37. package/src/components/TideSeoLinks.vue +3 -2
  38. package/src/components/TideSheet.vue +28 -23
  39. package/src/components/TideSwitch.vue +24 -20
  40. package/src/composables/useTideConfig.ts +23 -0
  41. package/src/stories/TideAccordionItem.stories.ts +33 -34
  42. package/src/stories/TideButtonPagination.stories.ts +6 -6
  43. package/src/stories/TideButtonSegmented.stories.ts +33 -25
  44. package/src/stories/TideCarousel.stories.ts +0 -1
  45. package/src/stories/TideChipFilter.stories.ts +33 -23
  46. package/src/stories/TideInputCheckbox.stories.ts +66 -23
  47. package/src/stories/TideInputRadio.stories.ts +39 -30
  48. package/src/stories/TideInputSelect.stories.ts +51 -27
  49. package/src/stories/TideInputText.stories.ts +83 -23
  50. package/src/stories/TideInputTextarea.stories.ts +66 -17
  51. package/src/stories/TideLink.stories.ts +1 -14
  52. package/src/stories/TideMenuItem.stories.ts +117 -0
  53. package/src/stories/TideModal.stories.ts +33 -37
  54. package/src/stories/TidePagination.stories.ts +33 -22
  55. package/src/stories/TidePopover.stories.ts +1 -1
  56. package/src/stories/TideSheet.stories.ts +33 -28
  57. package/src/stories/TideSwitch.stories.ts +33 -34
  58. package/src/types/Badge.ts +4 -0
  59. package/src/types/Element.ts +2 -2
  60. package/src/types/Formatted.ts +1 -0
  61. package/src/types/Storybook.ts +4 -6
  62. package/src/types/Type.ts +6 -0
  63. package/src/types/Validation.ts +1 -0
  64. package/src/utilities/storybook.ts +6 -2
  65. package/src/utilities/validation.ts +1 -1
  66. package/tests/InternalBaseLink.spec.ts +61 -0
@@ -0,0 +1,83 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Renders a generic menu item intended to be used in navigation menus or dropdowns.
4
+ *
5
+ * @see the [Storybook interface](https://tide-design-system.netlify.app/?path=/docs/components-tidemenuitem--docs) for TideMenuItem
6
+ */
7
+ export default {};
8
+ </script>
9
+
10
+ <script setup lang="ts">
11
+ import TideIcon from '@/components/TideIcon.vue';
12
+ import { ELEMENT } from '@/types/Element';
13
+ import { SIZE } from '@/types/Size';
14
+ import { CSS } from '@/types/Styles';
15
+ import { TARGET } from '@/types/Target';
16
+
17
+ import type { Element } from '@/types/Element';
18
+ import type { Icon } from '@/types/Icon';
19
+
20
+ type Props = {
21
+ /** The element to render the root element as. */
22
+ element?: Element;
23
+
24
+ /** The href attribute to apply when the element is a link. */
25
+ href?: string;
26
+
27
+ /** Whether to open link in a new tab. */
28
+ isNewTab?: boolean;
29
+
30
+ /** Icon to show after the label. */
31
+ iconTrailing?: Icon;
32
+
33
+ /** The label to be displayed. */
34
+ label: string;
35
+ };
36
+
37
+ withDefaults(defineProps<Props>(), {
38
+ element: ELEMENT.LINK,
39
+ href: undefined,
40
+ iconTrailing: undefined,
41
+ isNewTab: false,
42
+ });
43
+ </script>
44
+
45
+ <template>
46
+ <component
47
+ :class="[
48
+ 'tide-menu-item',
49
+ CSS.AXIS2.CENTER,
50
+ CSS.BORDER.RADIUS.ZERO,
51
+ CSS.DISPLAY.FLEX,
52
+ CSS.ELLIPSIS,
53
+ CSS.FONT.ROLE.LABEL_2,
54
+ CSS.GAP.QUARTER,
55
+ CSS.PADDING.X.ONE,
56
+ CSS.PADDING.Y.HALF,
57
+ CSS.UNDERLINE.REST.OFF,
58
+ CSS.WIDTH.FULL,
59
+ ]"
60
+ :href="href"
61
+ :target="isNewTab ? TARGET.BLANK : undefined"
62
+ :is="element"
63
+ >
64
+ <span>{{ label }}</span>
65
+
66
+ <TideIcon
67
+ :class="[CSS.MARGIN.LEFT.AUTO]"
68
+ :icon="iconTrailing"
69
+ :size="SIZE.LARGE"
70
+ v-if="iconTrailing"
71
+ />
72
+ </component>
73
+ </template>
74
+
75
+ <style scoped>
76
+ .tide-menu-item {
77
+ height: 40px;
78
+ }
79
+
80
+ .tide-menu-item:hover {
81
+ background-color: var(--tide-surface-variant);
82
+ }
83
+ </style>
@@ -1,24 +1,30 @@
1
1
  <script lang="ts" setup>
2
- import { nextTick, onMounted, ref, watch } from 'vue';
2
+ import { nextTick, onBeforeMount, onMounted, ref, watch } from 'vue';
3
3
 
4
4
  import TideButtonIcon from '@/components/TideButtonIcon.vue';
5
5
  import { BREAKPOINT } from '@/types/Breakpoint';
6
6
  import { ICON } from '@/types/Icon';
7
7
  import { PRIORITY } from '@/types/Priority';
8
8
  import { CSS } from '@/types/Styles';
9
- import { setScrollLock } from '@/utilities/viewport';
9
+ import { TOP_LAYER_ID, initFauxTopLayer, setScrollLock } from '@/utilities/viewport';
10
10
 
11
11
  import type { Ref } from 'vue';
12
12
 
13
13
  type Props = {
14
+ /**
15
+ * Called before the modal closes.
16
+ *
17
+ * Return `false` to cancel the close event.
18
+ */
19
+ beforeClose?: () => void | boolean | Promise<void | boolean>;
14
20
  isBackButton?: boolean;
15
21
  isDismissible?: boolean;
16
- isOpen: boolean;
17
22
  title?: string;
18
23
  width?: string;
19
24
  };
20
25
 
21
26
  const props = withDefaults(defineProps<Props>(), {
27
+ beforeClose: undefined,
22
28
  isBackButton: false,
23
29
  isDismissible: true,
24
30
  title: undefined,
@@ -26,7 +32,6 @@
26
32
  });
27
33
 
28
34
  type Emits = {
29
- (e: 'close'): void;
30
35
  (e: 'back'): void;
31
36
  };
32
37
 
@@ -35,6 +40,8 @@
35
40
  const modalContent: Ref<HTMLDivElement | undefined> = ref();
36
41
  const modalDialog: Ref<HTMLDialogElement | undefined> = ref();
37
42
 
43
+ const isOpen = defineModel<boolean>({ required: true });
44
+
38
45
  const triggerNativeDialogOpen = () => {
39
46
  modalDialog.value?.showModal();
40
47
  };
@@ -50,128 +57,140 @@
50
57
  });
51
58
  };
52
59
 
60
+ const close = async () => {
61
+ if (props.beforeClose) {
62
+ const result = await props.beforeClose();
63
+ if (result === false) return;
64
+ }
65
+
66
+ isOpen.value = false;
67
+ };
68
+
53
69
  const handleBackdropClick = () => {
54
- if (props.isDismissible) emit('close');
70
+ if (props.isDismissible) close();
55
71
  };
56
72
 
57
73
  const handleEscapeKeydown = (e: Event) => {
58
74
  e.preventDefault();
59
- if (props.isDismissible) emit('close');
75
+ if (props.isDismissible) close();
60
76
  };
61
77
 
62
- watch(
63
- () => props.isOpen,
64
- (newValue) => {
65
- if (!modalDialog.value) return;
66
- if (newValue) {
67
- triggerNativeDialogOpen();
68
- scrollContentToTop();
69
- } else {
70
- triggerNativeDialogClose();
71
- }
72
- setScrollLock(newValue);
78
+ watch(isOpen, () => {
79
+ if (!modalDialog.value) return;
80
+ if (isOpen.value) {
81
+ triggerNativeDialogOpen();
82
+ scrollContentToTop();
83
+ } else {
84
+ triggerNativeDialogClose();
73
85
  }
74
- );
86
+ setScrollLock(isOpen.value);
87
+ });
88
+
89
+ onBeforeMount(() => {
90
+ initFauxTopLayer();
91
+ });
75
92
 
76
93
  onMounted(() => {
77
- if (props.isOpen) {
94
+ if (isOpen.value) {
78
95
  triggerNativeDialogOpen();
79
96
  }
80
97
  });
81
98
  </script>
82
99
 
83
100
  <template>
84
- <dialog
85
- :class="['tide-modal', CSS.BG.INITIAL, CSS.HEIGHT.FULL, CSS.WIDTH.FULL, CSS.OVERFLOW.XY.HIDDEN]"
86
- ref="modalDialog"
87
- :style="{ '--modal-width': props.width }"
88
- @click.self="handleBackdropClick"
89
- @close.prevent
90
- @keydown.escape="handleEscapeKeydown"
91
- >
92
- <div
93
- :class="[
94
- 'tide-modal-body',
95
- CSS.BG.SURFACE.DEFAULT,
96
- CSS.BORDER.RADIUS.ONE,
97
- CSS.DISPLAY.FLEX,
98
- CSS.FLEX.DIRECTION.COLUMN,
99
- CSS.OVERFLOW.XY.HIDDEN,
100
- CSS.POSITION.ABSOLUTE,
101
- CSS.POSITIONING.BOTTOM,
102
- CSS.SHADOW.TOP,
103
- CSS.WIDTH.FULL,
104
- CSS.WIDTH.MAX_FULL,
105
- CSS.withBreakpoint([CSS.SHADOW.BOTTOM], BREAKPOINT.SM),
106
- ]"
101
+ <Teleport :to="`#${TOP_LAYER_ID}`">
102
+ <dialog
103
+ :class="['tide-modal', CSS.BG.INITIAL, CSS.HEIGHT.FULL, CSS.WIDTH.FULL, CSS.OVERFLOW.XY.HIDDEN]"
104
+ ref="modalDialog"
105
+ :style="{ '--modal-width': width }"
106
+ @click.self="handleBackdropClick"
107
+ @close.prevent
108
+ @keydown.escape="handleEscapeKeydown"
107
109
  >
108
- <header
110
+ <div
109
111
  :class="[
110
- 'tide-modal-header',
111
- CSS.AXIS2.CENTER,
112
- CSS.BORDER.BOTTOM.ONE,
113
- CSS.BORDER.COLOR.LOW,
112
+ 'tide-modal-body',
113
+ CSS.BG.SURFACE.DEFAULT,
114
+ CSS.BORDER.RADIUS.ONE,
114
115
  CSS.DISPLAY.FLEX,
115
- CSS.GAP.HALF,
116
- CSS.ISOLATION.ISOLATE,
117
- CSS.PADDING.Y.ONE,
118
- CSS.POSITION.RELATIVE,
119
- CSS.Z_INDEX.ONE,
116
+ CSS.FLEX.DIRECTION.COLUMN,
117
+ CSS.OVERFLOW.XY.HIDDEN,
118
+ CSS.POSITION.ABSOLUTE,
119
+ CSS.POSITIONING.BOTTOM,
120
+ CSS.SHADOW.TOP,
121
+ CSS.WIDTH.FULL,
122
+ CSS.WIDTH.MAX_FULL,
123
+ CSS.withBreakpoint([CSS.SHADOW.BOTTOM], BREAKPOINT.SM),
120
124
  ]"
121
125
  >
122
- <TideButtonIcon
123
- :icon="ICON.CHEVRON_LEFT"
124
- :priority="PRIORITY.QUATERNARY"
125
- @click="emit('back')"
126
- title="Back"
127
- v-if="isBackButton"
128
- />
126
+ <header
127
+ :class="[
128
+ 'tide-modal-header',
129
+ CSS.AXIS2.CENTER,
130
+ CSS.BORDER.BOTTOM.ONE,
131
+ CSS.BORDER.COLOR.LOW,
132
+ CSS.DISPLAY.FLEX,
133
+ CSS.GAP.HALF,
134
+ CSS.ISOLATION.ISOLATE,
135
+ CSS.PADDING.Y.ONE,
136
+ CSS.POSITION.RELATIVE,
137
+ CSS.Z_INDEX.ONE,
138
+ ]"
139
+ >
140
+ <TideButtonIcon
141
+ :icon="ICON.CHEVRON_LEFT"
142
+ :priority="PRIORITY.QUATERNARY"
143
+ @click="emit('back')"
144
+ title="Back"
145
+ v-if="isBackButton"
146
+ />
147
+
148
+ <div
149
+ :class="[CSS.FONT.ROLE.HEADLINE_2]"
150
+ v-text="title"
151
+ />
152
+
153
+ <TideButtonIcon
154
+ :class="[CSS.FLEX.GROW.OFF, CSS.FLEX.SHRINK.OFF, CSS.MARGIN.LEFT.AUTO]"
155
+ :icon="ICON.CLOSE"
156
+ :priority="PRIORITY.QUATERNARY"
157
+ @click="close"
158
+ v-if="isDismissible"
159
+ />
160
+ </header>
129
161
 
130
162
  <div
131
- :class="[CSS.FONT.ROLE.HEADLINE_2]"
132
- v-text="title"
133
- />
134
-
135
- <TideButtonIcon
136
- :class="[CSS.FLEX.GROW.OFF, CSS.FLEX.SHRINK.OFF, CSS.MARGIN.LEFT.AUTO]"
137
- :icon="ICON.CLOSE"
138
- :priority="PRIORITY.QUATERNARY"
139
- @click="emit('close')"
140
- v-if="isDismissible"
141
- />
142
- </header>
163
+ :class="[
164
+ 'tide-modal-content',
165
+ CSS.DISPLAY.GRID,
166
+ CSS.ISOLATION.ISOLATE,
167
+ CSS.OVERFLOW.X.HIDDEN,
168
+ CSS.OVERFLOW.Y.AUTO,
169
+ CSS.PADDING.Y.TWO,
170
+ CSS.WIDTH.FULL,
171
+ ]"
172
+ ref="modalContent"
173
+ >
174
+ <slot />
175
+ </div>
143
176
 
144
- <div
145
- :class="[
146
- 'tide-modal-content',
147
- CSS.DISPLAY.GRID,
148
- CSS.ISOLATION.ISOLATE,
149
- CSS.OVERFLOW.X.HIDDEN,
150
- CSS.OVERFLOW.Y.AUTO,
151
- CSS.PADDING.Y.TWO,
152
- CSS.WIDTH.FULL,
153
- ]"
154
- ref="modalContent"
155
- >
156
- <slot />
177
+ <footer
178
+ :class="[
179
+ 'tide-modal-footer',
180
+ CSS.AXIS1.END,
181
+ CSS.DISPLAY.FLEX,
182
+ CSS.GAP.TWO,
183
+ CSS.ISOLATION.ISOLATE,
184
+ CSS.PADDING.Y.ONE,
185
+ CSS.SHADOW.TOP,
186
+ ]"
187
+ v-if="$slots.footer"
188
+ >
189
+ <slot name="footer" />
190
+ </footer>
157
191
  </div>
158
-
159
- <footer
160
- :class="[
161
- 'tide-modal-footer',
162
- CSS.AXIS1.END,
163
- CSS.DISPLAY.FLEX,
164
- CSS.GAP.TWO,
165
- CSS.ISOLATION.ISOLATE,
166
- CSS.PADDING.Y.ONE,
167
- CSS.SHADOW.TOP,
168
- ]"
169
- v-if="$slots.footer"
170
- >
171
- <slot name="footer" />
172
- </footer>
173
- </div>
174
- </dialog>
192
+ </dialog>
193
+ </Teleport>
175
194
  </template>
176
195
 
177
196
  <style scoped>
@@ -1,5 +1,5 @@
1
1
  <script lang="ts" setup>
2
- import { computed, ref } from 'vue';
2
+ import { computed } from 'vue';
3
3
 
4
4
  import TideButtonIcon from '@/components/TideButtonIcon.vue';
5
5
  import TideButtonPagination from '@/components/TideButtonPagination.vue';
@@ -8,25 +8,25 @@
8
8
  import { CSS } from '@/types/Styles';
9
9
 
10
10
  type Props = {
11
- pageCurrent?: number;
12
- pageTotal?: number;
11
+ pageTotal: number;
13
12
  };
14
13
 
15
- const props = withDefaults(defineProps<Props>(), {
16
- pageCurrent: 1,
17
- pageTotal: 1,
18
- });
14
+ const props = defineProps<Props>();
19
15
 
20
- const emit = defineEmits(['change']);
21
-
22
- const pageCurrent = ref(props.pageCurrent);
16
+ const currentIndex = defineModel<number>({ required: true });
23
17
 
24
18
  const paginationButtons = computed(() => new Array(props.pageTotal).fill('').map((empty, index) => index + 1));
25
19
 
26
20
  const handleClick = (index: number) => {
27
- pageCurrent.value = index;
21
+ currentIndex.value = index;
22
+ };
28
23
 
29
- emit('change', event, index);
24
+ const handlePreviousClick = () => {
25
+ currentIndex.value--;
26
+ };
27
+
28
+ const handleNextClick = () => {
29
+ currentIndex.value++;
30
30
  };
31
31
  </script>
32
32
 
@@ -35,10 +35,10 @@
35
35
  :class="['tide-pagination', CSS.DISPLAY.FLEX, CSS.AXIS1.CENTER, CSS.AXIS2.CENTER, CSS.GAP.QUARTER, CSS.WIDTH.FULL]"
36
36
  >
37
37
  <TideButtonIcon
38
- :disabled="pageCurrent === 1"
38
+ :disabled="currentIndex <= 1"
39
39
  :icon="ICON.CHEVRON_LEFT"
40
40
  :priority="PRIORITY.QUATERNARY"
41
- @click="handleClick(pageCurrent - 1)"
41
+ @click="handlePreviousClick"
42
42
  />
43
43
 
44
44
  <ul :class="[CSS.DISPLAY.FLEX, CSS.AXIS2.CENTER, CSS.GAP.QUARTER, CSS.LIST_BULLETS.OFF]">
@@ -47,7 +47,7 @@
47
47
  v-for="paginationButton in paginationButtons"
48
48
  >
49
49
  <TideButtonPagination
50
- :disabled="pageCurrent === paginationButton"
50
+ :disabled="currentIndex === paginationButton"
51
51
  :label="paginationButton"
52
52
  :priority="PRIORITY.QUATERNARY"
53
53
  @click="handleClick(paginationButton)"
@@ -56,12 +56,10 @@
56
56
  </ul>
57
57
 
58
58
  <TideButtonIcon
59
- :disabled="pageCurrent === paginationButtons[paginationButtons.length - 1]"
59
+ :disabled="currentIndex >= paginationButtons[paginationButtons.length - 1]"
60
60
  :icon="ICON.CHEVRON_RIGHT"
61
61
  :priority="PRIORITY.QUATERNARY"
62
- @click="handleClick(pageCurrent + 1)"
62
+ @click="handleNextClick"
63
63
  />
64
64
  </section>
65
65
  </template>
66
-
67
- <style scoped></style>
@@ -1,4 +1,5 @@
1
1
  <script lang="ts" setup>
2
+ import InternalBaseLink from '@/components/InternalBaseLink.vue';
2
3
  import { CSS } from '@/types/Styles';
3
4
  import { TARGET } from '@/types/Target';
4
5
 
@@ -26,13 +27,13 @@
26
27
  :key="link.label"
27
28
  v-for="link in props.links"
28
29
  >
29
- <a
30
+ <InternalBaseLink
30
31
  :class="[CSS.UNDERLINE.REST.OFF]"
31
32
  :href="link.url"
32
33
  :target="link.isNewTab ? TARGET.BLANK : TARGET.SELF"
33
34
  >
34
35
  {{ link.label }}
35
- </a>
36
+ </InternalBaseLink>
36
37
  </li>
37
38
  </ul>
38
39
  </section>
@@ -2,30 +2,32 @@
2
2
  import { onBeforeMount, onMounted, ref, watch } from 'vue';
3
3
 
4
4
  import TideButtonIcon from '@/components/TideButtonIcon.vue';
5
+ import TideDivider from '@/components/TideDivider.vue';
5
6
  import { ICON } from '@/types/Icon';
6
7
  import { PRIORITY } from '@/types/Priority';
7
8
  import { CSS } from '@/types/Styles';
8
9
  import { TOP_LAYER_ID, initFauxTopLayer, setScrollLock } from '@/utilities/viewport';
9
10
 
10
- import TideDivider from './TideDivider.vue';
11
-
12
11
  import type { Ref } from 'vue';
13
12
 
14
13
  type Props = {
15
- isOpen: boolean;
16
14
  isBackButton?: boolean;
17
15
  };
18
- const props = defineProps<Props>();
16
+
17
+ withDefaults(defineProps<Props>(), {
18
+ isBackButton: false,
19
+ });
19
20
 
20
21
  type Emits = {
21
22
  (e: 'back'): void;
22
- (e: 'close'): void;
23
23
  };
24
24
 
25
25
  const emit = defineEmits<Emits>();
26
26
 
27
27
  const dialogElement: Ref<HTMLDialogElement | null> = ref(null);
28
28
 
29
+ const isOpen = defineModel<boolean>({ required: true });
30
+
29
31
  const triggerNativeDialogOpen = () => {
30
32
  dialogElement.value?.showModal();
31
33
  };
@@ -34,34 +36,35 @@
34
36
  dialogElement.value?.close();
35
37
  };
36
38
 
39
+ const close = () => {
40
+ isOpen.value = false;
41
+ };
42
+
37
43
  const handleBackdropClick = () => {
38
- emit('close');
44
+ close();
39
45
  };
40
46
 
41
47
  const handleEscapeKeydown = (e: Event) => {
42
48
  e.preventDefault();
43
- emit('close');
49
+ close();
44
50
  };
45
51
 
46
- watch(
47
- () => props.isOpen,
48
- (newValue) => {
49
- if (!dialogElement.value) return;
50
- if (newValue) {
51
- triggerNativeDialogOpen();
52
- } else {
53
- triggerNativeDialogClose();
54
- }
55
- setScrollLock(newValue);
52
+ watch(isOpen, () => {
53
+ if (!dialogElement.value) return;
54
+ if (isOpen.value) {
55
+ triggerNativeDialogOpen();
56
+ } else {
57
+ triggerNativeDialogClose();
56
58
  }
57
- );
59
+ setScrollLock(isOpen.value);
60
+ });
58
61
 
59
62
  onBeforeMount(() => {
60
63
  initFauxTopLayer();
61
64
  });
62
65
 
63
66
  onMounted(() => {
64
- if (props.isOpen) {
67
+ if (isOpen.value) {
65
68
  triggerNativeDialogOpen();
66
69
  }
67
70
  });
@@ -109,7 +112,7 @@
109
112
  :class="[CSS.FLEX.GROW.OFF, CSS.FLEX.SHRINK.OFF, CSS.MARGIN.LEFT.AUTO]"
110
113
  :icon="ICON.CLOSE"
111
114
  :priority="PRIORITY.QUATERNARY"
112
- @click="emit('close')"
115
+ @click="close"
113
116
  />
114
117
  </header>
115
118
 
@@ -119,11 +122,13 @@
119
122
  :class="[
120
123
  'tide-sheet-content',
121
124
  CSS.DISPLAY.GRID,
122
- CSS.OVERFLOW.Y.AUTO,
125
+ CSS.HEIGHT.FULL,
126
+ CSS.ISOLATION.ISOLATE,
123
127
  CSS.OVERFLOW.X.HIDDEN,
124
- CSS.WIDTH.FULL,
128
+ CSS.OVERFLOW.Y.AUTO,
125
129
  CSS.PADDING.BOTTOM.ONE,
126
- CSS.ISOLATION.ISOLATE,
130
+ CSS.POSITION.RELATIVE,
131
+ CSS.WIDTH.FULL,
127
132
  ]"
128
133
  >
129
134
  <slot />
@@ -6,19 +6,17 @@
6
6
 
7
7
  type Props = {
8
8
  disabled?: boolean;
9
- isActive?: boolean;
10
9
  };
11
10
 
12
11
  const props = withDefaults(defineProps<Props>(), {
13
12
  disabled: false,
14
- isActive: false,
15
13
  });
16
14
 
17
- const emit = defineEmits(['change']);
15
+ const isActive = defineModel<boolean>({ required: true });
18
16
 
19
17
  const handleClick = () => {
20
18
  if (!props.disabled) {
21
- emit('change');
19
+ isActive.value = !isActive.value;
22
20
  }
23
21
  };
24
22
  </script>
@@ -27,18 +25,20 @@
27
25
  <button
28
26
  :class="[
29
27
  'tide-switch',
30
- props.isActive ? 'active' : '',
31
28
  CSS.DISPLAY.FLEX,
32
29
  CSS.AXIS2.CENTER,
33
30
  CSS.BORDER.FULL.ONE,
34
31
  CSS.BORDER.RADIUS.FULL,
35
- props.isActive ? '' : CSS.BORDER.COLOR.DEFAULT,
36
32
  CSS.OVERFLOW.XY.HIDDEN,
37
- props.isActive ? CSS.BG.SECONDARY : CSS.BG.SURFACE.DEFAULT,
33
+ CSS.FLEX.SHRINK.OFF,
34
+ CSS.FLEX.GROW.OFF,
35
+ isActive ? ['active', CSS.BG.SECONDARY] : [CSS.BG.SURFACE.DEFAULT, CSS.BORDER.COLOR.DEFAULT],
36
+ disabled && [CSS.OPACITY.DISABLED],
38
37
  ]"
39
38
  :disabled="props.disabled"
40
39
  @click="handleClick"
41
40
  aria-label="Switch"
41
+ type="button"
42
42
  >
43
43
  <div
44
44
  :class="[
@@ -48,7 +48,7 @@
48
48
  CSS.AXIS1.CENTER,
49
49
  CSS.AXIS2.CENTER,
50
50
  CSS.BORDER.RADIUS.FULL,
51
- props.isActive ? CSS.BG.SURFACE.DEFAULT : CSS.BG.SECONDARY,
51
+ isActive ? ['active', CSS.BG.SURFACE.DEFAULT] : [CSS.BG.SECONDARY],
52
52
  ]"
53
53
  >
54
54
  <TideIcon
@@ -61,27 +61,31 @@
61
61
 
62
62
  <style scoped>
63
63
  .tide-switch {
64
- width: 64px;
65
- height: 32px;
66
- transition: var(--tide-animate);
67
- transition-property: background-color;
68
- }
64
+ --switch-indicator-size: 24px;
65
+ --switch-indicator-margin: var(--tide-spacing-1\/4);
66
+ --switch-border-width: var(--tide-border-width-1);
67
+ --switch-width: calc(var(--switch-indicator-size) * 2 + var(--switch-indicator-margin) * 4);
68
+ --switch-height: calc(var(--switch-indicator-size) + var(--switch-indicator-margin) * 2);
69
69
 
70
- .tide-switch:disabled {
71
- opacity: var(--tide-opacity);
70
+ width: var(--switch-width);
71
+ height: var(--switch-height);
72
+ transition: var(--tide-animate);
73
+ transition-property: background-color, border-color;
72
74
  }
73
75
 
74
76
  .tide-switch.active {
75
77
  border-color: var(--tide-secondary);
76
78
  }
77
79
 
78
- .tide-switch.active .tide-switch-indicator {
79
- left: 36px;
80
- }
81
-
82
80
  .tide-switch-indicator {
83
- left: 4px;
81
+ left: var(--switch-indicator-margin);
84
82
  transition: var(--tide-animate);
85
83
  transition-property: left, border-color, background-color;
86
84
  }
85
+
86
+ .tide-switch-indicator.active {
87
+ left: calc(
88
+ var(--switch-width) - var(--switch-indicator-size) - var(--switch-indicator-margin) - var(--switch-border-width)
89
+ );
90
+ }
87
91
  </style>