@ttdk-design-system/library-vue 0.0.0-stage → 1.0.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.
- package/AI_GUIDE.md +6518 -0
- package/llms.txt +6518 -0
- package/package.json +1096 -4
- package/src/core/index.ts +2 -0
- package/src/core/tailwind-preset.cjs +62 -0
- package/src/core/tailwind-preset.ts +85 -0
- package/src/core/theme/tokens.css +663 -0
- package/src/core/types.ts +836 -0
- package/src/index.ts +17 -0
- package/src/tailwind-preset.cjs +62 -0
- package/src/tailwind-preset.ts +85 -0
- package/src/types.ts +1 -0
- package/src/web/accordion/AI_GUIDE.md +149 -0
- package/src/web/accordion/accordion.css +1 -0
- package/src/web/accordion/accordion.vue +432 -0
- package/src/web/accordion/accordion.vue.d.ts +227 -0
- package/src/web/accordion/index.ts +3 -0
- package/src/web/accordion/manifest.json +351 -0
- package/src/web/alert/AI_GUIDE.md +139 -0
- package/src/web/alert/alert.css +1 -0
- package/src/web/alert/alert.vue +290 -0
- package/src/web/alert/alert.vue.d.ts +154 -0
- package/src/web/alert/index.ts +3 -0
- package/src/web/alert/manifest.json +240 -0
- package/src/web/avatar/AI_GUIDE.md +118 -0
- package/src/web/avatar/avatar.css +1 -0
- package/src/web/avatar/avatar.vue +217 -0
- package/src/web/avatar/avatar.vue.d.ts +135 -0
- package/src/web/avatar/index.ts +3 -0
- package/src/web/avatar/manifest.json +195 -0
- package/src/web/badge/AI_GUIDE.md +94 -0
- package/src/web/badge/badge.css +1 -0
- package/src/web/badge/badge.vue +135 -0
- package/src/web/badge/badge.vue.d.ts +108 -0
- package/src/web/badge/index.ts +3 -0
- package/src/web/badge/manifest.json +141 -0
- package/src/web/banner/AI_GUIDE.md +130 -0
- package/src/web/banner/banner.css +1 -0
- package/src/web/banner/banner.vue +303 -0
- package/src/web/banner/banner.vue.d.ts +162 -0
- package/src/web/banner/index.ts +3 -0
- package/src/web/banner/manifest.json +240 -0
- package/src/web/breadcrumb/AI_GUIDE.md +120 -0
- package/src/web/breadcrumb/breadcrumb.css +1 -0
- package/src/web/breadcrumb/breadcrumb.vue +313 -0
- package/src/web/breadcrumb/breadcrumb.vue.d.ts +139 -0
- package/src/web/breadcrumb/index.ts +3 -0
- package/src/web/breadcrumb/manifest.json +225 -0
- package/src/web/button/AI_GUIDE.md +121 -0
- package/src/web/button/button.css +1 -0
- package/src/web/button/button.vue +283 -0
- package/src/web/button/button.vue.d.ts +147 -0
- package/src/web/button/index.ts +3 -0
- package/src/web/button/manifest.json +177 -0
- package/src/web/calendar/AI_GUIDE.md +151 -0
- package/src/web/calendar/calendar.css +1 -0
- package/src/web/calendar/calendar.vue +1536 -0
- package/src/web/calendar/calendar.vue.d.ts +261 -0
- package/src/web/calendar/index.ts +3 -0
- package/src/web/calendar/manifest.json +470 -0
- package/src/web/card/AI_GUIDE.md +133 -0
- package/src/web/card/card.css +1 -0
- package/src/web/card/card.vue +423 -0
- package/src/web/card/card.vue.d.ts +232 -0
- package/src/web/card/index.ts +3 -0
- package/src/web/card/manifest.json +357 -0
- package/src/web/checkbox/AI_GUIDE.md +146 -0
- package/src/web/checkbox/checkbox.css +1 -0
- package/src/web/checkbox/checkbox.vue +304 -0
- package/src/web/checkbox/checkbox.vue.d.ts +189 -0
- package/src/web/checkbox/index.ts +3 -0
- package/src/web/checkbox/manifest.json +294 -0
- package/src/web/chip/AI_GUIDE.md +186 -0
- package/src/web/chip/chip.css +1 -0
- package/src/web/chip/chip.vue +220 -0
- package/src/web/chip/chip.vue.d.ts +140 -0
- package/src/web/chip/index.ts +3 -0
- package/src/web/chip/manifest.json +186 -0
- package/src/web/dropdown/AI_GUIDE.md +167 -0
- package/src/web/dropdown/dropdown.css +1 -0
- package/src/web/dropdown/dropdown.vue +1257 -0
- package/src/web/dropdown/dropdown.vue.d.ts +381 -0
- package/src/web/dropdown/index.ts +3 -0
- package/src/web/dropdown/manifest.json +576 -0
- package/src/web/flex/AI_GUIDE.md +113 -0
- package/src/web/flex/flex-item.vue +94 -0
- package/src/web/flex/flex-item.vue.d.ts +84 -0
- package/src/web/flex/flex.css +1 -0
- package/src/web/flex/flex.vue +103 -0
- package/src/web/flex/flex.vue.d.ts +92 -0
- package/src/web/flex/index.ts +5 -0
- package/src/web/flex/manifest.json +162 -0
- package/src/web/form/index.ts +7 -0
- package/src/web/grid/AI_GUIDE.md +112 -0
- package/src/web/grid/grid-item.vue +109 -0
- package/src/web/grid/grid-item.vue.d.ts +99 -0
- package/src/web/grid/grid.css +1 -0
- package/src/web/grid/grid.vue +94 -0
- package/src/web/grid/grid.vue.d.ts +84 -0
- package/src/web/grid/index.ts +5 -0
- package/src/web/grid/manifest.json +153 -0
- package/src/web/index.ts +86 -0
- package/src/web/input/AI_GUIDE.md +159 -0
- package/src/web/input/index.ts +3 -0
- package/src/web/input/input.css +1 -0
- package/src/web/input/input.vue +521 -0
- package/src/web/input/input.vue.d.ts +274 -0
- package/src/web/input/manifest.json +465 -0
- package/src/web/input-number/AI_GUIDE.md +164 -0
- package/src/web/input-number/index.ts +3 -0
- package/src/web/input-number/input-number.css +1 -0
- package/src/web/input-number/input-number.vue +1153 -0
- package/src/web/input-number/input-number.vue.d.ts +343 -0
- package/src/web/input-number/manifest.json +575 -0
- package/src/web/modal/AI_GUIDE.md +171 -0
- package/src/web/modal/index.ts +3 -0
- package/src/web/modal/manifest.json +375 -0
- package/src/web/modal/modal.css +1 -0
- package/src/web/modal/modal.vue +344 -0
- package/src/web/modal/modal.vue.d.ts +64 -0
- package/src/web/pagination/AI_GUIDE.md +148 -0
- package/src/web/pagination/index.ts +21 -0
- package/src/web/pagination/manifest.json +432 -0
- package/src/web/pagination/pagination-content.vue +42 -0
- package/src/web/pagination/pagination-content.vue.d.ts +28 -0
- package/src/web/pagination/pagination-ellipsis.vue +55 -0
- package/src/web/pagination/pagination-ellipsis.vue.d.ts +28 -0
- package/src/web/pagination/pagination-first.vue +161 -0
- package/src/web/pagination/pagination-first.vue.d.ts +67 -0
- package/src/web/pagination/pagination-item.vue +42 -0
- package/src/web/pagination/pagination-item.vue.d.ts +28 -0
- package/src/web/pagination/pagination-last.vue +161 -0
- package/src/web/pagination/pagination-last.vue.d.ts +67 -0
- package/src/web/pagination/pagination-link.vue +118 -0
- package/src/web/pagination/pagination-link.vue.d.ts +61 -0
- package/src/web/pagination/pagination-next.vue +160 -0
- package/src/web/pagination/pagination-next.vue.d.ts +68 -0
- package/src/web/pagination/pagination-previous.vue +160 -0
- package/src/web/pagination/pagination-previous.vue.d.ts +68 -0
- package/src/web/pagination/pagination.css +1 -0
- package/src/web/pagination/pagination.vue +730 -0
- package/src/web/pagination/pagination.vue.d.ts +247 -0
- package/src/web/popover/AI_GUIDE.md +163 -0
- package/src/web/popover/index.ts +3 -0
- package/src/web/popover/manifest.json +396 -0
- package/src/web/popover/popover.css +1 -0
- package/src/web/popover/popover.vue +579 -0
- package/src/web/popover/popover.vue.d.ts +83 -0
- package/src/web/radio/AI_GUIDE.md +155 -0
- package/src/web/radio/index.ts +3 -0
- package/src/web/radio/manifest.json +294 -0
- package/src/web/radio/radio.css +1 -0
- package/src/web/radio/radio.vue +288 -0
- package/src/web/radio/radio.vue.d.ts +182 -0
- package/src/web/selectbox/AI_GUIDE.md +216 -0
- package/src/web/selectbox/index.ts +3 -0
- package/src/web/selectbox/manifest.json +477 -0
- package/src/web/selectbox/selectbox.css +1 -0
- package/src/web/selectbox/selectbox.vue +1024 -0
- package/src/web/selectbox/selectbox.vue.d.ts +300 -0
- package/src/web/sonner/AI_GUIDE.md +98 -0
- package/src/web/sonner/index.ts +114 -0
- package/src/web/sonner/manifest.json +132 -0
- package/src/web/sonner/sonner.css +1 -0
- package/src/web/sonner/sonner.vue +180 -0
- package/src/web/sonner/sonner.vue.d.ts +113 -0
- package/src/web/table/AI_GUIDE.md +145 -0
- package/src/web/table/index.ts +34 -0
- package/src/web/table/manifest.json +168 -0
- package/src/web/table/table-body.vue +42 -0
- package/src/web/table/table-body.vue.d.ts +28 -0
- package/src/web/table/table-caption.vue +42 -0
- package/src/web/table/table-caption.vue.d.ts +28 -0
- package/src/web/table/table-cell.vue +71 -0
- package/src/web/table/table-cell.vue.d.ts +49 -0
- package/src/web/table/table-empty.vue +105 -0
- package/src/web/table/table-empty.vue.d.ts +57 -0
- package/src/web/table/table-footer.vue +42 -0
- package/src/web/table/table-footer.vue.d.ts +28 -0
- package/src/web/table/table-head.vue +78 -0
- package/src/web/table/table-head.vue.d.ts +50 -0
- package/src/web/table/table-header.vue +42 -0
- package/src/web/table/table-header.vue.d.ts +28 -0
- package/src/web/table/table-row.vue +70 -0
- package/src/web/table/table-row.vue.d.ts +44 -0
- package/src/web/table/table.css +1 -0
- package/src/web/table/table.vue +298 -0
- package/src/web/table/table.vue.d.ts +119 -0
- package/src/web/tabs/AI_GUIDE.md +150 -0
- package/src/web/tabs/index.ts +2 -0
- package/src/web/tabs/manifest.json +315 -0
- package/src/web/tabs/tabs.css +1 -0
- package/src/web/tabs/tabs.vue +164 -0
- package/src/web/tabs/tabs.vue.d.ts +60 -0
- package/src/web/text/AI_GUIDE.md +129 -0
- package/src/web/text/index.ts +2 -0
- package/src/web/text/manifest.json +195 -0
- package/src/web/text/text.css +1 -0
- package/src/web/text/text.vue +195 -0
- package/src/web/text/text.vue.d.ts +161 -0
- package/src/web/textarea/AI_GUIDE.md +141 -0
- package/src/web/textarea/index.ts +3 -0
- package/src/web/textarea/manifest.json +438 -0
- package/src/web/textarea/textarea.css +1 -0
- package/src/web/textarea/textarea.vue +458 -0
- package/src/web/textarea/textarea.vue.d.ts +265 -0
- package/src/web/tooltip/AI_GUIDE.md +110 -0
- package/src/web/tooltip/index.ts +3 -0
- package/src/web/tooltip/manifest.json +222 -0
- package/src/web/tooltip/tooltip.css +1 -0
- package/src/web/tooltip/tooltip.vue +417 -0
- package/src/web/tooltip/tooltip.vue.d.ts +150 -0
- package/src/webview/bottomsheet/AI_GUIDE.md +141 -0
- package/src/webview/bottomsheet/bottomsheet.css +1 -0
- package/src/webview/bottomsheet/bottomsheet.vue +441 -0
- package/src/webview/bottomsheet/bottomsheet.vue.d.ts +178 -0
- package/src/webview/bottomsheet/index.ts +4 -0
- package/src/webview/bottomsheet/manifest.json +377 -0
- package/src/webview/button/AI_GUIDE.md +79 -0
- package/src/webview/button/button-ios.css +1 -0
- package/src/webview/button/button-ios.vue +138 -0
- package/src/webview/button/button-ios.vue.d.ts +102 -0
- package/src/webview/button/button-material.css +1 -0
- package/src/webview/button/button-material.vue +138 -0
- package/src/webview/button/button-material.vue.d.ts +102 -0
- package/src/webview/button/index.ts +7 -0
- package/src/webview/button/manifest.json +148 -0
- package/src/webview/index.ts +7 -0
- package/tailwind-preset.cjs +62 -0
- package/tailwind-preset.js +62 -0
- package/ttdk-manifest.json +10188 -0
- package/README.md +0 -3
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Teleport to="body">
|
|
3
|
+
<div
|
|
4
|
+
v-if="isOpen"
|
|
5
|
+
ref="rootRef"
|
|
6
|
+
:class="[`ttdk-modal-portal ${className || ''}`.trim(), $attrs.class]"
|
|
7
|
+
role="dialog"
|
|
8
|
+
:aria-modal="modal !== false ? 'true' : 'false'"
|
|
9
|
+
:tabIndex="-1"
|
|
10
|
+
:style="{
|
|
11
|
+
...buildUiVars(ui),
|
|
12
|
+
...(typeof style === 'object' ? style : {}),
|
|
13
|
+
}"
|
|
14
|
+
>
|
|
15
|
+
<!-- 1. Backdrop Overlay -->
|
|
16
|
+
<div
|
|
17
|
+
v-if="overlay !== false"
|
|
18
|
+
:class="[`ttdk-modal-overlay ${overlayClass || ''}`.trim()]"
|
|
19
|
+
:style="{
|
|
20
|
+
...buildUiVars(overlayStyle),
|
|
21
|
+
}"
|
|
22
|
+
@click="handleOverlayClick"
|
|
23
|
+
/>
|
|
24
|
+
|
|
25
|
+
<!-- 2. Modal Dialog Card -->
|
|
26
|
+
<div
|
|
27
|
+
ref="dialogRef"
|
|
28
|
+
:class="[
|
|
29
|
+
'ttdk-modal-dialog',
|
|
30
|
+
isAnimDone ? 'ttdk-modal-anim-done' : '',
|
|
31
|
+
isShaking ? 'ttdk-modal-shake' : '',
|
|
32
|
+
contentClass || ''
|
|
33
|
+
].filter(Boolean).join(' ').trim()"
|
|
34
|
+
:data-size="size || 'md'"
|
|
35
|
+
:data-fullscreen="fullscreen ? 'true' : undefined"
|
|
36
|
+
:data-scrollable="scrollable !== false ? 'true' : undefined"
|
|
37
|
+
:style="{
|
|
38
|
+
...buildUiVars(contentStyle),
|
|
39
|
+
}"
|
|
40
|
+
>
|
|
41
|
+
<!-- Slot: Full content override -->
|
|
42
|
+
<slot name="content" :close="() => closeModal(true)">
|
|
43
|
+
<!-- Modal Header -->
|
|
44
|
+
<div
|
|
45
|
+
v-if="hasHeader"
|
|
46
|
+
:class="[`ttdk-modal-header ${headerClass || ''}`.trim()]"
|
|
47
|
+
:style="{
|
|
48
|
+
...buildUiVars(headerStyle),
|
|
49
|
+
}"
|
|
50
|
+
>
|
|
51
|
+
<slot name="header" :close="() => closeModal(true)">
|
|
52
|
+
<div class="ttdk-modal-header-info">
|
|
53
|
+
<h3 v-if="title" class="ttdk-modal-title">{{ title }}</h3>
|
|
54
|
+
<p v-if="description" class="ttdk-modal-description">{{ description }}</p>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<slot name="close" :close="() => closeModal(true)">
|
|
58
|
+
<button
|
|
59
|
+
v-if="close !== false"
|
|
60
|
+
type="button"
|
|
61
|
+
class="ttdk-modal-close"
|
|
62
|
+
aria-label="Tutup modal"
|
|
63
|
+
@click="closeModal(true)"
|
|
64
|
+
>
|
|
65
|
+
<component :is="closeIcon" v-if="closeIcon" />
|
|
66
|
+
<svg
|
|
67
|
+
v-else
|
|
68
|
+
width="16"
|
|
69
|
+
height="16"
|
|
70
|
+
viewBox="0 0 24 24"
|
|
71
|
+
fill="none"
|
|
72
|
+
stroke="currentColor"
|
|
73
|
+
>
|
|
74
|
+
<line x1="18" y1="6" x2="6" y2="18" />
|
|
75
|
+
<line x1="6" y1="6" x2="18" y2="18" />
|
|
76
|
+
</svg>
|
|
77
|
+
</button>
|
|
78
|
+
</slot>
|
|
79
|
+
</slot>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<!-- Modal Body -->
|
|
83
|
+
<div
|
|
84
|
+
:class="[`ttdk-modal-body ${bodyClass || ''}`.trim()]"
|
|
85
|
+
:style="{
|
|
86
|
+
...buildUiVars(bodyStyle),
|
|
87
|
+
}"
|
|
88
|
+
>
|
|
89
|
+
<slot name="body" :close="() => closeModal(true)">
|
|
90
|
+
<slot :close="() => closeModal(true)" />
|
|
91
|
+
</slot>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<!-- Modal Footer -->
|
|
95
|
+
<div
|
|
96
|
+
v-if="hasFooter"
|
|
97
|
+
:class="[`ttdk-modal-footer ${footerClass || ''}`.trim()]"
|
|
98
|
+
:style="{
|
|
99
|
+
...buildUiVars(footerStyle),
|
|
100
|
+
}"
|
|
101
|
+
>
|
|
102
|
+
<slot name="footer" :close="() => closeModal(true)" />
|
|
103
|
+
</div>
|
|
104
|
+
</slot>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</Teleport>
|
|
108
|
+
</template>
|
|
109
|
+
|
|
110
|
+
<script setup lang="ts">
|
|
111
|
+
import { ref, watch, onMounted, onUnmounted, useSlots, computed } from "vue";
|
|
112
|
+
import { buildUiVars, type UiProps } from "../../core/types";
|
|
113
|
+
|
|
114
|
+
export type ModalSize =
|
|
115
|
+
| 'xs'
|
|
116
|
+
| 'sm'
|
|
117
|
+
| 'md'
|
|
118
|
+
| 'lg'
|
|
119
|
+
| 'xl'
|
|
120
|
+
| '2xl'
|
|
121
|
+
| '3xl'
|
|
122
|
+
| '4xl'
|
|
123
|
+
| '5xl'
|
|
124
|
+
| 'full';
|
|
125
|
+
|
|
126
|
+
export type ModalUiProps = UiProps;
|
|
127
|
+
|
|
128
|
+
export interface ModalProps {
|
|
129
|
+
open?: boolean;
|
|
130
|
+
defaultOpen?: boolean;
|
|
131
|
+
title?: string;
|
|
132
|
+
description?: string;
|
|
133
|
+
close?: boolean | any;
|
|
134
|
+
closeIcon?: any;
|
|
135
|
+
fullscreen?: boolean;
|
|
136
|
+
size?: ModalSize;
|
|
137
|
+
dismissible?: boolean;
|
|
138
|
+
preventClose?: boolean;
|
|
139
|
+
overlay?: boolean;
|
|
140
|
+
modal?: boolean;
|
|
141
|
+
scrollable?: boolean;
|
|
142
|
+
preventScroll?: boolean;
|
|
143
|
+
lockScroll?: boolean;
|
|
144
|
+
ui?: ModalUiProps;
|
|
145
|
+
className?: string;
|
|
146
|
+
class?: string;
|
|
147
|
+
style?: any;
|
|
148
|
+
overlayClass?: string;
|
|
149
|
+
overlayStyle?: UiProps;
|
|
150
|
+
contentClass?: string;
|
|
151
|
+
contentStyle?: UiProps;
|
|
152
|
+
headerClass?: string;
|
|
153
|
+
headerStyle?: UiProps;
|
|
154
|
+
bodyClass?: string;
|
|
155
|
+
bodyStyle?: UiProps;
|
|
156
|
+
footerClass?: string;
|
|
157
|
+
footerStyle?: UiProps;
|
|
158
|
+
onUpdateOpen?: (open: boolean) => void;
|
|
159
|
+
onOpenChange?: (open: boolean) => void;
|
|
160
|
+
onOpen?: () => void;
|
|
161
|
+
onClose?: () => void;
|
|
162
|
+
onClosePrevent?: () => void;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const props = withDefaults(defineProps<ModalProps>(), {
|
|
166
|
+
defaultOpen: false,
|
|
167
|
+
close: true,
|
|
168
|
+
fullscreen: false,
|
|
169
|
+
size: "md",
|
|
170
|
+
dismissible: true,
|
|
171
|
+
preventClose: false,
|
|
172
|
+
overlay: true,
|
|
173
|
+
modal: true,
|
|
174
|
+
scrollable: true,
|
|
175
|
+
preventScroll: true,
|
|
176
|
+
lockScroll: true,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const emit = defineEmits<{
|
|
180
|
+
(e: "update:open", value: boolean): void;
|
|
181
|
+
(e: "update:modelValue", value: boolean): void;
|
|
182
|
+
(e: "open"): void;
|
|
183
|
+
(e: "close"): void;
|
|
184
|
+
(e: "close:prevent"): void;
|
|
185
|
+
}>();
|
|
186
|
+
|
|
187
|
+
const slots = useSlots();
|
|
188
|
+
const rootRef = ref<HTMLElement | null>(null);
|
|
189
|
+
const dialogRef = ref<HTMLElement | null>(null);
|
|
190
|
+
|
|
191
|
+
const isOpen = ref(props.open !== undefined ? props.open : props.defaultOpen);
|
|
192
|
+
const isShaking = ref(false);
|
|
193
|
+
const isAnimDone = ref(false);
|
|
194
|
+
let shakeTimer: any = null;
|
|
195
|
+
let animTimer: any = null;
|
|
196
|
+
|
|
197
|
+
const hasHeader = computed(() => {
|
|
198
|
+
return Boolean(slots.header || props.title || props.description || props.close !== false);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const hasFooter = computed(() => {
|
|
202
|
+
return Boolean(slots.footer);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
let lockCount = 0;
|
|
206
|
+
let originalOverflow = "";
|
|
207
|
+
let originalPaddingRight = "";
|
|
208
|
+
|
|
209
|
+
function lockBodyScroll() {
|
|
210
|
+
if (typeof document === "undefined") return;
|
|
211
|
+
if (lockCount === 0) {
|
|
212
|
+
originalOverflow = document.body.style.overflow;
|
|
213
|
+
originalPaddingRight = document.body.style.paddingRight;
|
|
214
|
+
const scrollBarWidth = window.innerWidth - document.documentElement.clientWidth;
|
|
215
|
+
document.body.style.overflow = "hidden";
|
|
216
|
+
if (scrollBarWidth > 0) {
|
|
217
|
+
document.body.style.paddingRight = scrollBarWidth + "px";
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
lockCount++;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function unlockBodyScroll() {
|
|
224
|
+
if (typeof document === "undefined") return;
|
|
225
|
+
lockCount = Math.max(0, lockCount - 1);
|
|
226
|
+
if (lockCount === 0) {
|
|
227
|
+
document.body.style.overflow = originalOverflow;
|
|
228
|
+
document.body.style.paddingRight = originalPaddingRight;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
let isScrollLocked = false;
|
|
233
|
+
function applyScrollLock(shouldLock: boolean) {
|
|
234
|
+
if (shouldLock && !isScrollLocked) {
|
|
235
|
+
lockBodyScroll();
|
|
236
|
+
isScrollLocked = true;
|
|
237
|
+
} else if (!shouldLock && isScrollLocked) {
|
|
238
|
+
unlockBodyScroll();
|
|
239
|
+
isScrollLocked = false;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function isDismissible() {
|
|
244
|
+
if (props.preventClose === true) return false;
|
|
245
|
+
return props.dismissible !== false;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function triggerShake() {
|
|
249
|
+
if (shakeTimer) clearTimeout(shakeTimer);
|
|
250
|
+
isAnimDone.value = true;
|
|
251
|
+
isShaking.value = true;
|
|
252
|
+
shakeTimer = setTimeout(() => {
|
|
253
|
+
isShaking.value = false;
|
|
254
|
+
shakeTimer = null;
|
|
255
|
+
}, 400);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function openModal() {
|
|
259
|
+
if (!isOpen.value) {
|
|
260
|
+
isOpen.value = true;
|
|
261
|
+
emit("update:open", true);
|
|
262
|
+
emit("update:modelValue", true);
|
|
263
|
+
emit("open");
|
|
264
|
+
if (props.onOpen) props.onOpen();
|
|
265
|
+
if (props.onUpdateOpen) props.onUpdateOpen(true);
|
|
266
|
+
if (props.onOpenChange) props.onOpenChange(true);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function closeModal(force?: boolean) {
|
|
271
|
+
if (!isDismissible() && !force) {
|
|
272
|
+
triggerShake();
|
|
273
|
+
emit("close:prevent");
|
|
274
|
+
if (props.onClosePrevent) props.onClosePrevent();
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (isOpen.value) {
|
|
278
|
+
isOpen.value = false;
|
|
279
|
+
emit("update:open", false);
|
|
280
|
+
emit("update:modelValue", false);
|
|
281
|
+
emit("close");
|
|
282
|
+
if (props.onClose) props.onClose();
|
|
283
|
+
if (props.onUpdateOpen) props.onUpdateOpen(false);
|
|
284
|
+
if (props.onOpenChange) props.onOpenChange(false);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function handleOverlayClick() {
|
|
289
|
+
closeModal();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
293
|
+
if (!isOpen.value) return;
|
|
294
|
+
if (event.key === "Escape") {
|
|
295
|
+
closeModal();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
watch(
|
|
300
|
+
() => props.open,
|
|
301
|
+
(val) => {
|
|
302
|
+
if (val !== undefined && val !== isOpen.value) {
|
|
303
|
+
if (val) {
|
|
304
|
+
openModal();
|
|
305
|
+
} else {
|
|
306
|
+
closeModal(true);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
watch(
|
|
313
|
+
isOpen,
|
|
314
|
+
(val) => {
|
|
315
|
+
const shouldLock = Boolean(props.preventScroll !== false && props.lockScroll !== false);
|
|
316
|
+
if (val) {
|
|
317
|
+
isAnimDone.value = false;
|
|
318
|
+
if (animTimer) clearTimeout(animTimer);
|
|
319
|
+
animTimer = setTimeout(() => {
|
|
320
|
+
isAnimDone.value = true;
|
|
321
|
+
}, 250);
|
|
322
|
+
if (shouldLock) applyScrollLock(true);
|
|
323
|
+
if (typeof document !== "undefined") {
|
|
324
|
+
document.addEventListener("keydown", handleKeyDown, true);
|
|
325
|
+
}
|
|
326
|
+
} else {
|
|
327
|
+
isAnimDone.value = false;
|
|
328
|
+
applyScrollLock(false);
|
|
329
|
+
if (typeof document !== "undefined") {
|
|
330
|
+
document.removeEventListener("keydown", handleKeyDown, true);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
{ immediate: true }
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
onUnmounted(() => {
|
|
338
|
+
if (shakeTimer) clearTimeout(shakeTimer);
|
|
339
|
+
applyScrollLock(false);
|
|
340
|
+
if (typeof document !== "undefined") {
|
|
341
|
+
document.removeEventListener("keydown", handleKeyDown, true);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
</script>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// Auto-generated by TTDK Design System Vue Type Generator
|
|
3
|
+
import type { DefineComponent } from "vue";
|
|
4
|
+
import { buildUiVars, type UiProps } from "../../core/types";
|
|
5
|
+
|
|
6
|
+
export type ModalSize =
|
|
7
|
+
| 'xs'
|
|
8
|
+
| 'sm'
|
|
9
|
+
| 'md'
|
|
10
|
+
| 'lg'
|
|
11
|
+
| 'xl'
|
|
12
|
+
| '2xl'
|
|
13
|
+
| '3xl'
|
|
14
|
+
| '4xl'
|
|
15
|
+
| '5xl'
|
|
16
|
+
| 'full';
|
|
17
|
+
|
|
18
|
+
export type ModalUiProps = UiProps;
|
|
19
|
+
|
|
20
|
+
export interface ModalProps {
|
|
21
|
+
open?: boolean;
|
|
22
|
+
defaultOpen?: boolean;
|
|
23
|
+
title?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
close?: boolean | any;
|
|
26
|
+
closeIcon?: any;
|
|
27
|
+
fullscreen?: boolean;
|
|
28
|
+
size?: ModalSize;
|
|
29
|
+
dismissible?: boolean;
|
|
30
|
+
preventClose?: boolean;
|
|
31
|
+
overlay?: boolean;
|
|
32
|
+
modal?: boolean;
|
|
33
|
+
scrollable?: boolean;
|
|
34
|
+
preventScroll?: boolean;
|
|
35
|
+
lockScroll?: boolean;
|
|
36
|
+
ui?: ModalUiProps;
|
|
37
|
+
className?: string;
|
|
38
|
+
class?: string;
|
|
39
|
+
style?: any;
|
|
40
|
+
overlayClass?: string;
|
|
41
|
+
overlayStyle?: UiProps;
|
|
42
|
+
contentClass?: string;
|
|
43
|
+
contentStyle?: UiProps;
|
|
44
|
+
headerClass?: string;
|
|
45
|
+
headerStyle?: UiProps;
|
|
46
|
+
bodyClass?: string;
|
|
47
|
+
bodyStyle?: UiProps;
|
|
48
|
+
footerClass?: string;
|
|
49
|
+
footerStyle?: UiProps;
|
|
50
|
+
onUpdateOpen?: (open: boolean) => void;
|
|
51
|
+
onOpenChange?: (open: boolean) => void;
|
|
52
|
+
onOpen?: () => void;
|
|
53
|
+
onClose?: () => void;
|
|
54
|
+
onClosePrevent?: () => void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
declare const component: DefineComponent<ModalProps, {}, {}, {}, {}, {}, {}, {
|
|
58
|
+
(e: "update:open", value: boolean): void;
|
|
59
|
+
(e: "update:modelValue", value: boolean): void;
|
|
60
|
+
(e: "open"): void;
|
|
61
|
+
(e: "close"): void;
|
|
62
|
+
(e: "close:prevent"): void;
|
|
63
|
+
}>;
|
|
64
|
+
export default component;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# <Pagination /> — TTDK Component Reference
|
|
2
|
+
|
|
3
|
+
Komponen Pagination navigasi halaman dengan dukungan Compound API (ala Shadcn UI) dan Props-driven mode.
|
|
4
|
+
|
|
5
|
+
## 1. Stylesheets & Theming (Pure CSS)
|
|
6
|
+
Import the corresponding stylesheet for the theme you wish to use:
|
|
7
|
+
- **COMMON Theme**: `import "@ttdk-design-system/library-<react|vue|svelte>/web/pagination/pagination.css";`
|
|
8
|
+
|
|
9
|
+
## 2. Subpath Component Import
|
|
10
|
+
```ts
|
|
11
|
+
// React
|
|
12
|
+
import Pagination from "@ttdk-design-system/library-react/web/pagination";
|
|
13
|
+
|
|
14
|
+
// Vue 3
|
|
15
|
+
import Pagination from "@ttdk-design-system/library-vue/web/pagination";
|
|
16
|
+
|
|
17
|
+
// Svelte
|
|
18
|
+
import Pagination from "@ttdk-design-system/library-svelte/web/pagination";
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 3. Props & API Reference
|
|
22
|
+
|
|
23
|
+
| Prop | Type | Default | Description |
|
|
24
|
+
| :--- | :--- | :--- | :--- |
|
|
25
|
+
| `page?` | `number` | `1` | Nomor halaman saat ini (1-indexed). |
|
|
26
|
+
| `maxPages?` | `number` | `1` | Jumlah total halaman maksimum (alias untuk totalPages). |
|
|
27
|
+
| `totalPages?` | `number` | `1` | Jumlah total halaman (alias maxPages). |
|
|
28
|
+
| `limit?` | `number` | `10` | Jumlah baris/item data per halaman (page size). |
|
|
29
|
+
| `onLimitChange?` | `(limit: number) => void` | `-` | Callback saat jumlah baris per halaman diubah via select box. |
|
|
30
|
+
| `limitOptions?` | `number[]` | `[10, 20, 50, 100]` | Pilihan angka baris per halaman pada select box. |
|
|
31
|
+
| `canPrevPage?` | `boolean` | `false` | Menentukan apakah navigasi ke halaman sebelumnya diizinkan (false = tombol Previous dinonaktifkan). |
|
|
32
|
+
| `canNextPage?` | `boolean` | `false` | Menentukan apakah navigasi ke halaman berikutnya diizinkan (false = tombol Next dinonaktifkan). |
|
|
33
|
+
| `onFirstPage?` | `() => void` | `-` | Callback saat tombol First Page (halaman pertama) diklik. |
|
|
34
|
+
| `onPrevPage?` | `() => void` | `-` | Callback saat tombol Previous Page (halaman sebelumnya) diklik. |
|
|
35
|
+
| `onNextPage?` | `() => void` | `-` | Callback saat tombol Next Page (halaman selanjutnya) diklik. |
|
|
36
|
+
| `onLastPage?` | `() => void` | `-` | Callback saat tombol Last Page (halaman terakhir) diklik. |
|
|
37
|
+
| `onPageChange?` | `(page: number) => void` | `-` | Callback saat nomor halaman tertentu diklik. |
|
|
38
|
+
| `siblingCount?` | `number` | `1` | Jumlah tetangga nomor halaman di samping kiri dan kanan halaman aktif. |
|
|
39
|
+
| `showEdges?` | `boolean` | `false` | Menampilkan tombol First (<<) dan Last (>>). |
|
|
40
|
+
| `showPageNumbers?` | `boolean` | `true` | Menampilkan nomor halaman terpaginasi (1, 2, 3...). |
|
|
41
|
+
| `size?` | `'sm' | 'md' | 'lg'` | `md` | Skala ukuran pagination: - `'sm'`: 28px - `'md'`: 36px (standar) - `'lg'`: 44px |
|
|
42
|
+
| `style?` | `any` | `-` | Inline CSS style object native. |
|
|
43
|
+
| `ui?` | `UiProps` | `-` | Kustomisasi visual granular root kontainer pagination (UiProps). |
|
|
44
|
+
| `contentStyle?` | `UiProps` | `-` | Style kustom untuk kontainer daftar pagination content (ul.ttdk-pagination-content). |
|
|
45
|
+
| `contentClass?` | `string` | `-` | Class CSS kustom untuk kontainer daftar pagination content. |
|
|
46
|
+
| `linkStyle?` | `UiProps` | `-` | Style kustom untuk tombol link nomor halaman / navigasi. |
|
|
47
|
+
| `linkClass?` | `string` | `-` | Class CSS kustom untuk tombol link nomor halaman / navigasi. |
|
|
48
|
+
| `activeLinkStyle?` | `UiProps` | `-` | Style kustom untuk tombol nomor halaman saat aktif. |
|
|
49
|
+
| `activeLinkClass?` | `string` | `-` | Class CSS kustom untuk tombol nomor halaman saat aktif. |
|
|
50
|
+
| `limitStyle?` | `UiProps` | `-` | Style kustom untuk kontainer baris per halaman (limit). |
|
|
51
|
+
| `limitClass?` | `string` | `-` | Class CSS kustom untuk kontainer baris per halaman (limit). |
|
|
52
|
+
| `limitSelectStyle?` | `UiProps` | `-` | Style kustom untuk elemen Selectbox baris per halaman (limit). |
|
|
53
|
+
| `limitSlot?` | `any` | `-` | Slot kustom untuk menggantikan elemen limit selector bawaan. |
|
|
54
|
+
| `showLimit?` | `boolean` | `-` | Menentukan apakah kontrol baris per halaman (limit selectbox) ditampilkan. Jika tidak disetel, otomatis mengikuti keberadaan callback onLimitChange. |
|
|
55
|
+
| `showLimitLabel?` | `boolean` | `true` | Menentukan apakah teks label 'Baris per halaman:' ditampilkan di samping selectbox limit. |
|
|
56
|
+
| `limitLabel?` | `string` | `Baris per halaman:` | Teks label untuk opsi baris per halaman. |
|
|
57
|
+
| `firstIcon?` | `any` | `-` | Ikon kustom untuk tombol halaman pertama (first page, menggantikan <<). |
|
|
58
|
+
| `prevIcon?` | `any` | `-` | Ikon kustom untuk tombol halaman sebelumnya (previous page, menggantikan <). |
|
|
59
|
+
| `nextIcon?` | `any` | `-` | Ikon kustom untuk tombol halaman selanjutnya (next page, menggantikan >). |
|
|
60
|
+
| `lastIcon?` | `any` | `-` | Ikon kustom untuk tombol halaman terakhir (last page, menggantikan >>). |
|
|
61
|
+
| `ellipsisIcon?` | `any` | `-` | Ikon kustom untuk separator elipsis (menggantikan ...). |
|
|
62
|
+
| `prevText?` | `string` | `Previous` | Label teks tombol Previous. |
|
|
63
|
+
| `nextText?` | `string` | `Next` | Label teks tombol Next. |
|
|
64
|
+
| `iconOnly?` | `boolean` | `false` | Mode hanya menampilkan ikon pada tombol Previous dan Next tanpa teks label. |
|
|
65
|
+
| `className?` | `string` | `-` | Class CSS kustom. |
|
|
66
|
+
| `children?` | `any` | `-` | Konten kustom atau subkomponen compound (PaginationContent, PaginationItem, dll). |
|
|
67
|
+
|
|
68
|
+
### Sub-interface: `PaginationPageItem`
|
|
69
|
+
|
|
70
|
+
| Field | Type | Default | Description |
|
|
71
|
+
| :--- | :--- | :--- | :--- |
|
|
72
|
+
| `type` | `'page' | 'ellipsis'` | `-` | |
|
|
73
|
+
| `value` | `number | string` | `-` | |
|
|
74
|
+
| `key` | `string` | `-` | |
|
|
75
|
+
|
|
76
|
+
## 4. Usage Examples
|
|
77
|
+
|
|
78
|
+
### React Example
|
|
79
|
+
```tsx
|
|
80
|
+
import { useState } from "react";
|
|
81
|
+
import Pagination from "@ttdk-design-system/library-react/pagination";
|
|
82
|
+
import "@ttdk-design-system/library-react/pagination/pagination.css";
|
|
83
|
+
|
|
84
|
+
export function Example() {
|
|
85
|
+
const [page, setPage] = useState(1);
|
|
86
|
+
const [limit, setLimit] = useState(10);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<Pagination
|
|
90
|
+
page={page}
|
|
91
|
+
totalPages={15}
|
|
92
|
+
limit={limit}
|
|
93
|
+
limitOptions={[10, 25, 50, 100]}
|
|
94
|
+
canPrevPage={page > 1}
|
|
95
|
+
canNextPage={page < 15}
|
|
96
|
+
onPageChange={(p) => setPage(p)}
|
|
97
|
+
onLimitChange={(l) => setLimit(l)}
|
|
98
|
+
/>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Vue 3 Example
|
|
104
|
+
```vue
|
|
105
|
+
<script setup lang="ts">
|
|
106
|
+
import { ref } from "vue";
|
|
107
|
+
import Pagination from "@ttdk-design-system/library-vue/pagination";
|
|
108
|
+
import "@ttdk-design-system/library-vue/pagination/pagination.css";
|
|
109
|
+
|
|
110
|
+
const page = ref(1);
|
|
111
|
+
const limit = ref(10);
|
|
112
|
+
</script>
|
|
113
|
+
|
|
114
|
+
<template>
|
|
115
|
+
<Pagination
|
|
116
|
+
:page="page"
|
|
117
|
+
:total-pages="15"
|
|
118
|
+
:limit="limit"
|
|
119
|
+
:limit-options="[10, 25, 50, 100]"
|
|
120
|
+
:can-prev-page="page > 1"
|
|
121
|
+
:can-next-page="page < 15"
|
|
122
|
+
@page-change="(p: number) => (page = p)"
|
|
123
|
+
@limit-change="(l: number) => (limit = l)"
|
|
124
|
+
/>
|
|
125
|
+
</template>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Svelte Example
|
|
129
|
+
```svelte
|
|
130
|
+
<script lang="ts">
|
|
131
|
+
import Pagination from "@ttdk-design-system/library-svelte/pagination";
|
|
132
|
+
import "@ttdk-design-system/library-svelte/pagination/pagination.css";
|
|
133
|
+
|
|
134
|
+
let page = 1;
|
|
135
|
+
let limit = 10;
|
|
136
|
+
</script>
|
|
137
|
+
|
|
138
|
+
<Pagination
|
|
139
|
+
{page}
|
|
140
|
+
totalPages={15}
|
|
141
|
+
{limit}
|
|
142
|
+
limitOptions={[10, 25, 50, 100]}
|
|
143
|
+
canPrevPage={page > 1}
|
|
144
|
+
canNextPage={page < 15}
|
|
145
|
+
on:pageChange={(e) => (page = e.detail)}
|
|
146
|
+
on:limitChange={(e) => (limit = e.detail)}
|
|
147
|
+
/>
|
|
148
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export { default as Pagination } from './pagination.vue';
|
|
2
|
+
export { default as PaginationContent } from './pagination-content.vue';
|
|
3
|
+
export { default as PaginationItem } from './pagination-item.vue';
|
|
4
|
+
export { default as PaginationLink } from './pagination-link.vue';
|
|
5
|
+
export { default as PaginationPrevious } from './pagination-previous.vue';
|
|
6
|
+
export { default as PaginationNext } from './pagination-next.vue';
|
|
7
|
+
export { default as PaginationFirst } from './pagination-first.vue';
|
|
8
|
+
export { default as PaginationLast } from './pagination-last.vue';
|
|
9
|
+
export { default as PaginationEllipsis } from './pagination-ellipsis.vue';
|
|
10
|
+
|
|
11
|
+
export * from './pagination.vue';
|
|
12
|
+
export * from './pagination-content.vue';
|
|
13
|
+
export * from './pagination-item.vue';
|
|
14
|
+
export * from './pagination-link.vue';
|
|
15
|
+
export * from './pagination-previous.vue';
|
|
16
|
+
export * from './pagination-next.vue';
|
|
17
|
+
export * from './pagination-first.vue';
|
|
18
|
+
export * from './pagination-last.vue';
|
|
19
|
+
export * from './pagination-ellipsis.vue';
|
|
20
|
+
|
|
21
|
+
export { default } from './pagination.vue';
|