@iankibetsh/sh-tailwind 0.1.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 (40) hide show
  1. package/README.md +177 -0
  2. package/dist/sh-tailwind.cjs.js +1 -0
  3. package/dist/sh-tailwind.es.js +3695 -0
  4. package/package.json +56 -0
  5. package/src/components/actions/ShConfirmAction.vue +78 -0
  6. package/src/components/actions/ShSilentAction.vue +66 -0
  7. package/src/components/actions/ShSpinner.vue +6 -0
  8. package/src/components/form/ShForm.vue +272 -0
  9. package/src/components/form/ShFormSteps.vue +30 -0
  10. package/src/components/form/inputs/DateInput.vue +29 -0
  11. package/src/components/form/inputs/EmailInput.vue +27 -0
  12. package/src/components/form/inputs/NumberInput.vue +32 -0
  13. package/src/components/form/inputs/PasswordInput.vue +47 -0
  14. package/src/components/form/inputs/PhoneInput.vue +190 -0
  15. package/src/components/form/inputs/SelectInput.vue +50 -0
  16. package/src/components/form/inputs/ShSuggest.vue +198 -0
  17. package/src/components/form/inputs/TextAreaInput.vue +27 -0
  18. package/src/components/form/inputs/TextInput.vue +26 -0
  19. package/src/components/overlay/ShDialog.vue +143 -0
  20. package/src/components/overlay/ShDialogBtn.vue +41 -0
  21. package/src/components/overlay/ShDialogForm.vue +80 -0
  22. package/src/components/overlay/ShDrawer.vue +129 -0
  23. package/src/components/overlay/ShDrawerBtn.vue +40 -0
  24. package/src/components/table/ShTable.vue +472 -0
  25. package/src/components/table/ShTablePagination.vue +96 -0
  26. package/src/composables/useDialog.js +68 -0
  27. package/src/composables/useScrollLock.js +19 -0
  28. package/src/data/countries.js +1474 -0
  29. package/src/index.js +45 -0
  30. package/src/plugin/ShTailwind.js +36 -0
  31. package/src/table/localQuery.js +60 -0
  32. package/src/table/tableCache.js +116 -0
  33. package/src/table/useTableData.js +125 -0
  34. package/src/theme/defaultTheme.js +148 -0
  35. package/src/theme/keys.js +3 -0
  36. package/src/theme/useTheme.js +11 -0
  37. package/src/utils/deepMerge.js +19 -0
  38. package/src/utils/normalizeField.js +61 -0
  39. package/src/utils/normalizeOptions.js +18 -0
  40. package/src/utils/strings.js +11 -0
