@citruslime/ui 1.2.1-beta.0 → 2.0.0-beta.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.
Files changed (52) hide show
  1. package/dist/.eslintrc.js +8 -2
  2. package/dist/@types/appUser.d.ts +1 -0
  3. package/dist/@types/components/grid/column.d.ts +2 -1
  4. package/dist/@types/components/header/index.d.ts +0 -1
  5. package/dist/@types/components/{header/navigation.d.ts → navigation/index.d.ts} +7 -4
  6. package/dist/@types/index.d.ts +1 -0
  7. package/dist/components/index.d.ts +17 -14
  8. package/dist/main.d.ts +1 -1
  9. package/dist/style.css +1 -1
  10. package/dist/theme.js +2 -4
  11. package/dist/ui.es.js +1 -1
  12. package/dist/ui.umd.js +1 -1
  13. package/package.json +7 -4
  14. package/src/components/accordion/cl-ui-accordion.vue +89 -0
  15. package/src/components/app/cl-ui-app.vue +35 -0
  16. package/src/components/button/{button.vue → cl-ui-button.vue} +26 -6
  17. package/src/components/calendar/cl-ui-calendar.vue +277 -0
  18. package/src/components/card/{card.vue → cl-ui-card.vue} +17 -1
  19. package/src/components/combo-box/cl-ui-combo-box.vue +357 -0
  20. package/src/components/combo-box/search-container/cl-ui-combo-box-search.vue +279 -0
  21. package/src/components/combo-box/search-container/{header-option/header-option.vue → header/cl-ui-combo-box-header.vue} +17 -2
  22. package/src/components/combo-box/search-container/selectable/cl-ui-combo-box-selectable.vue +99 -0
  23. package/src/components/footer/{footer.vue → cl-ui-footer.vue} +10 -2
  24. package/src/components/grid/cell/{cell.vue → cl-ui-grid-cell.vue} +90 -1
  25. package/src/components/grid/cl-ui-grid.vue +477 -0
  26. package/src/components/grid/filter/cl-ui-grid-filter.vue +270 -0
  27. package/src/components/grid/footer/{footer.vue → cl-ui-grid-footer.vue} +100 -5
  28. package/src/components/grid/header/cl-ui-grid-header.vue +76 -0
  29. package/src/components/grid/view-manager/cl-ui-grid-view-manager.vue +145 -0
  30. package/src/components/header/cl-ui-header.vue +11 -0
  31. package/src/components/header-helper/cl-ui-header-helper.vue +50 -0
  32. package/src/components/language-switcher/{language-switcher.vue → cl-ui-language-switcher.vue} +49 -3
  33. package/src/components/loading-spinner/cl-ui-loading-spinner.vue +16 -0
  34. package/src/components/login/{login.vue → cl-ui-login.vue} +101 -19
  35. package/src/components/modal/{modal.vue → cl-ui-modal.vue} +74 -2
  36. package/src/components/navigation/cl-ui-navigation.vue +124 -0
  37. package/src/components/notification/{notification.vue → cl-ui-notification.vue} +21 -2
  38. package/src/components/slider/cl-ui-slider.vue +145 -0
  39. package/src/components/accordion/accordion.vue +0 -30
  40. package/src/components/calendar/calendar.vue +0 -35
  41. package/src/components/combo-box/combo-box.vue +0 -79
  42. package/src/components/combo-box/search-container/search-container.vue +0 -57
  43. package/src/components/combo-box/search-container/selectable-option/selectable-option.vue +0 -27
  44. package/src/components/grid/filter/filter.vue +0 -93
  45. package/src/components/grid/grid.vue +0 -194
  46. package/src/components/grid/header/header.vue +0 -39
  47. package/src/components/grid/view-manager/view-manager.vue +0 -73
  48. package/src/components/header/header-helper/header-helper.vue +0 -95
  49. package/src/components/header/header.vue +0 -33
  50. package/src/components/header/navigation/navigation.vue +0 -84
  51. package/src/components/loading-spinner/loading-spinner.vue +0 -8
  52. package/src/components/slider/slider.vue +0 -41
@@ -1,6 +1,52 @@
1
- <script lang="ts" src="./language-switcher"></script>
1
+ <script setup lang="ts">
2
+ import { computed, ref, watch } from 'vue';
2
3
 
