@naptics/vue-collection 0.2.15 → 0.3.1
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.
- package/.github/workflows/build.yml +26 -0
- package/.github/workflows/deploy-demo.yml +46 -0
- package/.github/workflows/deploy-lib.yml +59 -0
- package/.gitlab-ci.yml +57 -0
- package/.nvmrc +1 -0
- package/.prettierrc +8 -0
- package/.vscode/extensions.json +10 -0
- package/.vscode/launch.json +23 -0
- package/.vscode/settings.json +13 -0
- package/babel.config.json +3 -0
- package/components/NAlert.d.ts +1 -44
- package/components/NBadge.d.ts +1 -133
- package/components/NBreadcrub.d.ts +2 -106
- package/components/NBreadcrub.js +1 -1
- package/components/NButton.d.ts +2 -118
- package/components/NCheckbox.d.ts +1 -32
- package/components/NCheckboxLabel.d.ts +1 -45
- package/components/NCheckboxLabel.js +1 -1
- package/components/NCrudModal.d.ts +7 -251
- package/components/NCrudModal.js +1 -1
- package/components/NDialog.d.ts +1 -110
- package/components/NDialog.js +1 -1
- package/components/NDropdown.d.ts +1 -69
- package/components/NDropdown.js +1 -1
- package/components/NDropzone.d.ts +1 -115
- package/components/NDropzone.js +1 -1
- package/components/NForm.d.ts +1 -23
- package/components/NFormModal.d.ts +7 -151
- package/components/NIconButton.d.ts +3 -159
- package/components/NIconButton.js +1 -1
- package/components/NIconCircle.d.ts +1 -87
- package/components/NInput.d.ts +1 -164
- package/components/NInput.js +1 -1
- package/components/NInputPhone.d.ts +2 -114
- package/components/NInputPhone.js +1 -1
- package/components/NInputSelect.d.ts +2 -187
- package/components/NInputSelect.js +1 -1
- package/components/NInputSuggestion.d.ts +2 -155
- package/components/NInputSuggestion.js +1 -1
- package/components/NLink.d.ts +6 -70
- package/components/NLink.js +8 -1
- package/components/NList.d.ts +1 -43
- package/components/NList.js +1 -1
- package/components/NLoadingIndicator.d.ts +1 -49
- package/components/NModal.d.ts +12 -250
- package/components/NModal.js +15 -9
- package/components/NPagination.d.ts +1 -63
- package/components/NSearchbar.d.ts +1 -56
- package/components/NSearchbarList.d.ts +3 -63
- package/components/NSearchbarList.js +1 -1
- package/components/NSelect.d.ts +2 -148
- package/components/NSelect.js +1 -1
- package/components/NSuggestionList.d.ts +3 -126
- package/components/NSuggestionList.js +5 -2
- package/components/NTable.d.ts +1 -85
- package/components/NTable.js +12 -6
- package/components/NTableAction.d.ts +2 -46
- package/components/NTableAction.js +1 -1
- package/components/NTextArea.d.ts +2 -181
- package/components/NTextArea.js +1 -1
- package/components/NTooltip.d.ts +1 -105
- package/components/NTooltip.js +1 -1
- package/components/NValInput.d.ts +7 -182
- package/components/NValInput.js +1 -1
- package/env.d.ts +15 -0
- package/eslint.config.cjs +29 -0
- package/index.html +13 -0
- package/package.json +21 -19
- package/postcss.config.js +6 -0
- package/public/favicon.ico +0 -0
- package/scripts/build-lib.sh +52 -0
- package/scripts/sync-node-types.js +70 -0
- package/src/demo/App.css +9 -0
- package/src/demo/App.tsx +5 -0
- package/src/demo/components/ColorGrid.tsx +26 -0
- package/src/demo/components/ComponentGrid.tsx +26 -0
- package/src/demo/components/ComponentSection.tsx +30 -0
- package/src/demo/components/VariantSection.tsx +18 -0
- package/src/demo/i18n/de.ts +7 -0
- package/src/demo/i18n/en.ts +7 -0
- package/src/demo/i18n/index.ts +24 -0
- package/src/demo/main.ts +13 -0
- package/src/demo/router/index.ts +21 -0
- package/src/demo/views/HomeView.tsx +94 -0
- package/src/demo/views/NavigationView.tsx +43 -0
- package/src/demo/views/presentation/AlertView.tsx +40 -0
- package/src/demo/views/presentation/BadgeView.tsx +61 -0
- package/src/demo/views/presentation/BreadcrumbView.tsx +52 -0
- package/src/demo/views/presentation/ButtonView.tsx +49 -0
- package/src/demo/views/presentation/CheckboxView.tsx +59 -0
- package/src/demo/views/presentation/DropdownView.tsx +59 -0
- package/src/demo/views/presentation/DropzoneView.tsx +39 -0
- package/src/demo/views/presentation/IconButtonView.tsx +47 -0
- package/src/demo/views/presentation/IconCircleView.tsx +38 -0
- package/src/demo/views/presentation/InputView.tsx +179 -0
- package/src/demo/views/presentation/LinkView.tsx +60 -0
- package/src/demo/views/presentation/ListView.tsx +29 -0
- package/src/demo/views/presentation/LoadingIndicatorView.tsx +38 -0
- package/src/demo/views/presentation/ModalView.tsx +210 -0
- package/src/demo/views/presentation/PaginationView.tsx +25 -0
- package/src/demo/views/presentation/SearchbarView.tsx +80 -0
- package/src/demo/views/presentation/TableView.tsx +146 -0
- package/src/demo/views/presentation/TooltipView.tsx +86 -0
- package/src/lib/components/NAlert.tsx +85 -0
- package/src/lib/components/NBadge.tsx +75 -0
- package/src/lib/components/NBreadcrub.tsx +97 -0
- package/src/lib/components/NButton.tsx +80 -0
- package/src/lib/components/NCheckbox.tsx +55 -0
- package/src/lib/components/NCheckboxLabel.tsx +51 -0
- package/src/lib/components/NCrudModal.tsx +133 -0
- package/src/lib/components/NDialog.tsx +182 -0
- package/src/lib/components/NDropdown.tsx +167 -0
- package/src/lib/components/NDropzone.tsx +265 -0
- package/src/lib/components/NForm.tsx +32 -0
- package/src/lib/components/NFormModal.tsx +66 -0
- package/src/lib/components/NIconButton.tsx +92 -0
- package/src/lib/components/NIconCircle.tsx +78 -0
- package/src/lib/components/NInput.css +11 -0
- package/src/lib/components/NInput.tsx +139 -0
- package/src/lib/components/NInputPhone.tsx +53 -0
- package/src/lib/components/NInputSelect.tsx +126 -0
- package/src/lib/components/NInputSuggestion.tsx +80 -0
- package/src/lib/components/NLink.tsx +82 -0
- package/src/lib/components/NList.tsx +67 -0
- package/src/lib/components/NLoadingIndicator.css +46 -0
- package/src/lib/components/NLoadingIndicator.tsx +63 -0
- package/src/lib/components/NModal.tsx +243 -0
- package/src/lib/components/NPagination.css +15 -0
- package/src/lib/components/NPagination.tsx +131 -0
- package/src/lib/components/NSearchbar.tsx +78 -0
- package/src/lib/components/NSearchbarList.tsx +47 -0
- package/src/lib/components/NSelect.tsx +128 -0
- package/src/lib/components/NSuggestionList.tsx +216 -0
- package/src/lib/components/NTable.css +3 -0
- package/src/lib/components/NTable.tsx +247 -0
- package/src/lib/components/NTableAction.tsx +49 -0
- package/src/lib/components/NTextArea.tsx +159 -0
- package/src/lib/components/NTooltip.css +37 -0
- package/src/lib/components/NTooltip.tsx +250 -0
- package/src/lib/components/NValInput.tsx +163 -0
- package/src/lib/components/ValidatedForm.ts +71 -0
- package/src/lib/components/__tests__/NButton.spec.tsx +26 -0
- package/src/lib/components/__tests__/NCheckbox.spec.tsx +39 -0
- package/src/lib/i18n/de/vue-collection.json +58 -0
- package/src/lib/i18n/en/vue-collection.json +58 -0
- package/src/lib/i18n/index.ts +54 -0
- package/src/lib/index.ts +2 -0
- package/src/lib/jsx.d.ts +13 -0
- package/src/lib/utils/__tests__/identifiable.spec.ts +72 -0
- package/src/lib/utils/__tests__/validation.spec.ts +92 -0
- package/src/lib/utils/breakpoints.ts +47 -0
- package/src/lib/utils/component.tsx +131 -0
- package/src/lib/utils/deferred.ts +28 -0
- package/src/lib/utils/identifiable.ts +87 -0
- package/src/lib/utils/stringMaxLength.ts +25 -0
- package/src/lib/utils/tailwind.ts +41 -0
- package/src/lib/utils/utils.ts +90 -0
- package/src/lib/utils/vModel.ts +260 -0
- package/src/lib/utils/validation.ts +189 -0
- package/src/lib/utils/vue.ts +25 -0
- package/tailwind.config.js +38 -0
- package/tsconfig.config.json +9 -0
- package/tsconfig.demo.json +19 -0
- package/tsconfig.json +16 -0
- package/tsconfig.lib.json +18 -0
- package/tsconfig.vitest.json +8 -0
- package/utils/breakpoints.d.ts +1 -1
- package/utils/component.d.ts +3 -7
- package/utils/component.js +5 -2
- package/utils/identifiable.js +5 -1
- package/vite.config.ts +28 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { createComponent } from '../utils/component'
|
|
2
|
+
import { computed, useCssVars } from 'vue'
|
|
3
|
+
import './NLoadingIndicator.css'
|
|
4
|
+
|
|
5
|
+
export const nLoadingIndicator = {
|
|
6
|
+
/**
|
|
7
|
+
* The color of the loading-indicator.
|
|
8
|
+
*/
|
|
9
|
+
color: {
|
|
10
|
+
type: String,
|
|
11
|
+
default: 'primary',
|
|
12
|
+
},
|
|
13
|
+
/**
|
|
14
|
+
* The shade of the loading-indicator.
|
|
15
|
+
*/
|
|
16
|
+
shade: {
|
|
17
|
+
type: Number,
|
|
18
|
+
default: 400,
|
|
19
|
+
},
|
|
20
|
+
/**
|
|
21
|
+
* The height of the loading-indicator in px.
|
|
22
|
+
*/
|
|
23
|
+
size: {
|
|
24
|
+
type: Number,
|
|
25
|
+
default: 10,
|
|
26
|
+
},
|
|
27
|
+
} as const
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The `NLoadingIndicator` is a styled loading indicator.
|
|
31
|
+
*/
|
|
32
|
+
const Component = createComponent('NLoadingIndicator', nLoadingIndicator, props => {
|
|
33
|
+
const gap = computed(() => (props.size / 13) * 24)
|
|
34
|
+
|
|
35
|
+
const totalWidth = computed(() => gap.value * 2 + props.size)
|
|
36
|
+
|
|
37
|
+
useCssVars(() => ({
|
|
38
|
+
'n-loading-indicator-gap': `${gap.value}px`,
|
|
39
|
+
}))
|
|
40
|
+
|
|
41
|
+
return () => (
|
|
42
|
+
<div class="lds-ellipsis" style={`height:${props.size}px;width:${totalWidth.value}px`}>
|
|
43
|
+
<div
|
|
44
|
+
class={`bg-${props.color}-${props.shade}`}
|
|
45
|
+
style={`height:${props.size}px;width:${props.size}px;left:0px`}
|
|
46
|
+
/>
|
|
47
|
+
<div
|
|
48
|
+
class={`bg-${props.color}-${props.shade}`}
|
|
49
|
+
style={`height:${props.size}px;width:${props.size}px;left:0px`}
|
|
50
|
+
/>
|
|
51
|
+
<div
|
|
52
|
+
class={`bg-${props.color}-${props.shade}`}
|
|
53
|
+
style={`height:${props.size}px;width:${props.size}px;left:${gap.value}px`}
|
|
54
|
+
/>
|
|
55
|
+
<div
|
|
56
|
+
class={`bg-${props.color}-${props.shade}`}
|
|
57
|
+
style={`height:${props.size}px;width:${props.size}px;left:${2 * gap.value}px`}
|
|
58
|
+
/>
|
|
59
|
+
</div>
|
|
60
|
+
)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
export { Component as NLoadingIndicator, Component as default }
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { createComponentWithSlots } from '../utils/component'
|
|
2
|
+
import type { PropType } from 'vue'
|
|
3
|
+
import { Dialog, DialogOverlay, DialogTitle, TransitionRoot, TransitionChild } from '@headlessui/vue'
|
|
4
|
+
import NButton from './NButton'
|
|
5
|
+
import NIconButton from './NIconButton'
|
|
6
|
+
import { XMarkIcon } from '@heroicons/vue/24/solid'
|
|
7
|
+
import { trsl } from '../i18n'
|
|
8
|
+
import type { TWMaxWidth } from '../utils/tailwind'
|
|
9
|
+
import { vModelProps } from '../utils/vModel'
|
|
10
|
+
|
|
11
|
+
export const nModalProps = {
|
|
12
|
+
...vModelProps(Boolean),
|
|
13
|
+
/**
|
|
14
|
+
* If set to `true` the header of the modal is hidden.
|
|
15
|
+
*/
|
|
16
|
+
hideHeader: Boolean,
|
|
17
|
+
/**
|
|
18
|
+
* If set to `true` the footer of the modal is hidden.
|
|
19
|
+
*/
|
|
20
|
+
hideFooter: Boolean,
|
|
21
|
+
/**
|
|
22
|
+
* If set to `true` the X-button in the top right is hidden.
|
|
23
|
+
*/
|
|
24
|
+
hideX: Boolean,
|
|
25
|
+
/**
|
|
26
|
+
* The maximum width of the modal. A regular tailwind class.
|
|
27
|
+
*/
|
|
28
|
+
maxWidth: {
|
|
29
|
+
type: String as PropType<TWMaxWidth>,
|
|
30
|
+
default: 'max-w-md',
|
|
31
|
+
},
|
|
32
|
+
/**
|
|
33
|
+
* The vertical position of the modal. Default is `center`.
|
|
34
|
+
*/
|
|
35
|
+
verticalPosition: {
|
|
36
|
+
type: String as PropType<'start' | 'center' | 'end'>,
|
|
37
|
+
default: 'center',
|
|
38
|
+
},
|
|
39
|
+
/**
|
|
40
|
+
* The horizontal position of the modal. Default is `center`.
|
|
41
|
+
*/
|
|
42
|
+
horizontalPosition: {
|
|
43
|
+
type: String as PropType<'start' | 'center' | 'end'>,
|
|
44
|
+
default: 'center',
|
|
45
|
+
},
|
|
46
|
+
/**
|
|
47
|
+
* The title of the modal which is displayed in the header.
|
|
48
|
+
*/
|
|
49
|
+
title: String,
|
|
50
|
+
/**
|
|
51
|
+
* The text of the ok-button.
|
|
52
|
+
*/
|
|
53
|
+
okText: {
|
|
54
|
+
type: String,
|
|
55
|
+
default: trsl('vue-collection.action.save'),
|
|
56
|
+
},
|
|
57
|
+
/**
|
|
58
|
+
* The color of the ok-button.
|
|
59
|
+
*/
|
|
60
|
+
okColor: {
|
|
61
|
+
type: String,
|
|
62
|
+
default: 'primary',
|
|
63
|
+
},
|
|
64
|
+
/**
|
|
65
|
+
* If set to `true` the modal is closed when `onOk` is called.
|
|
66
|
+
*/
|
|
67
|
+
closeOnOk: {
|
|
68
|
+
type: Boolean,
|
|
69
|
+
default: true,
|
|
70
|
+
},
|
|
71
|
+
/**
|
|
72
|
+
* If set to `true` the ok-button is hidden.
|
|
73
|
+
*/
|
|
74
|
+
hideOk: Boolean,
|
|
75
|
+
/**
|
|
76
|
+
* If set to `true` the ok-button is disabled.
|
|
77
|
+
*/
|
|
78
|
+
okDisabled: Boolean,
|
|
79
|
+
/**
|
|
80
|
+
* The text of the cancel-button.
|
|
81
|
+
*/
|
|
82
|
+
cancelText: {
|
|
83
|
+
type: String,
|
|
84
|
+
default: trsl('vue-collection.action.cancel'),
|
|
85
|
+
},
|
|
86
|
+
/**
|
|
87
|
+
* The color of the cancel-button.
|
|
88
|
+
*/
|
|
89
|
+
cancelColor: {
|
|
90
|
+
type: String,
|
|
91
|
+
default: 'default',
|
|
92
|
+
},
|
|
93
|
+
/**
|
|
94
|
+
* If set to `true`, the modal is closed when clicking on the background.
|
|
95
|
+
* This will call `onCancel`. Default is `true`.
|
|
96
|
+
*/
|
|
97
|
+
closeOnBackground: {
|
|
98
|
+
type: Boolean,
|
|
99
|
+
default: true,
|
|
100
|
+
},
|
|
101
|
+
/**
|
|
102
|
+
* If set to `true` the cancel-button is hidden.
|
|
103
|
+
*/
|
|
104
|
+
hideCancel: Boolean,
|
|
105
|
+
/**
|
|
106
|
+
* This is called when the ok-button was clicked.
|
|
107
|
+
*/
|
|
108
|
+
onOk: Function as PropType<() => void>,
|
|
109
|
+
/**
|
|
110
|
+
* This is called when the cancel-button or X-button was clicked or
|
|
111
|
+
* if the modal was closed by clicking on the background.
|
|
112
|
+
*/
|
|
113
|
+
onCancel: Function as PropType<() => void>,
|
|
114
|
+
/**
|
|
115
|
+
* A slot to replace the whole modal content including all buttons, header and footer.
|
|
116
|
+
*/
|
|
117
|
+
modal: Function as PropType<(props: ModalSlotProps) => JSX.Element>,
|
|
118
|
+
/**
|
|
119
|
+
* A slot to replace the whole content section, i.e. everything between the header and the footer.
|
|
120
|
+
*/
|
|
121
|
+
content: Function as PropType<(props: ModalSlotProps) => JSX.Element>,
|
|
122
|
+
/**
|
|
123
|
+
* A slot to replace the whole header section (excluding the x).
|
|
124
|
+
*/
|
|
125
|
+
header: Function as PropType<() => JSX.Element>,
|
|
126
|
+
/**
|
|
127
|
+
* A slot to replace the whole footer section.
|
|
128
|
+
*/
|
|
129
|
+
footer: Function as PropType<(props: ModalSlotProps) => JSX.Element>,
|
|
130
|
+
} as const
|
|
131
|
+
|
|
132
|
+
export type ModalSlotProps = { ok: () => void; cancel: () => void }
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The `NModal` is the base component for all modals and dialogs.
|
|
136
|
+
* It provides the core mechanics to display a window in front of everything else.
|
|
137
|
+
*/
|
|
138
|
+
const Component = createComponentWithSlots('NModal', nModalProps, ['modal', 'header', 'footer'], (props, { slots }) => {
|
|
139
|
+
const ok = () => {
|
|
140
|
+
props.onOk?.()
|
|
141
|
+
if (props.closeOnOk) props.onUpdateValue?.(false)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const cancel = () => {
|
|
145
|
+
props.onCancel?.()
|
|
146
|
+
props.onUpdateValue?.(false)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return () => (
|
|
150
|
+
<TransitionRoot as="template" show={props.value}>
|
|
151
|
+
<Dialog as="div" static class="fixed z-40 inset-0 overflow-y-auto" onClose={cancel} open={props.value}>
|
|
152
|
+
<div class={`flex items-${props.verticalPosition} justify-${props.horizontalPosition} min-h-screen`}>
|
|
153
|
+
<TransitionChild
|
|
154
|
+
as="template"
|
|
155
|
+
enter="ease-out duration-300"
|
|
156
|
+
enter-from="opacity-0"
|
|
157
|
+
enter-to="opacity-100"
|
|
158
|
+
leave="ease-in duration-200"
|
|
159
|
+
leave-from="opacity-100"
|
|
160
|
+
leave-to="opacity-0"
|
|
161
|
+
>
|
|
162
|
+
{props.closeOnBackground ? (
|
|
163
|
+
<DialogOverlay class="fixed inset-0 bg-default-700 bg-opacity-75" />
|
|
164
|
+
) : (
|
|
165
|
+
<div class="fixed inset-0 bg-default-700 bg-opacity-75" />
|
|
166
|
+
)}
|
|
167
|
+
</TransitionChild>
|
|
168
|
+
|
|
169
|
+
{/* This element is to trick the browser into centering the modal contents. */}
|
|
170
|
+
<span class="hidden align-middle h-screen" aria-hidden="true">
|
|
171
|
+
​
|
|
172
|
+
</span>
|
|
173
|
+
|
|
174
|
+
<TransitionChild
|
|
175
|
+
as="template"
|
|
176
|
+
enter="ease-out duration-300"
|
|
177
|
+
enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
178
|
+
enter-to="opacity-100 translate-y-0 sm:scale-100"
|
|
179
|
+
leave="ease-in duration-200"
|
|
180
|
+
leave-from="opacity-100 translate-y-0 sm:scale-100"
|
|
181
|
+
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
182
|
+
>
|
|
183
|
+
<div class={['transform m-4 w-full align-middle', props.maxWidth]}>
|
|
184
|
+
{props.modal?.({ ok, cancel }) || (
|
|
185
|
+
<div
|
|
186
|
+
class={[
|
|
187
|
+
'shadow-xl rounded-lg bg-white divide-y divide-default-100',
|
|
188
|
+
props.maxWidth,
|
|
189
|
+
]}
|
|
190
|
+
>
|
|
191
|
+
{!props.hideX && (
|
|
192
|
+
<div class="sm:block absolute top-0 right-0 mt-3 mr-3">
|
|
193
|
+
<NIconButton icon={XMarkIcon} color="default" size={5} onClick={cancel} />
|
|
194
|
+
</div>
|
|
195
|
+
)}
|
|
196
|
+
|
|
197
|
+
{!props.hideHeader && (
|
|
198
|
+
<div class="px-4 sm:px-6 pt-4 pb-2 bg-default-50 rounded-t-lg">
|
|
199
|
+
{props.header?.() || (
|
|
200
|
+
<DialogTitle as="h4" class="text-lg font-semibold">
|
|
201
|
+
{props.title}
|
|
202
|
+
</DialogTitle>
|
|
203
|
+
)}
|
|
204
|
+
</div>
|
|
205
|
+
)}
|
|
206
|
+
|
|
207
|
+
{props.content?.({ ok, cancel }) || (
|
|
208
|
+
<div class="px-4 sm:px-6 py-4 rounded-lg">{slots.default?.()}</div>
|
|
209
|
+
)}
|
|
210
|
+
|
|
211
|
+
{!props.hideFooter && (
|
|
212
|
+
<div class="px-4 sm:px-6 pb-4 pt-2 bg-default-50 rounded-b-lg">
|
|
213
|
+
{props.footer?.({ ok, cancel }) || (
|
|
214
|
+
<div class="flex justify-end space-x-2">
|
|
215
|
+
{!props.hideCancel && (
|
|
216
|
+
<NButton color={props.cancelColor} onClick={cancel}>
|
|
217
|
+
{props.cancelText}
|
|
218
|
+
</NButton>
|
|
219
|
+
)}
|
|
220
|
+
{!props.hideOk && (
|
|
221
|
+
<NButton
|
|
222
|
+
color={props.okColor}
|
|
223
|
+
onClick={ok}
|
|
224
|
+
disabled={props.okDisabled}
|
|
225
|
+
>
|
|
226
|
+
{props.okText}
|
|
227
|
+
</NButton>
|
|
228
|
+
)}
|
|
229
|
+
</div>
|
|
230
|
+
)}
|
|
231
|
+
</div>
|
|
232
|
+
)}
|
|
233
|
+
</div>
|
|
234
|
+
)}
|
|
235
|
+
</div>
|
|
236
|
+
</TransitionChild>
|
|
237
|
+
</div>
|
|
238
|
+
</Dialog>
|
|
239
|
+
</TransitionRoot>
|
|
240
|
+
)
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
export { Component as NModal, Component as default }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
.pagination-item {
|
|
2
|
+
@apply h-9 w-9 text-sm inline-flex items-center justify-center border font-medium border-default-300 text-default-500;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.pagination-item.not-small {
|
|
6
|
+
@apply sm:w-11;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.pagination-item.selectable {
|
|
10
|
+
@apply hover:bg-default-50 focus:outline-none focus-visible:border-primary-500 focus-visible:ring-primary-500 focus-visible:ring-1 focus-visible:z-[2];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.pagination-item.selectable.selected {
|
|
14
|
+
@apply border-primary-500 bg-primary-100 hover:bg-primary-100 text-primary-600 z-[1];
|
|
15
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { createComponent } from '../utils/component'
|
|
2
|
+
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/vue/24/solid'
|
|
3
|
+
import { computed, watch, type ComputedRef, type PropType } from 'vue'
|
|
4
|
+
import './NPagination.css'
|
|
5
|
+
|
|
6
|
+
export const nPaginationProps = {
|
|
7
|
+
/**
|
|
8
|
+
* The page number which is currently selected.
|
|
9
|
+
*/
|
|
10
|
+
value: {
|
|
11
|
+
type: Number,
|
|
12
|
+
default: () => 1,
|
|
13
|
+
},
|
|
14
|
+
/**
|
|
15
|
+
* This is called, when a new page number has been selected.
|
|
16
|
+
*/
|
|
17
|
+
onUpdateValue: Function as PropType<(newValue: number) => void>,
|
|
18
|
+
/**
|
|
19
|
+
* The total pages which exists. This is needed to correctly display the selectable pages.
|
|
20
|
+
*/
|
|
21
|
+
total: {
|
|
22
|
+
type: Number,
|
|
23
|
+
default: () => 1,
|
|
24
|
+
},
|
|
25
|
+
/**
|
|
26
|
+
* If set to `true`, the pagination is displayed smaller.
|
|
27
|
+
*/
|
|
28
|
+
small: Boolean,
|
|
29
|
+
/**
|
|
30
|
+
* This is called, when the visible pages, which are selectable in the pagination, have changed.
|
|
31
|
+
* This is useful as only these pages can be navigated to on the next click.
|
|
32
|
+
* This information can be useful for prefetching.
|
|
33
|
+
*/
|
|
34
|
+
onVisiblePagesChanged: Function as PropType<(visiblePages: number[]) => void>,
|
|
35
|
+
} as const
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The `NPagination` is a styled pagination component.
|
|
39
|
+
*/
|
|
40
|
+
const Component = createComponent('NPagination', nPaginationProps, props => {
|
|
41
|
+
const numbers = computed(() => {
|
|
42
|
+
if (props.total <= 7) {
|
|
43
|
+
return range(1, props.total)
|
|
44
|
+
} else if (props.value <= 4) {
|
|
45
|
+
return [...range(1, 5), -1, props.total]
|
|
46
|
+
} else if (props.value >= props.total - 3) {
|
|
47
|
+
return [1, -1, ...range(props.total - 4, props.total)]
|
|
48
|
+
} else {
|
|
49
|
+
return [1, -1, ...range(props.value - 1, props.value + 1), -1, props.total]
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
watch(
|
|
54
|
+
() => numbers.value,
|
|
55
|
+
() => {
|
|
56
|
+
const visiblePages = numbers.value.filter(value => value != -1)
|
|
57
|
+
props.onVisiblePagesChanged?.(visiblePages)
|
|
58
|
+
},
|
|
59
|
+
{ immediate: true }
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
const clickedNumber = (value: number) => {
|
|
63
|
+
if (value <= props.total && value >= 1) props.onUpdateValue?.(value)
|
|
64
|
+
}
|
|
65
|
+
const next = () => clickedNumber(props.value + 1)
|
|
66
|
+
const previous = () => clickedNumber(props.value - 1)
|
|
67
|
+
|
|
68
|
+
const items: ComputedRef<PaginationItem[]> = computed(() =>
|
|
69
|
+
numbers.value.map(number => {
|
|
70
|
+
if (number == -1) return { label: '...', selectable: false, selected: false }
|
|
71
|
+
else
|
|
72
|
+
return {
|
|
73
|
+
label: `${number}`,
|
|
74
|
+
selectable: true,
|
|
75
|
+
selected: number == props.value,
|
|
76
|
+
click: () => clickedNumber(number),
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
const classesForItem = (item: PaginationItem) => [
|
|
82
|
+
'pagination-item',
|
|
83
|
+
item.selectable ? 'selectable ' : '',
|
|
84
|
+
item.selected ? 'selected' : '',
|
|
85
|
+
props.small ? '' : 'not-small',
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
return () => (
|
|
89
|
+
<nav class="inline-flex rounded-md shadow -space-x-px">
|
|
90
|
+
<button
|
|
91
|
+
class={['pagination-item selectable rounded-l-md', props.small ? '' : 'not-small']}
|
|
92
|
+
onClick={previous}
|
|
93
|
+
>
|
|
94
|
+
<ChevronLeftIcon class="h-5 w-5" aria-hidden="true" />
|
|
95
|
+
</button>
|
|
96
|
+
|
|
97
|
+
{items.value.map((item, index) =>
|
|
98
|
+
item.selectable ? (
|
|
99
|
+
<button key={index} class={classesForItem(item)} onClick={item.click}>
|
|
100
|
+
{item.label}
|
|
101
|
+
</button>
|
|
102
|
+
) : (
|
|
103
|
+
<div key={index} class={classesForItem(item)}>
|
|
104
|
+
{item.label}
|
|
105
|
+
</div>
|
|
106
|
+
)
|
|
107
|
+
)}
|
|
108
|
+
|
|
109
|
+
<button class={['pagination-item selectable rounded-r-md', props.small ? '' : 'not-small']} onClick={next}>
|
|
110
|
+
<ChevronRightIcon class="h-5 w-5" aria-hidden="true" />
|
|
111
|
+
</button>
|
|
112
|
+
</nav>
|
|
113
|
+
)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
export { Component as NPagination, Component as default }
|
|
117
|
+
|
|
118
|
+
type PaginationItem = {
|
|
119
|
+
label: string
|
|
120
|
+
selectable: boolean
|
|
121
|
+
selected: boolean
|
|
122
|
+
click?: () => void
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function range(from: number, to: number): number[] {
|
|
126
|
+
const array: number[] = []
|
|
127
|
+
for (let i = from; i <= to; i++) {
|
|
128
|
+
array.push(i)
|
|
129
|
+
}
|
|
130
|
+
return array
|
|
131
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { trsl } from '../i18n'
|
|
2
|
+
import { createComponent } from '../utils/component'
|
|
3
|
+
import { MagnifyingGlassIcon } from '@heroicons/vue/24/solid'
|
|
4
|
+
import { ref, type PropType } from 'vue'
|
|
5
|
+
import { vModelProps } from '../utils/vModel'
|
|
6
|
+
|
|
7
|
+
export const nSearchbarProps = {
|
|
8
|
+
...vModelProps(String),
|
|
9
|
+
/**
|
|
10
|
+
* The placeholder of the search-bar.
|
|
11
|
+
*/
|
|
12
|
+
placeholder: {
|
|
13
|
+
type: String,
|
|
14
|
+
default: trsl('vue-collection.action.search'),
|
|
15
|
+
},
|
|
16
|
+
/**
|
|
17
|
+
* If set to `true` the search-bar is displayed smaller.
|
|
18
|
+
*/
|
|
19
|
+
small: Boolean,
|
|
20
|
+
/**
|
|
21
|
+
* Adds the classes directly to the input (e.g. for shadow).
|
|
22
|
+
*/
|
|
23
|
+
inputClass: String,
|
|
24
|
+
/**
|
|
25
|
+
* This is called when the search-bar receives focus.
|
|
26
|
+
*/
|
|
27
|
+
onFocus: Function as PropType<() => void>,
|
|
28
|
+
/**
|
|
29
|
+
* This is called when the search-bar looses focus.
|
|
30
|
+
*/
|
|
31
|
+
onBlur: Function as PropType<() => void>,
|
|
32
|
+
} as const
|
|
33
|
+
|
|
34
|
+
export type NSearchbarExposed = {
|
|
35
|
+
/**
|
|
36
|
+
* Request focus on the search-bar.
|
|
37
|
+
*/
|
|
38
|
+
focus(): void
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The `NSearchbar` is a styled input with a search icon.
|
|
43
|
+
*/
|
|
44
|
+
const Component = createComponent('NSearchbar', nSearchbarProps, (props, context) => {
|
|
45
|
+
const inputRef = ref<HTMLInputElement>()
|
|
46
|
+
const exposed: NSearchbarExposed = {
|
|
47
|
+
focus: () => {
|
|
48
|
+
inputRef.value?.focus()
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
context.expose(exposed)
|
|
52
|
+
|
|
53
|
+
return () => (
|
|
54
|
+
<div class="relative">
|
|
55
|
+
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
56
|
+
<MagnifyingGlassIcon class="h-5 w-5 text-default-400" aria-hidden="true" />
|
|
57
|
+
</div>
|
|
58
|
+
<input
|
|
59
|
+
ref={inputRef}
|
|
60
|
+
value={props.value}
|
|
61
|
+
onInput={event => props.onUpdateValue?.((event.target as HTMLInputElement).value)}
|
|
62
|
+
type="text"
|
|
63
|
+
name="search"
|
|
64
|
+
placeholder={props.placeholder}
|
|
65
|
+
autocomplete="off"
|
|
66
|
+
class={[
|
|
67
|
+
'block w-full pl-10 pr-4 rounded-md border focus:outline-none focus:ring-1 transition placeholder-default-400 border-default-300 focus:border-primary-500 focus:ring-primary-500',
|
|
68
|
+
props.small ? 'py-1' : 'py-2',
|
|
69
|
+
props.inputClass,
|
|
70
|
+
]}
|
|
71
|
+
onFocus={props.onFocus}
|
|
72
|
+
onBlur={props.onBlur}
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
export { Component as NSearchbar, Component as default }
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createComponentWithSlots } from '../utils/component'
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import { vModelProps } from '../utils/vModel'
|
|
4
|
+
|
|
5
|
+
import NSearchbar, { nSearchbarProps, type NSearchbarExposed } from './NSearchbar'
|
|
6
|
+
import NSuggestionList, { nSuggestionListPropsForConfig } from './NSuggestionList'
|
|
7
|
+
|
|
8
|
+
export const nSearchbarListProps = {
|
|
9
|
+
...nSuggestionListPropsForConfig,
|
|
10
|
+
...vModelProps(String),
|
|
11
|
+
/**
|
|
12
|
+
* @see {@link nSearchbarProps.placeholder}
|
|
13
|
+
*/
|
|
14
|
+
placeholder: nSearchbarProps.placeholder,
|
|
15
|
+
/**
|
|
16
|
+
* Adds the classes directly to the input (e.g. for shadow).
|
|
17
|
+
*/
|
|
18
|
+
inputClass: String,
|
|
19
|
+
} as const
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The `NSearchbarList` is a {@link NSearchbar} with a {@link NSuggestionList}.
|
|
23
|
+
*/
|
|
24
|
+
const Component = createComponentWithSlots('NSearchbarList', nSearchbarListProps, ['listItem'], props => {
|
|
25
|
+
const searchbarRef = ref<NSearchbarExposed>()
|
|
26
|
+
|
|
27
|
+
return () => (
|
|
28
|
+
<NSuggestionList
|
|
29
|
+
{...props}
|
|
30
|
+
inputValue={props.value || ''}
|
|
31
|
+
input={({ onFocus, onBlur }) => (
|
|
32
|
+
<NSearchbar
|
|
33
|
+
ref={searchbarRef}
|
|
34
|
+
value={props.value}
|
|
35
|
+
onUpdateValue={props.onUpdateValue}
|
|
36
|
+
placeholder={props.placeholder}
|
|
37
|
+
inputClass={`shadow-lg ${props.inputClass}`}
|
|
38
|
+
onFocus={onFocus}
|
|
39
|
+
onBlur={onBlur}
|
|
40
|
+
/>
|
|
41
|
+
)}
|
|
42
|
+
onRequestInputFocus={() => searchbarRef.value?.focus()}
|
|
43
|
+
/>
|
|
44
|
+
)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
export { Component as NSearchbarList, Component as default }
|