@@ -0,0 +1,80 @@
1
+ <script setup>
2
+ import { computed, ref } from 'vue'
3
+ import ShDialog from './ShDialog.vue'
4
+ import ShForm from '../form/ShForm.vue'
5
+ import { useTheme } from '../../theme/useTheme.js'
6
+
7
+ const props = defineProps({
8
+ // dialog
9
+ title: String,
10
+ size: { type: String, default: 'md' },
11
+ static: Boolean,
12
+ retainDialog: Boolean,
13
+ btnClass: String,
14
+ dialogClasses: Object,
15
+ // form (passed through to ShForm)
16
+ action: { type: String, required: true },
17
+ method: { type: String, default: 'post' },
18
+ fields: { type: Array, required: true },
19
+ currentData: Object,
20
+ steps: Array,
21
+ submitLabel: { type: String, default: 'Submit' },
22
+ successMessage: String,
23
+ retainData: Boolean,
24
+ preSubmit: Function,
25
+ hiddenId: { type: Boolean, default: true },
26
+ classes: Object
27
+ })
28
+ const emit = defineEmits(['success', 'error', 'fieldChanged', 'opened', 'closed'])
29
+
30
+ const buttons = useTheme('buttons')
31
+ const open = ref(false)
32
+
33
+ // Re-key the form whenever the record being edited changes
34
+ const formKey = computed(() => JSON.stringify(props.currentData ?? {}))
35
+
36
+ const onSuccess = (data) => {
37
+ emit('success', data)
38
+ }
39
+
40
+ defineExpose({
41
+ show: () => { open.value = true },
42
+ close: () => { open.value = false }
43
+ })
44
+ </script>
45
+
46
+ <template>
47
+ <button type="button" :class="btnClass ?? buttons.primary" @click="open = true">
48
+ <slot name="trigger">{{ title ?? 'Open form' }}</slot>
49
+ </button>
50
+ <ShDialog
51
+ v-model:open="open"
52
+ :title="title"
53
+ :size="size"
54
+ :static="static"
55
+ :retain-on-success="retainDialog"
56
+ :classes="dialogClasses"
57
+ @opened="$emit('opened')"
58
+ @closed="$emit('closed')"
59
+ >
60
+ <ShForm
61
+ :key="formKey"
62
+ :action="action"
63
+ :method="method"
64
+ :fields="fields"
65
+ :current-data="currentData"
66
+ :steps="steps"
67
+ :submit-label="submitLabel"
68
+ :success-message="successMessage"
69
+ :retain-data="retainData"
70
+ :pre-submit="preSubmit"
71
+ :hidden-id="hiddenId"
72
+ :classes="classes"
73
+ @success="onSuccess"
74
+ @error="$emit('error', $event)"
75
+ @field-changed="(name, value, data) => $emit('fieldChanged', name, value, data)"
76
+ >
77
+ <slot />
78
+ </ShForm>
79
+ </ShDialog>
80
+ </template>
@@ -0,0 +1,129 @@
1
+ <script setup>
2
+ import { computed, provide, ref, watch } from 'vue'
3
+ import { useDialog } from '../../composables/useDialog.js'
4
+ import { useTheme } from '../../theme/useTheme.js'
5
+ import { SH_DIALOG_CONTEXT } from '../../theme/keys.js'
6
+
7
+ const props = defineProps({
8
+ open: Boolean,
9
+ title: String,
10
+ position: { type: String, default: 'end' }, // start | end | top | bottom
11
+ size: { type: String, default: 'md' },
12
+ static: Boolean,
13
+ hideClose: Boolean,
14
+ classes: Object
15
+ })
16
+ const emit = defineEmits(['update:open', 'opened', 'closed'])
17
+
18
+ const t = useTheme('drawer', computed(() => props.classes))
19
+ const panel = ref(null)
20
+ const rendered = ref(false)
21
+ const visible = ref(false)
22
+
23
+ // Whole class literals per position so Tailwind @source extraction works
24
+ const positions = {
25
+ start: { panel: 'inset-y-0 left-0 h-full w-full', hidden: '-translate-x-full' },
26
+ end: { panel: 'inset-y-0 right-0 h-full w-full', hidden: 'translate-x-full' },
27
+ top: { panel: 'inset-x-0 top-0 w-full', hidden: '-translate-y-full' },
28
+ bottom: { panel: 'inset-x-0 bottom-0 w-full', hidden: 'translate-y-full' }
29
+ }
30
+ const pos = computed(() => positions[props.position] ?? positions.end)
31
+ const isVertical = computed(() => ['top', 'bottom'].includes(props.position))
32
+ const sizeClass = computed(() =>
33
+ isVertical.value
34
+ ? (t.value.sizesVertical[props.size] ?? t.value.sizesVertical.md)
35
+ : (t.value.sizes[props.size] ?? t.value.sizes.md)
36
+ )
37
+
38
+ const dialog = useDialog({
39
+ isStatic: () => props.static,
40
+ onClose: () => emit('update:open', false)
41
+ })
42
+
43
+ const show = () => {
44
+ rendered.value = true
45
+ dialog.show()
46
+ requestAnimationFrame(() => {
47
+ visible.value = true
48
+ })
49
+ if (!props.open) {
50
+ emit('update:open', true)
51
+ }
52
+ }
53
+
54
+ const close = (reason) => {
55
+ visible.value = false
56
+ dialog.close(reason)
57
+ }
58
+
59
+ watch(() => props.open, (value) => {
60
+ if (value) {
61
+ show()
62
+ } else if (visible.value) {
63
+ close('prop')
64
+ }
65
+ }, { immediate: true })
66
+
67
+ watch(dialog.isOpen, (value) => {
68
+ if (!value && visible.value) {
69
+ visible.value = false
70
+ }
71
+ })
72
+
73
+ provide(SH_DIALOG_CONTEXT, {
74
+ close: () => close('context'),
75
+ requestClose: (reason) => close(reason)
76
+ })
77
+
78
+ defineExpose({ show, close })
79
+ </script>
80
+
81
+ <template>
82
+ <teleport to="body">
83
+ <div v-if="rendered" class="fixed inset-0" :style="{ zIndex: dialog.zIndex.value }">
84
+ <Transition
85
+ enter-active-class="transition-opacity duration-200 ease-out"
86
+ enter-from-class="opacity-0"
87
+ enter-to-class="opacity-100"
88
+ leave-active-class="transition-opacity duration-150 ease-in"
89
+ leave-from-class="opacity-100"
90
+ leave-to-class="opacity-0"
91
+ >
92
+ <div v-show="visible" :class="t.backdrop" @click="dialog.onBackdrop" />
93
+ </Transition>
94
+ <Transition
95
+ enter-active-class="transition-transform duration-250 ease-out"
96
+ :enter-from-class="pos.hidden"
97
+ enter-to-class="translate-x-0 translate-y-0"
98
+ leave-active-class="transition-transform duration-200 ease-in"
99
+ leave-from-class="translate-x-0 translate-y-0"
100
+ :leave-to-class="pos.hidden"
101
+ @after-enter="panel?.focus(); emit('opened')"
102
+ @after-leave="rendered = false; emit('closed')"
103
+ >
104
+ <div
105
+ v-show="visible"
106
+ ref="panel"
107
+ tabindex="-1"
108
+ role="dialog"
109
+ aria-modal="true"
110
+ :class="[t.panel, pos.panel, sizeClass]"
111
+ >
112
+ <header v-if="title || $slots.title || !hideClose" :class="t.header">
113
+ <slot name="title">
114
+ <h3 :class="t.title">{{ title }}</h3>
115
+ </slot>
116
+ <button v-if="!hideClose" type="button" :class="t.closeBtn" aria-label="Close" @click="close('button')">
117
+ <svg xmlns="http://www.w3.org/2000/svg" class="size-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
118
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
119
+ </svg>
120
+ </button>
121
+ </header>
122
+ <div :class="t.body">
123
+ <slot :close="close" />
124
+ </div>
125
+ </div>
126
+ </Transition>
127
+ </div>
128
+ </teleport>
129
+ </template>
@@ -0,0 +1,40 @@
1
+ <script setup>
2
+ import { ref } from 'vue'
3
+ import ShDrawer from './ShDrawer.vue'
4
+ import { useTheme } from '../../theme/useTheme.js'
5
+
6
+ defineProps({
7
+ title: String,
8
+ position: { type: String, default: 'end' },
9
+ size: { type: String, default: 'md' },
10
+ static: Boolean,
11
+ hideClose: Boolean,
12
+ btnClass: String,
13
+ classes: Object
14
+ })
15
+ defineEmits(['opened', 'closed'])
16
+
17
+ const buttons = useTheme('buttons')
18
+ const open = ref(false)
19
+ </script>
20
+
21
+ <template>
22
+ <button type="button" :class="btnClass ?? buttons.secondary" @click="open = true">
23
+ <slot name="trigger">Open</slot>
24
+ </button>
25
+ <ShDrawer
26
+ v-model:open="open"
27
+ :title="title"
28
+ :position="position"
29
+ :size="size"
30
+ :static="static"
31
+ :hide-close="hideClose"
32
+ :classes="classes"
33
+ @opened="$emit('opened')"
34
+ @closed="$emit('closed')"
35
+ >
36
+ <template #default="{ close }">
37
+ <slot :close="close" />
38
+ </template>
39
+ </ShDrawer>
40
+ </template>