3
- <style scoped src="./language-switcher.css"></style>
4
+ import { isLanguageLocaleFormat, Language } from '../../@types';
5
+
6
+ const props = withDefaults(defineProps<{
7
+ currentLocale: string;
8
+ supportedLocales: Language[];
9
+ disabled?: boolean;
10
+ }>(), {
11
+ disabled: false
12
+ });
13
+
14
+ defineEmits({
15
+ 'update:current-locale': null
16
+ });
17
+
18
+ const localeToggleOpen = ref<boolean>(false);
19
+
20
+ const selectedLanguage = computed<Language | null>(() => props.supportedLocales.find(loc => loc.localeCode === props.currentLocale) ?? null);
21
+
22
+ const validLanguages = computed<Language[]>(() => props.supportedLocales.filter(e => isLanguageLocaleFormat(e)).sort((a,b) => (a.nativeName > b.nativeName) ? 1 : ((b.nativeName > a.nativeName) ? -1 : 0)));
23
+
24
+ /**
25
+ * Toggles whether the locale dropdown is shown or hidden.
26
+ */
27
+ function toggleLocaleSwitcher (): void {
28
+ if (!props.disabled) {
29
+ localeToggleOpen.value = !localeToggleOpen.value;
30
+ }
31
+ }
32
+
33
+ watch(() => props.disabled, (newValue) => {
34
+ if (newValue) {
35
+ localeToggleOpen.value = false;
36
+ }
37
+ });
38
+ </script>
39
+
40
+ <style scoped lang="postcss">
41
+ .flag-svg,
42
+ .flag-svg[data-size='small'] {
43
+ background-position: center center !important;
44
+ background-repeat: no-repeat !important;
45
+ background-size: cover !important;
46
+ height: 12px;
47
+ width: 16px;
48
+ }
49
+ </style>
4
50
 
5
51
  <template>
6
52
  <div v-if="selectedLanguage !== null && isLanguageLocaleFormat(selectedLanguage)"
@@ -23,7 +69,7 @@
23
69
  class="border-b border-grey-0 content-center cursor-pointer flex hover:bg-grey-0 items-center last-child:border-b-0 last:border-b-0 p-2 text-center transition-colors"
24
70
  :data-localename="location.name"
25
71
  :data-localecode="location.localeCode"
26
- @click="selectedLocale = location.localeCode">
72
+ @click="$emit('update:current-locale', location.localeCode)">
27
73
  <span class="flag-svg inline-block mr-3"
28
74
  :style="{ 'background': `url('${location.svgCode}')` }"></span>
29
75
 
@@ -0,0 +1,16 @@
1
+ <style scoped lang="postcss">
2
+ .loading-spinner {
3
+ @apply h-8 w-8;
4
+ }
5
+
6
+ .loading-spinner:after {
7
+ @apply absolute animate-spin border-2 border-primary-default rounded-full h-8 w-8;
8
+
9
+ border-bottom: 2px solid transparent;
10
+ content: '';
11
+ }
12
+ </style>
13
+
14
+ <template>
15
+ <div class="loading-spinner"></div>
16
+ </template>
@@ -1,6 +1,82 @@
1
- <script lang="ts" src="./login"></script>
1
+ <script setup lang="ts">
2
+ import { computed, ref, watch } from 'vue';
2
3
 
