@dpa-id-components/dpa-shared-components 22.0.0-next.2 → 22.0.0-next.4

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.
@@ -1,5 +1,10 @@
1
1
  <template>
2
- <UiPopover :initially-open="isOpen" :animate :placement="floatingUiPlacement">
2
+ <UiPopover
3
+ :initially-open="isOpen"
4
+ :animate
5
+ :placement="floatingUiPlacement"
6
+ @toggle="$event ? $emit('open') : $emit('close')"
7
+ >
3
8
  <template #button="{ toggle, isOpen: isPopoverOpen }">
4
9
  <UiButton
5
10
  class="w-full justify-between"
@@ -10,10 +15,7 @@
10
15
  :disabled
11
16
  :size="filterButtonSize"
12
17
  data-testid="menu-button"
13
- @click="
14
- toggle();
15
- isPopoverOpen ? $emit('close') : $emit('open');
16
- "
18
+ @click="toggle"
17
19
  >
18
20
  <div class="flex items-center gap-2">
19
21
  <UiIcon v-if="iconLeft" :name="iconLeft" size="sm" />
@@ -23,106 +25,100 @@
23
25
  </UiButton>
24
26
  </template>
25
27
 
26
- <template #default="{ close }">
28
+ <div
29
+ class="block w-max max-w-full divide-y overflow-hidden rounded-sm text-base/6 shadow-lg focus:outline-hidden sm:text-sm/5"
30
+ >
27
31
  <div
28
- class="block w-max max-w-full divide-y overflow-hidden rounded-sm text-base/6 shadow-lg focus:outline-hidden sm:text-sm/5"
32
+ v-if="hasSearch"
33
+ class="flex items-center gap-2 rounded-t-sm px-4 py-2 text-neutral-primary [&:has(>input:focus-visible)]:focus-outline [&:has(>input:focus-visible)]:-outline-offset-2"
29
34
  >
30
- <div
31
- v-if="hasSearch"
32
- class="flex items-center gap-2 rounded-t-sm px-4 py-2 text-neutral-primary [&:has(>input:focus-visible)]:focus-outline [&:has(>input:focus-visible)]:-outline-offset-2"
33
- >
34
- <UiIcon
35
- class="shrink-0 text-neutral-emphasis"
36
- name="search"
37
- size="sm"
38
- />
39
- <input
40
- ref="search-input"
41
- v-model="queryModel"
42
- type="text"
43
- spellcheck="false"
44
- class="w-full text-sm focus:outline-hidden"
45
- :placeholder="searchPlaceholder"
46
- data-testid="menu-search-input"
47
- />
48
- </div>
35
+ <UiIcon
36
+ class="shrink-0 text-neutral-emphasis"
37
+ name="search"
38
+ size="sm"
39
+ />
40
+ <input
41
+ ref="search-input"
42
+ v-model="queryModel"
43
+ type="text"
44
+ spellcheck="false"
45
+ class="w-full text-sm focus:outline-hidden"
46
+ :placeholder="searchPlaceholder"
47
+ data-testid="menu-search-input"
48
+ />
49
+ </div>
49
50
 
50
- <slot
51
- v-bind="{
52
- checkboxAppearance,
53
- checkboxSize,
54
- iconSize,
55
- imageShape,
56
- listVariant,
57
- options,
58
- groupedOptions,
59
- }"
51
+ <slot
52
+ v-bind="{
53
+ checkboxAppearance,
54
+ checkboxSize,
55
+ iconSize,
56
+ imageShape,
57
+ listVariant,
58
+ options,
59
+ groupedOptions,
60
+ }"
61
+ >
62
+ <ul
63
+ v-if="processedOptions.some(({ options }) => options.length > 0)"
64
+ class="max-h-80 overflow-y-auto"
65
+ data-testid="menu-option-list"
60
66
  >
61
- <ul
62
- v-if="processedOptions.some(({ options }) => options.length > 0)"
63
- class="max-h-80 overflow-y-auto"
64
- data-testid="menu-option-list"
67
+ <template
68
+ v-for="group in processedOptions"
69
+ :key="`group-${group.groupLabel ?? 'default'}`"
65
70
  >
