@privyid/persona 1.0.0-rc.1 → 1.0.0-rc.2

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.
@@ -8,6 +8,7 @@
8
8
  :disabled="disabled">
9
9
  <template #activator>
10
10
  <Input
11
+ v-bind="$attrs"
11
12
  :value="value"
12
13
  :model-value="value"
13
14
  data-testid="datepicker-input"
@@ -16,7 +17,10 @@
16
17
  :disabled="disabled"
17
18
  :error="error"
18
19
  :size="size"
20
+ :clearable="clearable"
21
+ :container-class="containerClass"
19
22
  readonly
23
+ @clear="onClear"
20
24
  @focus="onFocus">
21
25
  <template #append>
22
26
  <IconCalendar
@@ -27,6 +31,7 @@
27
31
 
28
32
  <Calendar
29
33
  v-model="model"
34
+ class="datepicker__calendar"
30
35
  :start="start"
31
36
  :end="end"
32
37
  :disabled="disabled"
@@ -37,7 +42,6 @@
37
42
  :range="range"
38
43
  :min-range="minRange"
39
44
  :max-range="maxRange"
40
- class="datepicker__calendar"
41
45
  @change="onSelected"
42
46
  @update:start="$emit('update:start', $event)"
43
47
  @update:end="$emit('update:end', $event)" />
@@ -60,6 +64,8 @@ import { useVModel } from '../input'
60
64
  import IconCalendar from '@privyid/persona-icon/vue/calendar/16.vue'
61
65
  import type { SizeVariant } from '../button'
62
66
 