3
- <style scoped src="./login.css"></style>
4
+ import { Authentication, Language, LoginLocalisations } from '../../@types';
5
+ import ClUiLanguageSwitcher from '../language-switcher/cl-ui-language-switcher.vue';
6
+
7
+ type PasswordFieldType = 'text' | 'password';
8
+
9
+ const props = withDefaults(defineProps<{
10
+ errors?: string[];
11
+ currentLocale?: string;
12
+ supportedLocales: Language[];
13
+ localisations?: LoginLocalisations;
14
+ logo: string;
15
+ backgroundImage: string;
16
+ loading?: false;
17
+ }>(), {
18
+ errors: () => [],
19
+ currentLocale: 'en-GB',
20
+ localisations: () => ({
21
+ login: 'Login',
22
+ email: 'Email Address',
23
+ password: 'Password',
24
+ validEmail: 'Username is a valid email address.',
25
+ invalidEmail: 'Username must be a valid email address.'
26
+ }),
27
+ loading: false
28
+ });
29
+
30
+ const emit = defineEmits({
31
+ login: null,
32
+ 'update:errors': null,
33
+ 'update:current-locale': null
34
+ });
35
+
36
+ const username = ref('');
37
+ const password = ref('');
38
+
39
+ const passwordFieldType = ref<PasswordFieldType>('password');
40
+ const usernameValid = ref<boolean>(false);
41
+
42
+ const selectedLocale = computed<string>({
43
+ get: () => props.currentLocale,
44
+ set: (value) => emit('update:current-locale', value)
45
+ });
46
+
47
+ /**
48
+ * Emits the current username and password values.
49
+ */
50
+ function login (): void {
51
+ emit('login', {
52
+ username: username.value,
53
+ password: password.value
54
+ } as Authentication);
55
+ }
56
+
57
+ /**
58
+ * Emits an event to clear the currently displayed error.
59
+ */
60
+ function clearErrors (): void {
61
+ emit('update:errors', []);
62
+ }
63
+
64
+ watch(username, () => {
65
+ const emailRegex = /[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)/gi;
66
+
67
+ usernameValid.value = emailRegex.test(username.value);
68
+ });
69
+ </script>
70
+
71
+ <style scoped lang="postcss">
72
+ *[data-valid='true'] {
73
+ @apply border-2 border-primary-default;
74
+ }
75
+
76
+ *[data-valid='false'] {
77
+ @apply border-2 border-danger-default;
78
+ }
79
+ </style>
4
80
 
5
81
  <template>
6
82
  <div class="bg-cover bg-left-bottom h-screen"
@@ -17,8 +93,9 @@
17
93
 
18
94
  <div class="md:pb-0 md:pr-2 md:w-1/4 pb-2 w-full">
19
95
  <div class="inline relative text-grey-4 w-full">
20
- <ph-user class="absolute left-3 top-0.5"
21
- :size="18" />
96
+ <icon class="absolute left-3 top-0.5"
97
+ icon="ph:user"
98
+ :size="18" />
22
99
 
23
100
  <input v-model.trim="username"
24
101
  class="!pb-1 !pl-9 w-full"
@@ -33,8 +110,9 @@
33
110
  class="text-grey-3 text-sm">
34
111
  <div v-show="usernameValid"
35
112
  class="flex mt-2">
36
- <ph-check class="text-primary-default"
37
- :size="16" />
113
+ <icon class="text-primary-default"
114
+ icon="ph:check"
115
+ :size="16" />
38
116
 
39
117
  <em class="align-middle inline-block ml-4">
40
118
  {{ localisations.validEmail }}
@@ -43,7 +121,8 @@
43
121
 
44
122
  <div v-show="!usernameValid"
45
123
  class="flex mt-2">
46
- <ph-x class="text-danger-default"
124
+ <icon class="text-danger-default"
125
+ icon="ph:x"
47
126
  :size="16" />
48
127
 
49
128
  <em class="align-middle inline-block ml-4">
@@ -55,18 +134,21 @@
55
134
 
56
135
  <div class="md:pb-0 md:pr-2 md:w-1/4 pb-2 w-full">
57
136
  <div class="inline relative text-grey-4 w-full">
58
- <ph-lock class="absolute left-3 top-0.5"
59
- :size="18" />
60
-
61
- <ph-eye v-show="passwordFieldType === 'password'"
62
- class="absolute cursor-pointer right-3 top-0.5"
63
- :size="18"
64
- @click="passwordFieldType = 'text'" />
65
-
66
- <ph-eye-slash v-show="passwordFieldType === 'text'"
67
- class="absolute cursor-pointer right-3 top-0.5"
68
- :size="18"
69
- @click="passwordFieldType = 'password'" />
137
+ <icon class="absolute left-3 top-0.5"
138
+ icon="ph:lock"
139
+ :size="18" />
140
+
141
+ <icon v-show="passwordFieldType === 'password'"
142
+ class="absolute cursor-pointer right-3 top-0.5"
143
+ icon="ph:eye"
144
+ :size="18"
145
+ @click="passwordFieldType = 'text'" />
146
+
147
+ <icon v-show="passwordFieldType === 'text'"
148
+ class="absolute cursor-pointer right-3 top-0.5"
149
+ icon="ph:eye-slash"
150
+ :size="18"
151
+ @click="passwordFieldType = 'password'" />
70
152
 