66
- <template
67
- v-for="group in processedOptions"
68
- :key="`group-${group.groupLabel ?? 'default'}`"
69
- >
70
- <template v-if="group.options.length > 0">
71
- <li
72
- v-if="group.groupLabel"
73
- class="flex h-6 items-center bg-neutral-whisper px-4 text-xs font-semibold tracking-wider text-neutral-subtle uppercase"
74
- >
75
- {{ group.groupLabel }}
76
- </li>
71
+ <template v-if="group.options.length > 0">
72
+ <li
73
+ v-if="group.groupLabel"
74
+ class="flex h-6 items-center bg-neutral-whisper px-4 text-xs font-semibold tracking-wider text-neutral-subtle uppercase"
75
+ >
76
+ {{ group.groupLabel }}
77
+ </li>
77
78
 
78
- <UiListItem
79
- v-for="(option, index) in group.options"
80
- :key="`option-${option.value}`"
81
- :selected="option.selected"
82
- :selectable="listVariant === 'selectable'"
83
- :is-checked="option.selected"
84
- :check-box-menu="listVariant === 'checkbox'"
85
- :icon-size="iconSize"
86
- :image-shape="imageShape"
87
- :image-src="option.imageSrc"
88
- :icon-name="option.iconName"
89
- :checkbox-size="checkboxSize"
90
- :checkbox-appearance="checkboxAppearance"
91
- class="hover:bg-neutral-whisper focus:bg-neutral-faint"
92
- :class="{
93
- 'border-t': option.hasDividerAbove,
94
- }"
95
- :data-testid="`menu-option-button-${index}`"
96
- @list-item-click="
97
- (value, shouldCloseMenu) => {
98
- selectOption(option);
99
- if (shouldCloseMenu) {
100
- close();
101
- }
102
- }
103
- "
104
- >{{ option.label }}</UiListItem
105
- >
106
- </template>
79
+ <UiListItem
80
+ v-for="(option, index) in group.options"
81
+ :key="`option-${option.value}`"
82
+ :selected="option.selected"
83
+ :selectable="listVariant === 'selectable'"
84
+ :is-checked="option.selected"
85
+ :check-box-menu="listVariant === 'checkbox'"
86
+ :icon-size="iconSize"
87
+ :image-shape="imageShape"
88
+ :image-src="option.imageSrc"
89
+ :icon-name="option.iconName"
90
+ :checkbox-size="checkboxSize"
91
+ :checkbox-appearance="checkboxAppearance"
92
+ class="hover:bg-neutral-whisper focus:bg-neutral-faint"
93
+ :class="{
94
+ 'border-t': option.hasDividerAbove,
95
+ }"
96
+ :data-testid="`menu-option-button-${index}`"
97
+ @list-item-click="selectOption(option)"
98
+ >{{ option.label }}</UiListItem
99
+ >
107
100
  </template>
108
- </ul>
109
- </slot>
101
+ </template>
102
+ </ul>
103
+ </slot>
110
104
 
111
- <div v-if="hasResetOption" class="px-4 py-2 text-neutral-primary">
112
- <UiButton
113
- appearance="secondary"
114
- :disabled="disabledReset"
115
- icon-name="reset"
116
- size="xs"
117
- data-testid="menu-search-reset-button"
118
- @click="$emit('reset')"
119
- >
120
- <UiIcon name="reset" />
121
- {{ resetLabel }}
122
- </UiButton>
123
- </div>
105
+ <div v-if="hasResetOption" class="px-4 py-2 text-neutral-primary">
106
+ <UiButton
107
+ appearance="secondary"
108
+ :disabled="disabledReset"
109
+ icon-name="reset"
110
+ size="xs"
111
+ data-testid="menu-search-reset-button"
112
+ @click="
113
+ queryModel = '';
114
+ $emit('reset');
115
+ "
116
+ >
117
+ <UiIcon name="reset" />
118
+ {{ resetLabel }}
119
+ </UiButton>
124
120
  </div>
125
- </template>
121
+ </div>
126
122
  </UiPopover>
127
123
  </template>
128
124
 
@@ -1,5 +1,16 @@
1
1
  <template>
2
- <div ref="reference" v-click-away="close" class="w-fit" @click="update">
2
+ <div
3
+ ref="reference"
4
+ v-click-away="
5
+ () => {
6
+ if (isOpen) {
7
+ close();
8
+ }
9
+ }
10
+ "
11
+ class="w-fit"
12
+ @click="update"
13
+ >
3
14
  <slot name="button" v-bind="{ open, close, toggle, isOpen }" />
4
15
 