67
+ defineOptions({ inheritAttrs: false })
68
+
63
69
  const props = defineProps({
64
70
  modelValue: {
65
71
  type : [Date, Array] as PropType<Date | [Date, Date]>,
@@ -69,6 +75,30 @@ const props = defineProps({
69
75
  type : String as PropType<SizeVariant>,
70
76
  default: 'md',
71
77
  },
78
+ disabled: {
79
+ type : Boolean,
80
+ default: false,
81
+ },
82
+ readonly: {
83
+ type : Boolean,
84
+ default: false,
85
+ },
86
+ error: {
87
+ type : Boolean,
88
+ default: false,
89
+ },
90
+ clearable: {
91
+ type : Boolean,
92
+ default: false,
93
+ },
94
+ containerClass: {
95
+ type: [
96
+ String,
97
+ Array,
98
+ Object,
99
+ ],
100
+ default: undefined,
101
+ },
72
102
  start: {
73
103
  type : Date,
74
104
  default: undefined,
@@ -85,18 +115,6 @@ const props = defineProps({
85
115
  type : String,
86
116
  default: 'dd/MM/yyyy',
87
117
  },
88
- disabled: {
89
- type : Boolean,
90
- default: undefined,
91
- },
92
- readonly: {
93
- type : Boolean,
94
- default: undefined,
95
- },
96
- error: {
97
- type : Boolean,
98
- default: undefined,
99
- },
100
118
  max: {
101
119
  type : Date,
102
120
  default: undefined,
@@ -178,6 +196,11 @@ function onFocus () {
178
196
  isOpen.value = true
179
197
  }
180
198
 
199
+ function onClear () {
200
+ if (!props.disabled && !props.readonly)
201
+ model.value = undefined
202
+ }
203
+
181
204
  function onSelected (event: Event) {
182
205
  isOpen.value = false
183
206
 
@@ -197,7 +197,12 @@ defineEmits<{
197
197
  }>()
198
198
 
199
199
  const slots = defineSlots<{
200
- 'activator'(props: { isOpen: boolean, open: () => void, close: () => void, toggle: () => void }): VNode[],
200
+ 'activator'(props: {
201
+ isOpen: boolean,
202
+ open: () => void,
203
+ close: () => void,
204
+ toggle: () => void,
205
+ }): VNode[],
201
206
  'button-content'(): VNode[],
202
207
  'prepend'(): VNode[],
203
208
  'append'(): VNode[],
@@ -11,7 +11,7 @@ declare const _default: import("vue-demi").DefineComponent<{
11
11
  default: () => never[];
12
12
  };
13
13
  modelValue: {
14
- type: (BooleanConstructor | ArrayConstructor | NumberConstructor | ObjectConstructor | DateConstructor | StringConstructor)[];
14
+ type: (NumberConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor | DateConstructor | StringConstructor)[];
15
15
  default: undefined;
16
16
  };
17
17
  }, {
@@ -30,7 +30,7 @@ declare const _default: import("vue-demi").DefineComponent<{
30
30
  default: () => never[];
31
31
  };
32
32
  modelValue: {
33
- type: (BooleanConstructor | ArrayConstructor | NumberConstructor | ObjectConstructor | DateConstructor | StringConstructor)[];
33
+ type: (NumberConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor | DateConstructor | StringConstructor)[];
34
34
  default: undefined;
35
35
  };
36
36
  }>> & {
@@ -6,15 +6,15 @@ declare const _default: import("vue-demi").DefineComponent<{
6
6
  required: true;
7
7
  };
8
8
  modelValue: {
9
- type: (BooleanConstructor | ArrayConstructor | NumberConstructor | ObjectConstructor | DateConstructor | StringConstructor)[];
9
+ type: (NumberConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor | DateConstructor | StringConstructor)[];
10
10
  default: boolean;
11
11
  };
12
12
  value: {
13
- type: (BooleanConstructor | ArrayConstructor | NumberConstructor | ObjectConstructor | DateConstructor | StringConstructor)[];
13
+ type: (NumberConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor | DateConstructor | StringConstructor)[];
14
14
  default: boolean;
15
15
  };
16
16
  uncheckedValue: {
17
- type: (BooleanConstructor | ArrayConstructor | NumberConstructor | ObjectConstructor | DateConstructor | StringConstructor)[];
17
+ type: (NumberConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor | DateConstructor | StringConstructor)[];
18
18
  default: boolean;
19
19
  };
20
20
  checked: {
@@ -30,15 +30,15 @@ declare const _default: import("vue-demi").DefineComponent<{
30
30
  required: true;
31
31
  };
32
32
  modelValue: {
33
- type: (BooleanConstructor | ArrayConstructor | NumberConstructor | ObjectConstructor | DateConstructor | StringConstructor)[];
33
+ type: (NumberConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor | DateConstructor | StringConstructor)[];
34
34
  default: boolean;
35
35
  };
36
36
  value: {
37
- type: (BooleanConstructor | ArrayConstructor | NumberConstructor | ObjectConstructor | DateConstructor | StringConstructor)[];
37
+ type: (NumberConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor | DateConstructor | StringConstructor)[];
38
38
  default: boolean;
39
39
  };
40
40
  uncheckedValue: {
41
- type: (BooleanConstructor | ArrayConstructor | NumberConstructor | ObjectConstructor | DateConstructor | StringConstructor)[];
41
+ type: (NumberConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor | DateConstructor | StringConstructor)[];
42
42
  default: boolean;
43
43
  };
44
44
  checked: {
@@ -49,8 +49,8 @@ declare const _default: import("vue-demi").DefineComponent<{
49
49
  "onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
50
50
  onChange?: ((...args: any[]) => any) | undefined;
51
51
  }, {
52
- modelValue: string | number | boolean | Record<string, any> | unknown[] | Date;
53
52
  value: string | number | boolean | Record<string, any> | unknown[] | Date;
53
+ modelValue: string | number | boolean | Record<string, any> | unknown[] | Date;
54
54
  checked: boolean;
55
55
  uncheckedValue: string | number | boolean | Record<string, any> | unknown[] | Date;
56
56
  }, {}>;
@@ -25,9 +25,9 @@
25
25
  "PCollapse": "components/collapse/Collapse.vue",
26
26
  "PContextualBar": "components/contextual-bar/ContextualBar.vue",
27
27
  "PCropper": "components/cropper/Cropper.vue",
28
+ "PDatepicker": "components/datepicker/Datepicker.vue",
28
29
  "PDialog": "components/dialog/Dialog.vue",
29
30
  "PDialogFooter": "components/dialog/DialogFooter.vue",
30
- "PDatepicker": "components/datepicker/Datepicker.vue",
31
31
  "PDivider": "components/divider/Divider.vue",
32
32
  "PDot": "components/dot/Dot.vue",
33
33
  "PDropdown": "components/dropdown/Dropdown.vue",
@@ -74,16 +74,16 @@
74
74
  "PPdfObjectAddon": "components/pdf-object/PdfObjectAddon.vue",
75
75
  "PPdfObjectDebugger": "components/pdf-object/PdfObjectDebugger.vue",
76
76
  "PPdfObjects": "components/pdf-object/PdfObjects.vue",
77
+ "PPdfText": "components/pdf-text/PdfText.vue",
77
78
  "PPdfError": "components/pdf-viewer/PdfError.vue",
78
79
  "PPdfLoading": "components/pdf-viewer/PdfLoading.vue",
79
80
  "PPdfNavigation": "components/pdf-viewer/PdfNavigation.vue",
80
81
  "PPdfViewer": "components/pdf-viewer/PdfViewer.vue",
81
- "PPdfText": "components/pdf-text/PdfText.vue",
82
82
  "PPopover": "components/popover/Popover.vue",
83
83
  "PPopup": "components/popup/Popup.vue",
84
- "PProgressIndicator": "components/progress-indicator/ProgressIndicator.vue",
85
84
  "PProgress": "components/progress/Progress.vue",
86
85
  "PProgressItem": "components/progress/ProgressItem.vue",
86
+ "PProgressIndicator": "components/progress-indicator/ProgressIndicator.vue",
87
87
  "PProgressbar": "components/progressbar/Progressbar.vue",
88
88
  "PRadio": "components/radio/Radio.vue",
89
89
  "PRingbar": "components/ringbar/Ringbar.vue",
@@ -118,8 +118,11 @@
118
118
  "PTab": "components/tabs/Tab.vue",
119
119
  "PTabContent": "components/tabs/TabContent.vue",
120
120
  "PTabs": "components/tabs/Tabs.vue",
121
- "PTextarea": "components/textarea/Textarea.vue",
122
121
  "PText": "components/text/Text.vue",
122
+ "PTextarea": "components/textarea/Textarea.vue",
123
+ "PTime": "components/time/Time.vue",
124
+ "PTimeItem": "components/time/TimeItem.vue",
125
+ "PTimepicker": "components/timepicker/Timepicker.vue",
123
126
  "PToast": "components/toast/Toast.vue",
124
127
  "PToggle": "components/toggle/Toggle.vue",
125
128
  "PTooltip": "components/tooltip/Tooltip.vue",
@@ -89,7 +89,9 @@
89
89
  <div
90
90
  data-testid="select-no-data"
91
91
  class="select__empty">
92
- <slot name="empty">
92
+ <slot
93
+ name="empty"
94
+ :keyword="keyword">
93
95
  {{ emptyText }}
94
96
  </slot>
95
97
  </div>
@@ -168,6 +170,7 @@ import IconLoading from '../spinner/SpinnerRing.vue'
168
170
  import type {
169
171
  PropType,
170
172
  HTMLAttributes,
173
+ VNode,
171
174
  } from 'vue-demi'
172
175
  import {
173
176
  computed,
@@ -459,6 +462,23 @@ defineExpose({
459
462
  menuEl,
460
463
  toggle,
461
464
  })
465
+
466
+ defineSlots<{
467
+ 'activator'(props: {
468
+ isOpen: boolean,
469
+ open: () => void,
470
+ close: () => void,
471
+ toggle: () => void,
472
+ }): VNode[],
473
+ 'selected'(props: { item: unknown, multiple: boolean }): VNode[],
474
+ 'placeholder'(): VNode[],
475
+ 'empty'(props: { keyword: string }): VNode[],
476
+ 'option'(props: {
477
+ item: SelectItem,
478
+ isSelected: boolean,
479
+ }): VNode[],
480
+ 'loading'(): VNode[],
481
+ }>()
462
482
  </script>
463
483
 
464
484
  <style lang="postcss">
@@ -0,0 +1,257 @@
1
+ <template>
2
+ <p-card
3
+ data-testid="time"
4
+ class="time"
5
+ element="div">
6
+ <div class="time__container">
7
+ <TimeItem
8
+ v-model="hour"
9
+ :options="hourOptions"
10
+ time-type="hour"
11
+ class="time__items--hour" />
12
+
13
+ <div class="time__separator">
14
+ {{ timeSeparator }}
15
+ </div>
16
+
17
+ <TimeItem
18
+ v-model="minute"
19
+ :options="minuteOptions"
20
+ time-type="minute"
21
+ class="time__items--minute" />
22
+
23
+ <TimeItem
24
+ v-if="is12Hour"
25
+ v-model="ampm"
26
+ :options="periodOptions"
27
+ time-type="ampm"
28
+ class="time__items--ampm" />
29
+ </div>
30
+ <template
31
+ v-if="$slots.default"
32
+ #footer>
33
+ <div class="time__action">
34
+ <slot :confirm="confirm" />
35
+ </div>
36
+ </template>
37
+ </p-card>
38
+ </template>
39
+
40
+ <script setup lang="ts">
41
+ import pCard from '../card/Card.vue'
42
+ import TimeItem from './TimeItem.vue'
43
+ import type { PropType } from 'vue-demi'
44
+ import {
45
+ watch,
46
+ computed,
47
+ nextTick,
48
+ onBeforeMount,
49
+ ref,
50
+ } from 'vue-demi'
51
+ import { watchDebounced } from '@vueuse/core'
52
+ import { useVModel } from '../input'
53
+ import {
54
+ format,
55
+ formatISO,
56
+ isDate,
57
+ parse,
58
+ startOfDay,
59
+ } from 'date-fns'
60
+ import type { TimeModel, TimeContext } from './index'
61
+ import {
62
+ getHourUnits,
63
+ getMinuteUnits,
64
+ parseTime,
65
+ validateInterval,
66
+ } from './utils'
67
+
68
+ const props = defineProps({
69
+ modelValue: {
70
+ type : [Date, Object] as PropType<Date | TimeModel>,
71
+ default: undefined,
72
+ },
73
+ timeSeparator: {
74
+ type : String,
75
+ default: ':',
76
+ },
77
+ is12Hour: {
78
+ type : Boolean,
79
+ default: false,
80
+ },
81
+ hourInterval: {
82
+ type : Number,
83
+ default : 1,
84
+ validator: (value: number, prop) => {
85
+ return validateInterval('hour', value, prop.is12Hour as boolean)
86
+ },
87
+ },
88
+ minuteInterval: {
89
+ type : Number,
90
+ default : 1,
91
+ validator: (value: number) => {
92
+ return validateInterval('minute', value)
93
+ },
94
+ },
95
+ min: {
96
+ type : Date,
97
+ default: undefined,
98
+ },
99
+ max: {
100
+ type : Date,
101
+ default: undefined,
102
+ },
103
+ })
104
+
105
+ const emit = defineEmits<{
106
+ 'update:modelValue': [unknown],
107
+ 'context': [TimeContext],
108
+ 'change': [TimeContext],
109
+ }>()
110
+
111
+ const model = useVModel(props)
112
+ const localModel = ref<Date>(startOfDay(new Date()))
113
+
114
+ const hour = ref('00')
115
+ const minute = ref('00')
116
+ const ampm = ref<'AM' | 'PM' | '-'>('AM')
117
+
118
+ const timeLabel = computed(() => {
119
+ const time = `${hour.value}:${minute.value}`
120
+
121
+ return props.is12Hour ? `${time} ${ampm.value}` : time
122
+ })
123
+
124
+ const timeFormat = computed(() => props.is12Hour ? 'hh:mm a' : 'HH:mm')
125
+
126
+ const hourOptions = computed(() => {
127
+ return getHourUnits(localModel.value, props.is12Hour, props.hourInterval, props.min, props.max)
128
+ })
129
+
130
+ const minuteOptions = computed(() => {
131
+ return getMinuteUnits(localModel.value, Number(hour.value), props.minuteInterval, props.min, props.max)
132
+ })
133
+
134
+ const periodOptions = computed(() => {
135
+ return ['AM', 'PM'] // TODO: should handle for period within time range
136
+ })
137
+
138
+ watchDebounced([
139
+ hour,
140
+ minute,
141
+ ampm,
142
+ ], () => {
143
+ const date = parse(timeLabel.value, timeFormat.value, localModel.value)
144
+
145
+ emit('change', getContext(date))
146
+ }, { flush: 'post', debounce: 300 })
147
+
148
+ watch(model, init)
149
+
150
+ onBeforeMount(init)
151
+
152
+ function init () {
153
+ try {
154
+ if (!model.value)
155
+ return
156
+
157
+ if (isDate(model.value)) {
158
+ localModel.value = new Date(model.value as Date)
159
+
160
+ const { hh, mm, a } = parseTime(localModel.value, timeFormat.value)
161
+
162
+ assignTime(hh, mm, a)
163
+ } else {
164
+ const { hh, mm, a } = (model.value as TimeModel)
165
+
166
+ assignTime(hh, mm, a)
167
+
168
+ localModel.value = parse(timeLabel.value, timeFormat.value, new Date())
169
+
170
+ !props.is12Hour && a && console.warn('Please provide is12Hour props to parse time to 12 Hour Format')
171
+ }
172
+ } catch (error) {
173
+ console.warn(error)
174
+ }
175
+ }
176
+
177
+ function confirm (): void {
178
+ try {
179
+ localModel.value = parse(timeLabel.value, timeFormat.value, localModel.value)
180
+
181
+ model.value = isDate(model.value)
182
+ ? new Date(localModel.value)
183
+ : {
184
+ hh: hour.value,
185
+ mm: minute.value,
186
+ a : props.is12Hour ? ampm.value : undefined,
187
+ }
188
+
189
+ emit('context', getContext(localModel.value))
190
+ } catch (error) {
191
+ console.warn(error)
192
+ }
193
+ }
194
+
195
+ async function assignTime (hh: string, mm: string, a?: 'AM' | 'PM' | '-') {
196
+ hour.value = hh ?? '00'
197
+ minute.value = mm ?? '00'
198
+ ampm.value = a ?? 'AM'
199
+
200
+ await nextTick()
201
+ }
202
+
203
+ function getContext (date: Date): TimeContext {
204
+ return {
205
+ time : format(date, timeFormat.value),
206
+ date : date,
207
+ dateISO: formatISO(date),
208
+ }
209
+ }
210
+ </script>
211
+
212
+ <style lang="postcss">
213
+ .time {
214
+ --p-card-padding-x: 2rem;
215
+ --p-card-padding-y: 2rem;
216
+
217
+ @apply flex flex-col shadow-xl;
218
+ @apply max-w-[320px] overflow-hidden;
219
+
220
+ &.card {
221
+ @apply border-transparent dark:border-transparent;
222
+ }
223
+
224
+ &__container {
225
+ @apply flex items-center justify-center w-full min-h-[144px] space-x-2;
226
+ }
227
+
228
+ &__separator {
229
+ @apply px-2.5 py-3;
230
+ }
231
+
232
+ &__action {
233
+ @apply grid grid-flow-col auto-cols-fr;
234
+
235
+ &--cancel,
236
+ &--confirm {
237
+ @apply mx-2;
238
+ }
239
+ }
240
+
241
+ .card__body {
242
+ @apply pb-6;
243
+ }
244
+
245
+ .card__footer {
246
+ @apply pt-0;
247
+ }
248
+
249
+ .splide__track {
250
+ @apply px-2;
251
+ }
252
+
253
+ .dropdown__subitem & {
254
+ @apply shadow-none;
255
+ }
256
+ }
257
+ </style>
@@ -0,0 +1,117 @@
1
+ <template>
2
+ <div
3
+ ref="container"
4
+ class="time__items splide"
5
+ :class="`time__times--${timeType}`">
6
+ <div class="splide__track">
7
+ <div class="splide__list">
8
+ <div
9
+ v-for="option in options"
10
+ :key="option"
11
+ class="time__item splide__slide">
12
+ {{ option }}
13
+ </div>
14
+ </div>
15
+ </div>
16
+ </div>
17
+ </template>
18
+
19
+ <script setup lang="ts">
20
+ import {
21
+ ref,
22
+ onMounted,
23
+ watch,
24
+ computed,
25
+ } from 'vue-demi'
26
+ import { watchDebounced } from '@vueuse/core'
27
+ import type {
28
+ PropType,
29
+ Ref,
30
+ } from 'vue-demi'
31
+ import { useTime } from './index'
32
+ import { useVModel } from '../input'
33
+ import type { Options } from '@splidejs/splide'
34
+ import '@splidejs/splide/css/core'
35
+
36
+ type TimeType = 'hour' | 'minute' | 'ampm'
37
+
38
+ const props = defineProps({
39
+ modelValue: {
40
+ type : String,
41
+ default: '00',
42
+ },
43
+ timeType: {
44
+ type : String as PropType<TimeType>,
45
+ default: 'hour',
46
+ },
47
+ options: {
48
+ type : Array<string>,
49
+ default: () => [],
50
+ },
51
+ })
52
+
53
+ const { splide, activeSplide, initSplide } = useTime()
54
+
55
+ const model = useVModel(props)
56
+ const container = ref<HTMLDivElement>()
57
+
58
+ const timeOptions = computed(() => props.options)
59
+
60
+ watch(activeSplide, (value) => {
61
+ model.value = props.options[value]
62
+ })
63
+
64
+ watchDebounced(timeOptions, (value) => {
65
+ if (!splide.value)
66
+ return
67
+
68
+ const splideOptions: Options = {
69
+ ...splide.value.options,
70
+ clones: value.length === 1 ? 0 : undefined,
71
+ }
72
+
73
+ splide.value.options = splideOptions
74
+ splide.value.refresh()
75
+
76
+ if (!value.includes(model.value))
77
+ model.value = value[0]
78
+ }, { flush: 'post', debounce: 50 })
79
+
80
+ onMounted(async () => {
81
+ if (container.value) {
82
+ const splideOptions: Options = {
83
+ start: timeOptions.value.indexOf(model.value),
84
+ type : props.timeType === 'ampm' ? 'slide' : 'loop',
85
+ }
86
+
87
+ await initSplide(container as Ref<HTMLDivElement>, splideOptions)
88
+ }
89
+
90
+ splide.value?.on('refresh', () => {
91
+ const index = timeOptions.value.indexOf(model.value)
92
+
93
+ splide.value?.go(index === -1 ? 0 : index)
94
+ })
95
+ })
96
+
97
+ </script>
98
+
99
+ <style lang="postcss">
100
+ .time {
101
+ &__items {
102
+ &:before {
103
+ @apply absolute content-[''] w-[42px] h-12 border-default dark:border-dark-default border-y;
104
+ @apply top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%];
105
+ }
106
+ }
107
+
108
+ &__item {
109
+ @apply text-center px-2.5 py-3 text-subtle dark:text-dark-subtle cursor-pointer;
110
+ @apply transition-[color] duration-[50ms];
111
+
112
+ &.is-active {
113
+ @apply text-default dark:text-dark-default font-medium cursor-default;
114
+ }
115
+ }
116
+ }
117
+ </style>
@@ -0,0 +1,19 @@
1
+ import type { MaybeRef } from '@vueuse/core';
2
+ import type { Options, Splide } from '@splidejs/splide';
3
+ export type TimeLimitation = Date | TimeModel | undefined;
4
+ export interface TimeModel {
5
+ hh: string;
6
+ mm: string;
7
+ a?: 'AM' | 'PM' | '-';
8
+ }
9
+ export interface TimeContext {
10
+ time: string;
11
+ date: Date;
12
+ dateISO: string;
13
+ }
14
+ export declare function useTime(): {
15
+ splide: import("vue-demi").Ref<Splide | undefined>;
16
+ activeSplide: import("vue-demi").Ref<number>;
17
+ initSplide: (element: MaybeRef<HTMLDivElement>, options?: Options) => Promise<void>;
18
+ initSplideEvent: () => void;
19
+ };
@@ -0,0 +1,53 @@
1
+ import {
2
+ onBeforeUnmount,
3
+ ref,
4
+ unref
5
+ } from "vue-demi";
6
+ import defu from "defu";
7
+ export function useTime() {
8
+ const splide = ref();
9
+ const activeSplide = ref(1);
10
+ async function initSplide(element, options) {
11
+ const { Splide } = await import("@splidejs/splide");
12
+ const container = unref(element);
13
+ const splideOptions = defu(options, {
14
+ direction: "ttb",
15
+ height: "9rem",
16
+ width: "100%",
17
+ focus: "center",
18
+ perPage: 3,
19
+ perMove: 1,
20
+ arrows: false,
21
+ wheel: true,
22
+ wheelSleep: 150,
23
+ isNavigation: true,
24
+ pagination: false
25
+ });
26
+ splide.value = new Splide(container, splideOptions);
27
+ initSplideEvent();
28
+ }
29
+ function initSplideEvent() {
30
+ if (!splide.value)
31
+ return;
32
+ splide.value.on("mounted", onTimeLoad);
33
+ splide.value.on("move", onTimeChange);
34
+ splide.value.mount();
35
+ }
36
+ function onTimeLoad() {
37
+ if (!splide.value)
38
+ return;
39
+ activeSplide.value = splide.value.index;
40
+ }
41
+ function onTimeChange(index) {
42
+ activeSplide.value = index;
43
+ }
44
+ onBeforeUnmount(() => {
45
+ splide.value?.destroy();
46
+ });
47
+ return {
48
+ splide,
49
+ activeSplide,
50
+ initSplide,
51
+ initSplideEvent
52
+ };
53
+ }
@@ -0,0 +1,9 @@
1
+ export declare function getHourUnits(model: Date, is12Hour: boolean, step?: number, min?: Date, max?: Date): string[];
2
+ export declare function getMinuteUnits(model: Date, currentHour: number, step?: number, min?: Date, max?: Date): string[];
3
+ export declare function getFormat(is12Hour: boolean): "hh:mm a" | "HH:mm";
4
+ export declare function parseTime(date: Date, timeFormat: string): {
5
+ hh: string;
6
+ mm: string;
7
+ a: "AM" | "PM";
8
+ };
9
+ export declare function validateInterval(type: 'hour' | 'minute', value: number, is12Hour?: boolean): boolean;
@@ -0,0 +1,82 @@
1
+ import {
2
+ set,
3
+ eachHourOfInterval,
4
+ eachMinuteOfInterval,
5
+ endOfDay,
6
+ format,
7
+ isBefore,
8
+ min as minDate,
9
+ max as maxDate,
10
+ startOfDay,
11
+ isSameHour,
12
+ endOfHour,
13
+ startOfHour
14
+ } from "date-fns";
15
+ import { uniq } from "lodash-es";
16
+ export function getHourUnits(model, is12Hour, step = 1, min, max) {
17
+ if (isBefore(max, min)) {
18
+ console.warn("`max` value should greater than `min` value and vice versa");
19
+ return [];
20
+ }
21
+ let start = startOfDay(/* @__PURE__ */ new Date());
22
+ let end = endOfDay(/* @__PURE__ */ new Date());
23
+ if (min)
24
+ start = maxDate([model, min]);
25
+ if (max)
26
+ end = minDate([endOfDay(start), max]);
27
+ const hourUnits = eachHourOfInterval({
28
+ start,
29
+ end
30
+ }, { step });
31
+ const units = hourUnits.map((date) => {
32
+ if (is12Hour)
33
+ return format(date, "hh");
34
+ return format(date, "HH");
35
+ });
36
+ return uniq(units);
37
+ }
38
+ export function getMinuteUnits(model, currentHour, step = 1, min, max) {
39
+ if (isBefore(max, min)) {
40
+ console.warn("`max` value should greater than `min` value and vice versa");
41
+ return [];
42
+ }
43
+ let start = startOfDay(/* @__PURE__ */ new Date());
44
+ let end = endOfDay(/* @__PURE__ */ new Date());
45
+ if (min)
46
+ start = maxDate([model, min]);
47
+ if (max)
48
+ end = minDate([endOfDay(start), max]);
49
+ const currentTime = set(start, { hours: currentHour });
50
+ const minuteUnits = eachMinuteOfInterval({
51
+ start: isSameHour(currentTime, start) ? currentTime : startOfHour(currentTime),
52
+ end: isSameHour(currentTime, end) ? end : endOfHour(currentTime)
53
+ }, { step });
54
+ const units = minuteUnits.map((date) => {
55
+ return format(date, "mm");
56
+ });
57
+ return uniq(units);
58
+ }
59
+ export function getFormat(is12Hour) {
60
+ return is12Hour ? "hh:mm a" : "HH:mm";
61
+ }
62
+ export function parseTime(date, timeFormat) {
63
+ const [hh, mm] = format(date, timeFormat).split(":");
64
+ const a = format(date, "a");
65
+ return {
66
+ hh,
67
+ mm: mm.slice(0, 2),
68
+ a
69
+ };
70
+ }
71
+ export function validateInterval(type, value, is12Hour = false) {
72
+ let min = 0;
73
+ let max = 0;
74
+ if (type === "hour") {
75
+ min = 1;
76
+ max = is12Hour ? 12 : 24;
77
+ } else if (type === "minute") {
78
+ min = 1;
79
+ max = 60;
80
+ }
81
+ return value >= min && value < max;
82
+ }
@@ -0,0 +1,230 @@
1
+ <template>
2
+ <Dropdown
3
+ v-model="isOpen"
4
+ class="timepicker"
5
+ data-testid="timepicker"
6
+ aria-label="timepicker"
7
+ :class="classNames"
8
+ :disabled="disabled">
9
+ <template #activator>
10
+ <Input
11
+ :value="value"
12
+ :model-value="value"
13
+ data-testid="timepicker-input"
14
+ class="timepicker__input"
15
+ :placeholder="placeholder"
16
+ :disabled="disabled"
17
+ :error="error"
18
+ :size="size"
19
+ readonly
20
+ @focus="onFocus">
21
+ <template #append>
22
+ <IconTime
23
+ class="timepicker__icon" />
24
+ </template>
25
+ </Input>
26
+ </template>
27
+
28
+ <Time
29
+ v-if="isOpen"
30
+ v-slot="{ confirm }"
31
+ v-model="model"
32
+ :time-separator="timeSeparator"
33
+ :is12-hour="is12Hour"
34
+ :hour-interval="hourInterval"
35
+ :minute-interval="minuteInterval"
36
+ :min="min"
37
+ :max="max"
38
+ @context="onTimeContext"
39
+ @change="(context) => emit('change', context)">
40
+ <slot
41
+ name="footer"
42
+ :confirm="confirm"
43
+ :close="onClose">
44
+ <Button
45
+ v-if="dismissable"
46
+ variant="ghost"
47
+ class="time__action--cancel"
48
+ @click="onClose">
49
+ {{ dimissLabel }}
50
+ </Button>
51
+ <Button
52
+ variant="solid"
53
+ color="info"
54
+ @click="confirm">
55
+ {{ confirmLabel }}
56
+ </Button>
57
+ </slot>
58
+ </Time>
59
+ </Dropdown>
60
+ </template>
61
+
62
+ <script setup lang="ts">
63
+ import Dropdown from '../dropdown/Dropdown.vue'
64
+ import Input from '../input/Input.vue'
65
+ import Button from '../button/Button.vue'
66
+ import IconTime from '@privyid/persona-icon/vue/time/16.vue'
67
+ import Time from '../time/Time.vue'
68
+ import type { SizeVariant } from '../button'
69
+ import { useVModel } from '../input'
70
+ import type { TimeContext, TimeModel } from '../time'
71
+ import { validateInterval } from '../time/utils'
72
+ import type { PropType, VNode } from 'vue-demi'
73
+ import {
74
+ ref, computed, onMounted,
75
+ } from 'vue-demi'
76
+ import { format, isDate } from 'date-fns'
77
+
78
+ const props = defineProps({
79
+ modelValue: {
80
+ type : [Date, Object] as PropType<Date | TimeModel>,
81
+ default: undefined,
82
+ },
83
+ size: {
84
+ type : String as PropType<SizeVariant>,
85
+ default: 'md',
86
+ },
87
+ placeholder: {
88
+ type : String,
89
+ default: '',
90
+ },
91
+ disabled: {
92
+ type : Boolean,
93
+ default: undefined,
94
+ },
95
+ readonly: {
96
+ type : Boolean,
97
+ default: undefined,
98
+ },
99
+ error: {
100
+ type : Boolean,
101
+ default: undefined,
102
+ },
103
+ dismissable: {
104
+ type : Boolean,
105
+ default: false,
106
+ },
107
+ dimissLabel: {
108
+ type : String,
109
+ default: 'Cancel',
110
+ },
111
+ confirmLabel: {
112
+ type : String,
113
+ default: 'Set',
114
+ },
115
+ timeSeparator: {
116
+ type : String,
117
+ default: ':',
118
+ },
119
+ is12Hour: {
120
+ type : Boolean,
121
+ default: false,
122
+ },
123
+ hourInterval: {
124
+ type : Number,
125
+ default : 1,
126
+ validator: (value: number, prop) => {
127
+ return validateInterval('hour', value, prop.is12Hour as boolean)
128
+ },
129
+ },
130
+ minuteInterval: {
131
+ type : Number,
132
+ default : 1,
133
+ validator: (value: number) => {
134
+ return validateInterval('minute', value)
135
+ },
136
+ },
137
+ min: {
138
+ type : Date,
139
+ default: undefined,
140
+ },
141
+ max: {
142
+ type : Date,
143
+ default: undefined,
144
+ },
145
+ })
146
+
147
+ const emit = defineEmits<{
148
+ 'update:modelValue': [unknown],
149
+ 'context': [TimeContext],
150
+ 'change': [TimeContext],
151
+ }>()
152
+
153
+ const model = useVModel(props)
154
+ const isOpen = ref(false)
155
+ const value = ref('')
156
+
157
+ const classNames = computed(() => {
158
+ const result: string[] = []
159
+
160
+ if (isOpen.value)
161
+ result.push('timepicker--open')
162
+
163
+ if (props.disabled)
164
+ result.push('timepicker--disabled')
165
+
166
+ if (props.readonly)
167
+ result.push('timepicker--readonly')
168
+
169
+ if (props.error)
170
+ result.push('timepicker--error', 'state--error')
171
+
172
+ return result
173
+ })
174
+
175
+ onMounted(() => {
176
+ if (!model.value)
177
+ return
178
+
179
+ if (isDate(model.value))
180
+ value.value = format(model.value as Date, props.is12Hour ? 'hh:mm a' : 'HH:mm')
181
+ else {
182
+ const { hh, mm, a } = model.value as TimeModel
183
+ const time = `${hh}:${mm}`
184
+
185
+ value.value = props.is12Hour ? `${time} ${a || ''}` : time
186
+ }
187
+ })
188
+
189
+ function onFocus () {
190
+ if (!props.disabled && !props.readonly)
191
+ isOpen.value = true
192
+ }
193
+
194
+ function onTimeContext (context: TimeContext) {
195
+ onClose()
196
+
197
+ value.value = context.time
198
+ emit('context', context)
199
+ }
200
+
201
+ function onClose () {
202
+ isOpen.value = false
203
+ }
204
+
205
+ defineSlots<{
206
+ 'footer'(props: {
207
+ confirm: () => void,
208
+ close: () => void,
209
+ }): VNode[], }>()
210
+ </script>
211
+
212
+ <style lang="postcss">
213
+ .timepicker {
214
+ &__input {
215
+ @apply pr-8 truncate;
216
+ }
217
+
218
+ &__icon {
219
+ @apply transition-transform duration-150 text-muted pointer-events-none;
220
+ @apply dark:text-dark-muted;
221
+ }
222
+
223
+ > .dropdown__menu {
224
+ --p-dropdown-size: auto;
225
+ --p-dropdown-max-height: 100%;
226
+
227
+ @apply min-w-max max-h-min;
228
+ }
229
+ }
230
+ </style>
package/dist/module.json CHANGED
@@ -4,5 +4,5 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.0.0"
6
6
  },
7
- "version": "1.0.0-rc.1"
7
+ "version": "1.0.0-rc.2"
8
8
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@privyid/persona",
3
3
  "description": "Persona core package",
4
- "version": "1.0.0-rc.1",
4
+ "version": "1.0.0-rc.2",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "url": "https://github.com/privy-open-source/design-system.git",
@@ -28,7 +28,7 @@
28
28
  "import": "./dist/directive/index.mjs",
29
29
  "types": "./dist/directive/index.d.ts"
30
30
  },
31
- "./components/": "./dist/components/"
31
+ "./components/*": "./dist/components/*"
32
32
  },
33
33
  "typesVersions": {
34
34
  "*": {
@@ -58,15 +58,15 @@
58
58
  "dev:prepare": "nuxt-module-build --stub && nuxi prepare playground"
59
59
  },
60
60
  "dependencies": {
61
- "@floating-ui/dom": "1.6.1",
61
+ "@floating-ui/dom": "1.6.3",
62
62
  "@jill64/universal-sanitizer": "^1.0.2",
63
63
  "@juggle/resize-observer": "3.4.0",
64
- "@nuxt/kit": "3.9.3",
64
+ "@nuxt/kit": "3.10.2",
65
65
  "@splidejs/splide": "4.1.4",
66
66
  "@testing-library/dom": "9.3.4",
67
67
  "@testing-library/user-event": "14.5.2",
68
68
  "@vueuse/core": "10.7.2",
69
- "@vueuse/math": "10.7.2",
69
+ "@vueuse/math": "10.8.0",
70
70
  "@zxing/browser": "0.1.4",
71
71
  "@zxing/library": "0.20.0",
72
72
  "chart.js": "4.4.1",
@@ -83,7 +83,7 @@
83
83
  "scroll-into-view": "1.16.2",
84
84
  "tabbable": "6.2.0",
85
85
  "vue-collapsed": "1.3.0",
86
- "vue-demi": "0.14.6",
86
+ "vue-demi": "0.14.7",
87
87
  "vuedraggable": "next",
88
88
  "webfontloader": "1.6.28",
89
89
  "zxcvbn": "4.4.2"
@@ -92,8 +92,8 @@
92
92
  "extends @privyid/browserslist-config"
93
93
  ],
94
94
  "peerDependencies": {
95
- "@privyid/browserslist-config": "^1.0.0-rc.1",
96
- "@privyid/tailwind-preset": "^1.0.0-rc.1",
95
+ "@privyid/browserslist-config": "^1.0.0-rc.2",
96
+ "@privyid/tailwind-preset": "^1.0.0-rc.2",
97
97
  "postcss-custom-properties": "^12.1.11 || ^13.0.0",
98
98
  "postcss-hexrgba": "^2.1.0",
99
99
  "postcss-lighten-darken": "^0.9.0",
@@ -102,13 +102,13 @@
102
102
  },
103
103
  "devDependencies": {
104
104
  "@nuxt/module-builder": "^0.5.0",
105
- "@nuxt/schema": "3.9.3",
105
+ "@nuxt/schema": "3.10.2",
106
106
  "@nuxtjs/tailwindcss": "^6.2.0",
107
- "@privyid/browserslist-config": "^1.0.0-rc.1",
108
- "@privyid/tailwind-preset": "^1.0.0-rc.1",
107
+ "@privyid/browserslist-config": "^1.0.0-rc.2",
108
+ "@privyid/tailwind-preset": "^1.0.0-rc.2",
109
109
  "@types/sanitize-html": "^2",
110
110
  "browserslist-to-esbuild": "^2.0.0",
111
- "nuxt": "3.9.3",
111
+ "nuxt": "3.10.2",
112
112
  "postcss-custom-properties": "^13.0.0",
113
113
  "postcss-hexrgba": "^2.1.0",
114
114
  "postcss-lighten-darken": "^0.9.0",