71
153
  <input v-model="password"
72
154
  class="!pb-1 !pl-9 !pr-9 w-full"
@@ -1,4 +1,75 @@
1
- <script lang="ts" src="./modal"></script>
1
+ <script lang="ts">
2
+ export default {
3
+ inheritAttrs: false
4
+ };
5
+ </script>
6
+
7
+ <script setup lang="ts">
8
+ import { computed, ref } from 'vue';
9
+
10
+ type ModalSize = 'x-small' | 'small' | 'medium' | 'large';
11
+
12
+ const props = withDefaults(defineProps<{
13
+ title: string;
14
+ size?: ModalSize;
15
+ confirmButton?: string;
16
+ cancelButton?: string;
17
+ confirmEnabled?: boolean;
18
+ }>(), {
19
+ size: 'medium',
20
+ confirmButton: '',
21
+ cancelButton: '',
22
+ confirmEnabled: true
23
+ });
24
+
25
+ const emit = defineEmits({
26
+ confirm: null,
27
+ cancel: null
28
+ });
29
+
30
+ const visible = ref<boolean>(false);
31
+
32
+ const showConfirmButton = computed(() => props.confirmButton !== '');
33
+ const showCancelButton = computed(() => props.cancelButton !== '');
34
+
35
+ /**
36
+ * Gets the text for a button, either the default or overridden text.
37
+ *
38
+ * @param buttonValue Enable/disable flag or button override text.
39
+ * @returns The label value to use.
40
+ */
41
+ function getButtonText (buttonValue: string | boolean): string {
42
+ return typeof buttonValue === 'string' ? buttonValue : '';
43
+ }
44
+
45
+ const confirmButtonText = computed(() => getButtonText(props.confirmButton));
46
+ const cancelButtonText = computed(() => getButtonText(props.cancelButton));
47
+
48
+ /**
49
+ * Toggles the visibility of the model, either to the opposite of its current value or to the provided value.
50
+ *
51
+ * @param show Whether to show or hide the modal.
52
+ */
53
+ function toggleModal (show?: boolean): void {
54
+ visible.value = typeof show !== 'undefined' ? show : !visible.value;
55
+ }
56
+
57
+ /**
58
+ * Emits an event depending on if the modal has been confirmed or cancelled.
59
+ *
60
+ * @param action Whether the confirm or cancel/close button has been selected.
61
+ */
62
+ function modalAction (action: boolean): void {
63
+ toggleModal(false);
64
+
65
+ if (action) {
66
+ emit('confirm', action);
67
+ }
68
+ else {
69
+ emit('cancel', action);
70
+ }
71
+ }
72
+ </script>
2
73
 
3
74
  <template>
4
75
  <slot name="trigger"
@@ -12,7 +83,8 @@
12
83
  :class="{ 'lg:w-2/12 w-10/12': size === 'x-small', 'w-6/12': size === 'small', 'w-8/12': size === 'medium', 'w-10/12': size === 'large' }">
13
84
  <div class="absolute cursor-pointer h-2 leading-5 right-3 text-black text-center top-3 w-5"
14
85
  @click="modalAction(false)">
15
- <ph-x :size="20" />
86
+ <icon icon="ph:x"
87
+ :size="20" />
16
88
  </div>
17
89
 
18
90
  <div class="border-b border-grey-2 font-semibold h-11 leading-10 overflow-ellipsis overflow-hidden pl-2 pr-8 text-2xl w-full whitespace-nowrap">