5
16
  <Transition :name="animate ? 'fade-up' : 'none'">
@@ -25,7 +36,7 @@ import {
25
36
  shift,
26
37
  useFloating,
27
38
  } from "@floating-ui/vue";
28
- import { ref, useTemplateRef } from "vue";
39
+ import { ref, useTemplateRef, watch } from "vue";
29
40
 
30
41
  import { vClickAway } from "../../directives/vClickAway.ts";
31
42
 
@@ -61,9 +72,12 @@ const {
61
72
  placement?: Placement;
62
73
  }>();
63
74
 
75
+ const emit = defineEmits<{
76
+ toggle: [isOpen: boolean];
77
+ }>();
78
+
64
79
  const reference = useTemplateRef("reference");
65
80
  const floating = useTemplateRef("floating");
66
- const isOpen = ref(initiallyOpen);
67
81
 
68
82
  const { floatingStyles, update } = useFloating(reference, floating, {
69
83
  placement,
@@ -71,6 +85,12 @@ const { floatingStyles, update } = useFloating(reference, floating, {
71
85
  whileElementsMounted: autoUpdate,
72
86
  });
73
87
 
88
+ const isOpen = ref(initiallyOpen);
89
+
90
+ watch(isOpen, (isOpen) => {
91
+ emit("toggle", isOpen);
92
+ });
93
+
74
94
  function open() {
75
95
  isOpen.value = true;
76
96
  }
@@ -1,19 +1,28 @@
1
- import type { Meta, StoryObj } from "@storybook/vue3-vite";
1
+ import type {
2
+ ComponentPropsAndSlots,
3
+ Meta,
4
+ StoryObj,
5
+ } from "@storybook/vue3-vite";
2
6
  import { ref } from "vue";
3
7
 
4
8
  import UiRadioButton from "./UiRadioButton.vue";
5
9
 
10
+ type PropsAndCustomArgs = ComponentPropsAndSlots<typeof UiRadioButton> & {
11
+ name?: string;
12
+ disabled?: boolean;
13
+ };
14
+
6
15
  const meta = {
7
16
  title: "forms/UiRadioButton",
8
17
  component: UiRadioButton,
9
18
  argTypes: {
10
19
  // Attributes
11
- // @ts-ignore Storybook doesn't know that **any** component can receive arbitrary attributes and enforces this on the type level.
20
+ name: { control: "text" },
12
21
  disabled: { control: "boolean" },
13
22
  },
14
23
  args: {
15
24
  // Attributes
16
- // @ts-ignore Storybook doesn't know that **any** component can receive arbitrary attributes and enforces this on the type level.
25
+ name: "name",
17
26
  disabled: false,
18
27
 
19
28
  // Props
@@ -24,10 +33,10 @@ const meta = {
24
33
  default: "Radio",
25
34
  errors: "",
26
35
  },
27
- } satisfies Meta<typeof UiRadioButton>;
36
+ } satisfies Meta<PropsAndCustomArgs>;
28
37
 
29
38
  export default meta;
30
- type Story = StoryObj<typeof meta>;
39
+ type Story = StoryObj<PropsAndCustomArgs>;
31
40
 
32
41
  export const Default: Story = {
33
42
  render: (args) => ({
@@ -13,7 +13,7 @@
13
13
  <input
14
14
  :id="uniqueId"
15
15
  v-model="model"
16
- class="inline-flex aspect-square appearance-none items-center justify-center rounded-full border-2 bg-neutral align-middle transition-colors after:size-2 after:rounded-full checked:after:content-[''] not-read-only:enabled:cursor-pointer disabled:border-neutral-muted disabled:text-neutral-soft disabled:checked:after:bg-neutral-muted"
16
+ class="inline-flex aspect-square appearance-none items-center justify-center rounded-full border-2 bg-neutral align-middle transition-colors after:size-2 after:rounded-full checked:after:content-[''] enabled:cursor-pointer disabled:border-neutral-muted disabled:text-neutral-soft disabled:checked:after:bg-neutral-muted"
17
17
  :class="{
18
18
  'size-4': size === 'sm',
19
19
  'size-5': size === 'md',
@@ -1,4 +1,8 @@
1
- import type { Meta, StoryObj } from "@storybook/vue3-vite";
1
+ import type {
2
+ ComponentPropsAndSlots,
3
+ Meta,
4
+ StoryObj,
5
+ } from "@storybook/vue3-vite";
2
6
 
3
7
  import UiSelect from "./UiSelect.vue";
4
8
 
@@ -12,12 +16,16 @@ const options: string[] = [
12
16
  "done",
13
17
  ];
14
18
 
19
+ type PropsAndCustomArgs = ComponentPropsAndSlots<typeof UiSelect> & {
20
+ name?: string;
21
+ disabled?: boolean;
22
+ };
23
+
15
24
  const meta = {
16
25
  title: "forms/UiSelect",
17
26
  component: UiSelect,
18
27
  args: {
19
28
  // Attributes
20
- // @ts-ignore Storybook doesn't know that **any** component can receive arbitrary attributes and enforces this on the type level.
21
29
  name: "status",
22
30
  disabled: false,
23
31
 
@@ -30,17 +38,16 @@ const meta = {
30
38
  },
31
39
  argTypes: {
32
40
  // Attributes
33
- // @ts-ignore Storybook doesn't know that **any** component can receive arbitrary attributes and enforces this on the type level.
34
41
  name: { control: "text" },
35
42
  disabled: { control: "boolean" },
36
43
 
37
44
  // Props
38
45
  modelValue: { control: "select", options },
39
46
  },
40
- } satisfies Meta<typeof UiSelect>;
47
+ } satisfies Meta<PropsAndCustomArgs>;
41
48
 
42
49
  export default meta;
43
- type Story = StoryObj<typeof UiSelect>;
50
+ type Story = StoryObj<PropsAndCustomArgs>;
44
51
 
45
52
  export const Default: Story = {
46
53
  render: (args) => ({
@@ -9,35 +9,23 @@
9
9
  "
10
10
  >
11
11
  <slot v-if="labelPosition === 'left'" />
12
- <div class="relative">
13
- <input
14
- :id="toggleId"
15
- v-model="modelValue"
16
- type="checkbox"
17
- data-testid="toggleButton"
18
- role="switch"
19
- class="sr-only"
20
- v-bind="{ ...$attrs, class: null }"
21
- />
22
- <div
23
- class="line rounded-full shadow-inner transition-opacity duration-200 ease-in-out"
24
- :class="{
25
- 'bg-secondary opacity-50': modelValue,
26
- 'bg-neutral-faint': !modelValue,
27
- 'h-3 w-6': size === 'sm',
28
- 'h-4 w-10': size === 'lg',
29
- }"
30
- />
31
- <div
32
- :class="{
33
- 'translate-x-full bg-secondary': modelValue,
34
- 'bg-neutral-muted': !modelValue,
35
- '-top-0.5 size-4': size === 'sm',
36
- '-top-1 size-6': size === 'lg',
37
- }"
38
- class="dot absolute -left-1 rounded-full shadow-sm transition-transform duration-200 ease-in-out"
39
- />
40
- </div>
12
+
13
+ <input
14
+ :id="toggleId"
15
+ v-model="model"
16
+ class="relative appearance-none rounded-full shadow-inner transition-opacity duration-200 ease-in-out after:absolute after:-left-1 after:block after:rounded-full after:shadow-sm after:transition-transform after:duration-200 after:ease-in-out after:content-[''] enabled:cursor-pointer"
17
+ :class="{
18
+ 'bg-secondary-emphasis after:translate-x-full after:bg-secondary':
19
+ model,
20
+ 'bg-neutral-faint after:bg-neutral-muted': !model,
21
+ 'h-3 w-6 after:-top-0.5 after:size-4': size === 'sm',
22
+ 'h-4 w-10 after:-top-1 after:size-6': size === 'lg',
23
+ }"
24
+ type="checkbox"
25
+ role="switch"
26
+ data-testid="toggleButton"
27
+ v-bind="{ ...$attrs, class: null }"
28
+ />
41
29
 
42
30
  <slot v-if="labelPosition === 'right'" />
43
31
  </UiLabel>
@@ -51,13 +39,13 @@ import UiLabel from "../UiLabel/UiLabel.vue";
51
39
 
52
40
  defineOptions({ inheritAttrs: false });
53
41
 
54
- const modelValue = defineModel<boolean>({ default: false });
42
+ const model = defineModel<boolean>({ default: false });
55
43
 
56
44
  defineSlots<{
57
45
  /**
58
46
  * Label content.
59
47
  */
60
- default?: () => any;
48
+ default: () => any;
61
49
  }>();
62
50
 
63
51
  const {