@@ -0,0 +1,124 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue';
3
+
4
+ import { NavigationGroup } from '../../@types';
5
+
6
+ withDefaults(defineProps<{
7
+ groups?: NavigationGroup[];
8
+ }>(), {
9
+ groups: () => []
10
+ });
11
+
12
+ defineEmits({
13
+ 'item-select': null
14
+ });
15
+
16
+ const activeGroup = ref<NavigationGroup | null>(null);
17
+ const menuPinned = ref(false);
18
+
19
+ /**
20
+ * Selects a given navigation group.
21
+ *
22
+ * @param group The group which has been selected.
23
+ */
24
+ function selectNavigationGroup (group: NavigationGroup): void {
25
+ activeGroup.value = (activeGroup.value?.name === group.name ? null : group);
26
+ }
27
+
28
+ /**
29
+ * Toggles the pinned state of the navigation.
30
+ */
31
+ function toggleMenuPinned (): void {
32
+ menuPinned.value = !menuPinned.value;
33
+ }
34
+ </script>
35
+
36
+ <style scoped lang="postcss">
37
+ .navslide-enter-from,
38
+ .navslide-leave-to {
39
+ @apply -left-full;
40
+ }
41
+
42
+ #group-container {
43
+ scrollbar-width: 0;
44
+ }
45
+
46
+ #group-container::-webkit-scrollbar {
47
+ @apply hidden;
48
+ }
49
+ </style>
50
+
51
+ <template>
52
+ <div class="flex flex-nowrap h-full max-h-full min-h-full min-w-20 relative self-start w-auto">
53
+ <div v-if="groups"
54
+ id="group-container"
55
+ class="bg-blue-default h-full left-0 max-h-full min-h-full min-w-20 overflow-y-auto self-start sticky w-20 z-30">
56
+ <div v-for="(group, index) in groups"
57
+ :key="index"
58
+ class="flex-grow w-full">
59
+ <div class="cursor-pointer h-20 hover:bg-blue-light transition-colors w-20"
60
+ :class="{ 'bg-blue-light' : group.name === activeGroup?.name }"
61
+ @click="selectNavigationGroup(group);">
62
+ <icon v-if="group.icon !== null"
63
+ class="block h-20 p-7 w-20"
64
+ :icon="group.icon"
65
+ color="#FFFFFF" />
66
+ </div>
67
+ </div>
68
+ </div>
69
+
70
+ <transition name="navslide">
71
+ <div v-if="activeGroup"
72
+ class="bg-grey-0 border-grey-2 border-r max-h-full min-h-full min-w-[260px] p-8 self-start top-0 transition-all z-20"
73
+ :class="{ 'relative' : menuPinned, 'absolute left-20': !menuPinned }">
74
+ <div class="font-bold leading-8 mb-8 pr-8 relative w-full">
75
+ <span class="select-none text-blue-light text-lg">
76
+ {{ activeGroup.caption }}
77
+ </span>
78
+
79
+ <span class="absolute cursor-pointer h-8 hidden leading-8 md:inline-block right-0 text-center top-0 w-8"
80
+ @click="toggleMenuPinned">
81
+ <icon v-if="!menuPinned"
82
+ class="inline-block"
83
+ icon="ph:push-pin" />
84
+ <icon v-else
85
+ class="inline-block"
86
+ icon="ph:push-pin-slash" />
87
+ </span>
88
+ </div>
89
+ <div v-for="(section, sectionIndex) in activeGroup.sections"
90
+ :key="sectionIndex"
91
+ class="mb-8 text-sm w-full">
92
+ <div class="font-bold mb-4 select-none w-full">
93
+ {{ section.caption }}
94
+ </div>
95
+
96
+ <span v-for="(record, recordIndex) in section.records"
97
+ :key="recordIndex"
98
+ class="block cursor-pointer hover:text-blue-light mb-2 transition-colors">
99
+ <template v-if="record.component">
100
+ <component :is="record.component"
101
+ :to="record.link"
102
+ @click="$emit('item-select', record)">
103
+ {{ record.caption }}
104
+ </component>
105
+ </template>
106
+ <a v-else
107
+ :href="record.link"
108
+ :alt="`External link to: ${record.link}`"
109
+ :title="`External link to: ${record.link}`"
110
+ target="new"
111
+ @click="$emit('item-select', record)">
112
+ <icon
113
+ class="inline-block"
114
+ icon="ph:arrow-square-out"
115
+ alt="External Link" />
116
+
117
+ {{ record.caption }}
118
+ </a>
119
+ </span>
120
+ </div>
121
+ </div>
122
+ </transition>
123
+ </div>
124
+ </template>
@@ -1,4 +1,22 @@
1
- <script lang="ts" src="./notification"></script>
1
+ <script lang="ts">
2
+ export default {
3
+ inheritAttrs: false
4
+ };
5
+ </script>
6
+
7
+ <script setup lang="ts">
8
+ import { notifications, removeNotification } from '../../composables/notification';
9
+
10
+ type NotificationPosition = 'top-left' | 'top-centre' | 'top-right' | 'bottom-left' | 'bottom-centre' | 'bottom-right';
11
+
12
+ withDefaults(defineProps<{
13
+ container?: string;
14
+ position?: NotificationPosition;
15
+ }>(), {
16
+ container: 'body',
17
+ position: 'top-right'
18
+ });
19
+ </script>
2
20
 
3
21
  <template>
4
22
  <teleport :to="container">
@@ -27,7 +45,8 @@
27
45
  {{ notification.message }}
28
46
  </span>
29
47
 
30
- <ph-x class="group-hover:opacity-100 inline-block opacity-0"
48
+ <icon class="group-hover:opacity-100 inline-block opacity-0"
49
+ icon="ph:x"
31
50
  :size="16"
32
51
  weight="bold" />
33
52
  </div>
@@ -0,0 +1,145 @@
1
+ <script setup lang="ts">
2
+ import { computed, nextTick } from 'vue';
3
+ import { useI18n } from 'vue-i18n';
4
+
5
+ import { NumberFormat, SliderLocalisations } from '../../@types';
6
+ import { useDebouncer } from '../../utils';
7
+
8
+ const props = withDefaults(defineProps<{
9
+ value: number;
10
+ min: number;
11
+ max: number;
12
+ currentLocale?: string;
13
+ localisations?: SliderLocalisations;
14
+ step?: number;
15
+ enforceStep?: boolean;
16
+ disabled?: boolean;
17
+ }>(), {
18
+ currentLocale: 'en-GB',
19
+ localisations: () => ({
20
+ invalidProps: 'The current combination of props is invalid. Please confirm the values provided are correct.'
21
+ }),
22
+ step: 1,
23
+ enforceStep: false,
24
+ disabled: false
25
+ });
26
+
27
+ const emit = defineEmits({
28
+ 'update:value': null
29
+ });
30
+
31
+ const { n } = useI18n();
32
+ const { debounce } = useDebouncer();
33
+
34
+ const currentValue = computed<number>({
35
+ get: () => props.value,
36
+ set: (value) => emit('update:value', value)
37
+ });
38
+
39
+ const colour = computed<string>(() => props.disabled ? 'rgba(153, 153, 153, 0.8)' : '#9acd32');
40
+ const percentage = computed<number>(() => {
41
+ let value = ((currentValue.value - props.min) / (props.max - props.min)) * 100;
42
+
43
+ if (value < 35 && value > 0) {
44
+ if (value < 20) {
45
+ value += 0.5;
46
+ }
47
+ else {
48
+ value += 0.25;
49
+ }
50
+ }
51
+ else if (value > 65 && value < 100) {
52
+ if (value > 80) {
53
+ value -= 0.5;
54
+ }
55
+ else {
56
+ value -= 0.25;
57
+ }
58
+ }
59
+
60
+ return value;
61
+ });
62
+ const validProps = computed<boolean>(() => props.min <= props.max && props.step > 0);
63
+
64
+ /**
65
+ * Updates the current value and forces a re-render of the input.
66
+ *
67
+ * @param target The input event target.
68
+ * @param forceUpdate $forceUpdate isn't available in the composition API but can be passed in as a parameter.
69
+ */
70
+ // eslint-disable-next-line @typescript-eslint/ban-types
71
+ function updateCurrentValue (target: EventTarget | null, forceUpdate: Function): void {
72
+ const inputValue: string = (target as HTMLInputElement | null)?.value ?? '';
73
+ const value: number = Math.max(Math.min(parseFloat(inputValue) || props.min, props.max), props.min);
74
+
75
+ currentValue.value = props.enforceStep ? Math.ceil(value / props.step) * props.step : value;
76
+
77
+ nextTick(() => forceUpdate());
78
+ }
79
+ </script>
80
+
81
+ <style scoped lang="postcss">
82
+ input[type='range']::-webkit-slider-runnable-track {
83
+ @apply bg-none h-1;
84
+ }
85
+
86
+ input[type='range']::-moz-range-track {
87
+ @apply bg-none rounded-full h-1;
88
+ }
89
+
90
+ input[type='range']::-webkit-slider-thumb {
91
+ @apply appearance-none bg-white border border-solid border-grey-2 rounded-full cursor-pointer h-6 w-6;
92
+
93
+ margin-top: -0.6rem;
94
+ }
95
+
96
+ input[type='range']:disabled::-webkit-slider-thumb {
97
+ @apply hidden;
98
+ }
99
+
100
+ input[type='range']::-moz-range-thumb {
101
+ @apply bg-white border border-grey-2 rounded-full cursor-pointer h-6 w-6;
102
+ }
103
+
104
+ input[type='range']:disabled::-moz-range-thumb {
105
+ @apply h-0 w-0;
106
+ }
107
+ </style>
108
+
109
+ <template>
110
+ <div v-if="validProps"
111
+ v-bind="$attrs"
112
+ class="flex flex-wrap items-center">
113
+ <input class="!text-sm md:!h-8 md:!w-auto"
114
+ type="number"
115
+ :value="currentValue"
116
+ :min="min"
117
+ :max="max"
118
+ :step="step"
119
+ :disabled="disabled"
120
+ @input="debounce(updateCurrentValue, [ $event.target, $forceUpdate ])">
121
+
122
+ <div class="flex flex-1 flex-wrap items-center md:mt-0 mt-3">
123
+ <span class="bg-grey-0 leading-6 md:ml-2 ml-0 px-3 rounded-full text-center text-xs">
124
+ {{ n(min, Number.isInteger(min) ? NumberFormat.INTEGER : NumberFormat.DECIMAL, currentLocale ) }}
125
+ </span>
126
+
127
+ <input v-model.number="currentValue"
128
+ class="align-middle appearance-none bg-gradient-to-r border border-grey-2 delay-500 ease-in flex-1 h-5 ml-2 outline-none rounded-full transition-colors"
129
+ :style="{ background: `linear-gradient(to right, ${colour} 0%, ${colour} ${percentage}%, white ${percentage}%, white 100%)` }"
130
+ type="range"
131
+ :min="min"
132
+ :max="max"
133
+ :step="step"
134
+ :disabled="disabled">
135
+
136
+ <span class="bg-grey-0 leading-6 ml-2 px-3 rounded-full text-center text-xs">
137
+ {{ n(max, Number.isInteger(max) ? NumberFormat.INTEGER : NumberFormat.DECIMAL, currentLocale ) }}
138
+ </span>
139
+ </div>
140
+ </div>
141
+ <div v-else
142
+ class="emphasis-danger text-sm w-full">
143
+ {{ localisations.invalidProps }}
144
+ </div>
145
+ </template>
@@ -1,30 +0,0 @@
1
- <script lang="ts" src="./accordion"></script>
2
-
3
- <template>
4
- <ul class="border border-grey-2">
5
- <li v-for="(item, index) in itemList"
6
- :key="index"
7
- class="relative">
8
- <div class="border border-grey-2 cursor-pointer ease-in-out focus:border-blue-light focus:outline-none font-semibold hover:scale-101 p-4 scale-100 transform-gpu transition-transform"
9
- :class="{
10
- 'border-b-2': item.open,
11
- [`bg-${titleBackground}`]: true
12
- }"
13
- :tabindex="index + 1"
14
- @click="toggleItem(item)"
15
- @keyup.enter="toggleItem(item)">
16
- {{ item.title }}
17
- </div>
18
-
19
- <div v-show="item.open"
20
- class="border-b border-grey-2 p-6 text-sm"
21
- :class="[ `bg-${contentBackground}` ]">
22
- <slot :name="item.id"></slot>
23
- </div>
24
-
25
- <ph-caret-down class="absolute cursor-pointer right-4 top-4 transform transition-transform"
26
- :class="{ 'rotate-180': item.open, 'rotate-0': !item.open }"
27
- @click="toggleItem(item)" />
28
- </li>
29
- </ul>
30
- </template>