@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.
Files changed (232) hide show
  1. package/AI_GUIDE.md +6518 -0
  2. package/llms.txt +6518 -0
  3. package/package.json +1096 -4
  4. package/src/core/index.ts +2 -0
  5. package/src/core/tailwind-preset.cjs +62 -0
  6. package/src/core/tailwind-preset.ts +85 -0
  7. package/src/core/theme/tokens.css +663 -0
  8. package/src/core/types.ts +836 -0
  9. package/src/index.ts +17 -0
  10. package/src/tailwind-preset.cjs +62 -0
  11. package/src/tailwind-preset.ts +85 -0
  12. package/src/types.ts +1 -0
  13. package/src/web/accordion/AI_GUIDE.md +149 -0
  14. package/src/web/accordion/accordion.css +1 -0
  15. package/src/web/accordion/accordion.vue +432 -0
  16. package/src/web/accordion/accordion.vue.d.ts +227 -0
  17. package/src/web/accordion/index.ts +3 -0
  18. package/src/web/accordion/manifest.json +351 -0
  19. package/src/web/alert/AI_GUIDE.md +139 -0
  20. package/src/web/alert/alert.css +1 -0
  21. package/src/web/alert/alert.vue +290 -0
  22. package/src/web/alert/alert.vue.d.ts +154 -0
  23. package/src/web/alert/index.ts +3 -0
  24. package/src/web/alert/manifest.json +240 -0
  25. package/src/web/avatar/AI_GUIDE.md +118 -0
  26. package/src/web/avatar/avatar.css +1 -0
  27. package/src/web/avatar/avatar.vue +217 -0
  28. package/src/web/avatar/avatar.vue.d.ts +135 -0
  29. package/src/web/avatar/index.ts +3 -0
  30. package/src/web/avatar/manifest.json +195 -0
  31. package/src/web/badge/AI_GUIDE.md +94 -0
  32. package/src/web/badge/badge.css +1 -0
  33. package/src/web/badge/badge.vue +135 -0
  34. package/src/web/badge/badge.vue.d.ts +108 -0
  35. package/src/web/badge/index.ts +3 -0
  36. package/src/web/badge/manifest.json +141 -0
  37. package/src/web/banner/AI_GUIDE.md +130 -0
  38. package/src/web/banner/banner.css +1 -0
  39. package/src/web/banner/banner.vue +303 -0
  40. package/src/web/banner/banner.vue.d.ts +162 -0
  41. package/src/web/banner/index.ts +3 -0
  42. package/src/web/banner/manifest.json +240 -0
  43. package/src/web/breadcrumb/AI_GUIDE.md +120 -0
  44. package/src/web/breadcrumb/breadcrumb.css +1 -0
  45. package/src/web/breadcrumb/breadcrumb.vue +313 -0
  46. package/src/web/breadcrumb/breadcrumb.vue.d.ts +139 -0
  47. package/src/web/breadcrumb/index.ts +3 -0
  48. package/src/web/breadcrumb/manifest.json +225 -0
  49. package/src/web/button/AI_GUIDE.md +121 -0
  50. package/src/web/button/button.css +1 -0
  51. package/src/web/button/button.vue +283 -0
  52. package/src/web/button/button.vue.d.ts +147 -0
  53. package/src/web/button/index.ts +3 -0
  54. package/src/web/button/manifest.json +177 -0
  55. package/src/web/calendar/AI_GUIDE.md +151 -0
  56. package/src/web/calendar/calendar.css +1 -0
  57. package/src/web/calendar/calendar.vue +1536 -0
  58. package/src/web/calendar/calendar.vue.d.ts +261 -0
  59. package/src/web/calendar/index.ts +3 -0
  60. package/src/web/calendar/manifest.json +470 -0
  61. package/src/web/card/AI_GUIDE.md +133 -0
  62. package/src/web/card/card.css +1 -0
  63. package/src/web/card/card.vue +423 -0
  64. package/src/web/card/card.vue.d.ts +232 -0
  65. package/src/web/card/index.ts +3 -0
  66. package/src/web/card/manifest.json +357 -0
  67. package/src/web/checkbox/AI_GUIDE.md +146 -0
  68. package/src/web/checkbox/checkbox.css +1 -0
  69. package/src/web/checkbox/checkbox.vue +304 -0
  70. package/src/web/checkbox/checkbox.vue.d.ts +189 -0
  71. package/src/web/checkbox/index.ts +3 -0
  72. package/src/web/checkbox/manifest.json +294 -0
  73. package/src/web/chip/AI_GUIDE.md +186 -0
  74. package/src/web/chip/chip.css +1 -0
  75. package/src/web/chip/chip.vue +220 -0
  76. package/src/web/chip/chip.vue.d.ts +140 -0
  77. package/src/web/chip/index.ts +3 -0
  78. package/src/web/chip/manifest.json +186 -0
  79. package/src/web/dropdown/AI_GUIDE.md +167 -0
  80. package/src/web/dropdown/dropdown.css +1 -0
  81. package/src/web/dropdown/dropdown.vue +1257 -0
  82. package/src/web/dropdown/dropdown.vue.d.ts +381 -0
  83. package/src/web/dropdown/index.ts +3 -0
  84. package/src/web/dropdown/manifest.json +576 -0
  85. package/src/web/flex/AI_GUIDE.md +113 -0
  86. package/src/web/flex/flex-item.vue +94 -0
  87. package/src/web/flex/flex-item.vue.d.ts +84 -0
  88. package/src/web/flex/flex.css +1 -0
  89. package/src/web/flex/flex.vue +103 -0
  90. package/src/web/flex/flex.vue.d.ts +92 -0
  91. package/src/web/flex/index.ts +5 -0
  92. package/src/web/flex/manifest.json +162 -0
  93. package/src/web/form/index.ts +7 -0
  94. package/src/web/grid/AI_GUIDE.md +112 -0
  95. package/src/web/grid/grid-item.vue +109 -0
  96. package/src/web/grid/grid-item.vue.d.ts +99 -0
  97. package/src/web/grid/grid.css +1 -0
  98. package/src/web/grid/grid.vue +94 -0
  99. package/src/web/grid/grid.vue.d.ts +84 -0
  100. package/src/web/grid/index.ts +5 -0
  101. package/src/web/grid/manifest.json +153 -0
  102. package/src/web/index.ts +86 -0
  103. package/src/web/input/AI_GUIDE.md +159 -0
  104. package/src/web/input/index.ts +3 -0
  105. package/src/web/input/input.css +1 -0
  106. package/src/web/input/input.vue +521 -0
  107. package/src/web/input/input.vue.d.ts +274 -0
  108. package/src/web/input/manifest.json +465 -0
  109. package/src/web/input-number/AI_GUIDE.md +164 -0
  110. package/src/web/input-number/index.ts +3 -0
  111. package/src/web/input-number/input-number.css +1 -0
  112. package/src/web/input-number/input-number.vue +1153 -0
  113. package/src/web/input-number/input-number.vue.d.ts +343 -0
  114. package/src/web/input-number/manifest.json +575 -0
  115. package/src/web/modal/AI_GUIDE.md +171 -0
  116. package/src/web/modal/index.ts +3 -0
  117. package/src/web/modal/manifest.json +375 -0
  118. package/src/web/modal/modal.css +1 -0
  119. package/src/web/modal/modal.vue +344 -0
  120. package/src/web/modal/modal.vue.d.ts +64 -0
  121. package/src/web/pagination/AI_GUIDE.md +148 -0
  122. package/src/web/pagination/index.ts +21 -0
  123. package/src/web/pagination/manifest.json +432 -0
  124. package/src/web/pagination/pagination-content.vue +42 -0
  125. package/src/web/pagination/pagination-content.vue.d.ts +28 -0
  126. package/src/web/pagination/pagination-ellipsis.vue +55 -0
  127. package/src/web/pagination/pagination-ellipsis.vue.d.ts +28 -0
  128. package/src/web/pagination/pagination-first.vue +161 -0
  129. package/src/web/pagination/pagination-first.vue.d.ts +67 -0
  130. package/src/web/pagination/pagination-item.vue +42 -0
  131. package/src/web/pagination/pagination-item.vue.d.ts +28 -0
  132. package/src/web/pagination/pagination-last.vue +161 -0
  133. package/src/web/pagination/pagination-last.vue.d.ts +67 -0
  134. package/src/web/pagination/pagination-link.vue +118 -0
  135. package/src/web/pagination/pagination-link.vue.d.ts +61 -0
  136. package/src/web/pagination/pagination-next.vue +160 -0
  137. package/src/web/pagination/pagination-next.vue.d.ts +68 -0
  138. package/src/web/pagination/pagination-previous.vue +160 -0
  139. package/src/web/pagination/pagination-previous.vue.d.ts +68 -0
  140. package/src/web/pagination/pagination.css +1 -0
  141. package/src/web/pagination/pagination.vue +730 -0
  142. package/src/web/pagination/pagination.vue.d.ts +247 -0
  143. package/src/web/popover/AI_GUIDE.md +163 -0
  144. package/src/web/popover/index.ts +3 -0
  145. package/src/web/popover/manifest.json +396 -0
  146. package/src/web/popover/popover.css +1 -0
  147. package/src/web/popover/popover.vue +579 -0
  148. package/src/web/popover/popover.vue.d.ts +83 -0
  149. package/src/web/radio/AI_GUIDE.md +155 -0
  150. package/src/web/radio/index.ts +3 -0
  151. package/src/web/radio/manifest.json +294 -0
  152. package/src/web/radio/radio.css +1 -0
  153. package/src/web/radio/radio.vue +288 -0
  154. package/src/web/radio/radio.vue.d.ts +182 -0
  155. package/src/web/selectbox/AI_GUIDE.md +216 -0
  156. package/src/web/selectbox/index.ts +3 -0
  157. package/src/web/selectbox/manifest.json +477 -0
  158. package/src/web/selectbox/selectbox.css +1 -0
  159. package/src/web/selectbox/selectbox.vue +1024 -0
  160. package/src/web/selectbox/selectbox.vue.d.ts +300 -0
  161. package/src/web/sonner/AI_GUIDE.md +98 -0
  162. package/src/web/sonner/index.ts +114 -0
  163. package/src/web/sonner/manifest.json +132 -0
  164. package/src/web/sonner/sonner.css +1 -0
  165. package/src/web/sonner/sonner.vue +180 -0
  166. package/src/web/sonner/sonner.vue.d.ts +113 -0
  167. package/src/web/table/AI_GUIDE.md +145 -0
  168. package/src/web/table/index.ts +34 -0
  169. package/src/web/table/manifest.json +168 -0
  170. package/src/web/table/table-body.vue +42 -0
  171. package/src/web/table/table-body.vue.d.ts +28 -0
  172. package/src/web/table/table-caption.vue +42 -0
  173. package/src/web/table/table-caption.vue.d.ts +28 -0
  174. package/src/web/table/table-cell.vue +71 -0
  175. package/src/web/table/table-cell.vue.d.ts +49 -0
  176. package/src/web/table/table-empty.vue +105 -0
  177. package/src/web/table/table-empty.vue.d.ts +57 -0
  178. package/src/web/table/table-footer.vue +42 -0
  179. package/src/web/table/table-footer.vue.d.ts +28 -0
  180. package/src/web/table/table-head.vue +78 -0
  181. package/src/web/table/table-head.vue.d.ts +50 -0
  182. package/src/web/table/table-header.vue +42 -0
  183. package/src/web/table/table-header.vue.d.ts +28 -0
  184. package/src/web/table/table-row.vue +70 -0
  185. package/src/web/table/table-row.vue.d.ts +44 -0
  186. package/src/web/table/table.css +1 -0
  187. package/src/web/table/table.vue +298 -0
  188. package/src/web/table/table.vue.d.ts +119 -0
  189. package/src/web/tabs/AI_GUIDE.md +150 -0
  190. package/src/web/tabs/index.ts +2 -0
  191. package/src/web/tabs/manifest.json +315 -0
  192. package/src/web/tabs/tabs.css +1 -0
  193. package/src/web/tabs/tabs.vue +164 -0
  194. package/src/web/tabs/tabs.vue.d.ts +60 -0
  195. package/src/web/text/AI_GUIDE.md +129 -0
  196. package/src/web/text/index.ts +2 -0
  197. package/src/web/text/manifest.json +195 -0
  198. package/src/web/text/text.css +1 -0
  199. package/src/web/text/text.vue +195 -0
  200. package/src/web/text/text.vue.d.ts +161 -0
  201. package/src/web/textarea/AI_GUIDE.md +141 -0
  202. package/src/web/textarea/index.ts +3 -0
  203. package/src/web/textarea/manifest.json +438 -0
  204. package/src/web/textarea/textarea.css +1 -0
  205. package/src/web/textarea/textarea.vue +458 -0
  206. package/src/web/textarea/textarea.vue.d.ts +265 -0
  207. package/src/web/tooltip/AI_GUIDE.md +110 -0
  208. package/src/web/tooltip/index.ts +3 -0
  209. package/src/web/tooltip/manifest.json +222 -0
  210. package/src/web/tooltip/tooltip.css +1 -0
  211. package/src/web/tooltip/tooltip.vue +417 -0
  212. package/src/web/tooltip/tooltip.vue.d.ts +150 -0
  213. package/src/webview/bottomsheet/AI_GUIDE.md +141 -0
  214. package/src/webview/bottomsheet/bottomsheet.css +1 -0
  215. package/src/webview/bottomsheet/bottomsheet.vue +441 -0
  216. package/src/webview/bottomsheet/bottomsheet.vue.d.ts +178 -0
  217. package/src/webview/bottomsheet/index.ts +4 -0
  218. package/src/webview/bottomsheet/manifest.json +377 -0
  219. package/src/webview/button/AI_GUIDE.md +79 -0
  220. package/src/webview/button/button-ios.css +1 -0
  221. package/src/webview/button/button-ios.vue +138 -0
  222. package/src/webview/button/button-ios.vue.d.ts +102 -0
  223. package/src/webview/button/button-material.css +1 -0
  224. package/src/webview/button/button-material.vue +138 -0
  225. package/src/webview/button/button-material.vue.d.ts +102 -0
  226. package/src/webview/button/index.ts +7 -0
  227. package/src/webview/button/manifest.json +148 -0
  228. package/src/webview/index.ts +7 -0
  229. package/tailwind-preset.cjs +62 -0
  230. package/tailwind-preset.js +62 -0
  231. package/ttdk-manifest.json +10188 -0
  232. package/README.md +0 -3
@@ -0,0 +1,150 @@
1
+ # <Tabs /> — TTDK Component Reference
2
+
3
+ Komponen Tabs untuk navigasi multi-panel dengan orientasi Horizontal & Vertikal, varian Line, Pills, dan Enclosed.
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/tabs/tabs.css";`
8
+
9
+ ## 2. Subpath Component Import
10
+ ```ts
11
+ // React
12
+ import Tabs from "@ttdk-design-system/library-react/web/tabs";
13
+
14
+ // Vue 3
15
+ import Tabs from "@ttdk-design-system/library-vue/web/tabs";
16
+
17
+ // Svelte
18
+ import Tabs from "@ttdk-design-system/library-svelte/web/tabs";
19
+ ```
20
+
21
+ ## 3. Props & API Reference
22
+
23
+ | Prop | Type | Default | Description |
24
+ | :--- | :--- | :--- | :--- |
25
+ | `items` | `TabItem[]` | `-` | Daftar tab items yang akan dirender. |
26
+ | `value?` | `string` | `-` | Nilai tab yang sedang aktif (v-model / controlled value). |
27
+ | `defaultValue?` | `string` | `-` | Nilai tab default saat pertama kali dirender (uncontrolled initial value). |
28
+ | `orientation?` | `'horizontal' | 'vertical'` | `horizontal` | Orientasi tata letak tab. - `'horizontal'`: Tab berjejer ke samping dengan garis indikator di bawah (standar). - `'vertical'`: Tab berjejer ke bawah dalam kolom dengan garis indikator di samping. |
29
+ | `variant?` | `'line' | 'pills'` | `line` | Varian visual tampilan tab. - `'line'`: Garis indikator bawah/samping minimalis elegan (sesuai desain TTDK). - `'pills'`: Bentuk kapsul/tombol latar belakang penuh ala Shadcn / Nuxt UI. |
30
+ | `size?` | `'sm' | 'md' | 'lg'` | `md` | Ukuran proporsional tab (tinggi, padding, ukuran font). - `'sm'`: Kecil (12px font, compact padding). - `'md'`: Standar (14px font, regular padding). - `'lg'`: Besar (16px font, spacious padding). |
31
+ | `fitted?` | `boolean` | `false` | Membuat lebar setiap tab mengisi rata seluruh ruang container (full width / equal width). |
32
+ | `rounded?` | `'none' | 'sm' | 'md' | 'lg' | 'full'` | `md` | Sudut kelengkungan (khusus untuk varian 'pills'). - `'none'`: Sudut 0px. - `'sm'`: Sudut 4px. - `'md'`: Sudut 6px. - `'lg'`: Sudut 8px. - `'full'`: Sudut 9999px. |
33
+ | `lazy?` | `boolean` | `false` | Hanya me-render konten panel tab saat tab tersebut aktif (lazy rendering). Berguna untuk menghemat performa saat tab memiliki konten berat. |
34
+ | `style?` | `any` | `-` | Inline CSS style object native. |
35
+ | `ui?` | `UiProps` | `-` | Kustomisasi visual granular root container (UiProps). |
36
+ | `listStyle?` | `UiProps` | `-` | Style kustom untuk container tab list (UiProps). |
37
+ | `listClass?` | `string` | `-` | Class CSS kustom untuk container tab list. |
38
+ | `tabStyle?` | `UiProps` | `-` | Style kustom untuk tombol tab item (UiProps). |
39
+ | `tabClass?` | `string` | `-` | Class CSS kustom untuk tombol tab item. |
40
+ | `activeTabStyle?` | `UiProps` | `-` | Style kustom untuk tombol tab item saat aktif (UiProps). |
41
+ | `activeTabClass?` | `string` | `-` | Class CSS kustom untuk tombol tab item saat aktif. |
42
+ | `indicatorStyle?` | `UiProps` | `-` | Style kustom untuk garis indikator tab (UiProps). |
43
+ | `indicatorClass?` | `string` | `-` | Class CSS kustom untuk garis indikator tab. |
44
+ | `badgeStyle?` | `UiProps` | `-` | Style kustom untuk badge tab (UiProps). |
45
+ | `badgeClass?` | `string` | `-` | Class CSS kustom untuk badge tab. |
46
+ | `panelStyle?` | `UiProps` | `-` | Style kustom untuk panel konten tab (UiProps). |
47
+ | `panelClass?` | `string` | `-` | Class CSS kustom untuk panel konten tab. |
48
+ | `onChange?` | `(value: string) => void` | `-` | Callback event saat pengguna mengganti tab aktif. |
49
+ | `className?` | `string` | `-` | Class CSS kustom. |
50
+ | `children?` | `any` | `-` | Konten kustom atau slot anak. |
51
+
52
+ ### Sub-interface: `TabItem`
53
+
54
+ | Field | Type | Default | Description |
55
+ | :--- | :--- | :--- | :--- |
56
+ | `label` | `string` | `-` | Label teks yang ditampilkan pada tab item. |
57
+ | `value` | `string` | `-` | Identifier unik / nilai aktif untuk tab ini. |
58
+ | `icon?` | `any` | `-` | Ikon atau emoji opsional di samping label teks. |
59
+ | `badge?` | `string | number` | `-` | Badge teks atau counter angka opsional (misal: '12', 'Baru'). |
60
+ | `disabled?` | `boolean` | `false` | Menonaktifkan tab item dari interaksi klik. |
61
+ | `content?` | `any` | `-` | Konten tab opsional jika ingin menggunakan panel terintegrasi. |
62
+
63
+ ## 4. Usage Examples
64
+
65
+ ### React Example
66
+ ```tsx
67
+ import { useState } from "react";
68
+ import Tabs from "@ttdk-design-system/library-react/web/tabs";
69
+ import "@ttdk-design-system/library-react/web/tabs/tabs.css";
70
+
71
+ export function Example() {
72
+ const [activeTab, setActiveTab] = useState("overview");
73
+
74
+ const tabItems = [
75
+ { label: "Ringkasan", value: "overview", badge: 4 },
76
+ { label: "Data Pasien", value: "patients" },
77
+ { label: "Integrasi API", value: "api" },
78
+ { label: "Pengaturan", value: "settings", disabled: true }
79
+ ];
80
+
81
+ return (
82
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
83
+ <Tabs
84
+ items={tabItems}
85
+ value={activeTab}
86
+ variant="line"
87
+ onChange={(val) => setActiveTab(val)}
88
+ />
89
+ <div>Konten aktif tab: {activeTab}</div>
90
+ </div>
91
+ );
92
+ }
93
+ ```
94
+
95
+ ### Vue 3 Example
96
+ ```vue
97
+ <script setup lang="ts">
98
+ import { ref } from "vue";
99
+ import Tabs from "@ttdk-design-system/library-vue/web/tabs";
100
+ import "@ttdk-design-system/library-vue/web/tabs/tabs.css";
101
+
102
+ const activeTab = ref("overview");
103
+
104
+ const tabItems = [
105
+ { label: "Ringkasan", value: "overview", badge: 4 },
106
+ { label: "Data Pasien", value: "patients" },
107
+ { label: "Integrasi API", value: "api" },
108
+ { label: "Pengaturan", value: "settings", disabled: true }
109
+ ];
110
+ </script>
111
+
112
+ <template>
113
+ <div style="display: flex; flex-direction: column; gap: 16px;">
114
+ <Tabs
115
+ :items="tabItems"
116
+ :value="activeTab"
117
+ variant="line"
118
+ @change="(val: string) => (activeTab = val)"
119
+ />
120
+ <div>Konten aktif tab: {{ activeTab }}</div>
121
+ </div>
122
+ </template>
123
+ ```
124
+
125
+ ### Svelte Example
126
+ ```svelte
127
+ <script lang="ts">
128
+ import Tabs from "@ttdk-design-system/library-svelte/web/tabs";
129
+ import "@ttdk-design-system/library-svelte/web/tabs/tabs.css";
130
+
131
+ let activeTab = "overview";
132
+
133
+ const tabItems = [
134
+ { label: "Ringkasan", value: "overview", badge: 4 },
135
+ { label: "Data Pasien", value: "patients" },
136
+ { label: "Integrasi API", value: "api" },
137
+ { label: "Pengaturan", value: "settings", disabled: true }
138
+ ];
139
+ </script>
140
+
141
+ <div style="display: flex; flex-direction: column; gap: 16px;">
142
+ <Tabs
143
+ items={tabItems}
144
+ value={activeTab}
145
+ variant="line"
146
+ on:change={(e) => (activeTab = e.detail)}
147
+ />
148
+ <div>Konten aktif tab: {activeTab}</div>
149
+ </div>
150
+ ```
@@ -0,0 +1,2 @@
1
+ export { default } from "./tabs.vue";
2
+ export type { TabItem, TabsProps, TabsUiProps } from "./tabs.vue";
@@ -0,0 +1,315 @@
1
+ {
2
+ "name": "@ttdk-design-system/tabs",
3
+ "component": "Tabs",
4
+ "folder": "tabs",
5
+ "description": "Komponen Tabs untuk navigasi multi-panel dengan orientasi Horizontal & Vertikal, varian Line, Pills, dan Enclosed.",
6
+ "stylesheets": [
7
+ {
8
+ "theme": "common",
9
+ "file": "tabs.css",
10
+ "importPath": "web/tabs/tabs.css"
11
+ }
12
+ ],
13
+ "props": {
14
+ "items": {
15
+ "name": "items",
16
+ "type": "TabItem[]",
17
+ "optional": false,
18
+ "default": null,
19
+ "description": "Daftar tab items yang akan dirender.",
20
+ "example": null,
21
+ "param": null
22
+ },
23
+ "value": {
24
+ "name": "value",
25
+ "type": "string",
26
+ "optional": true,
27
+ "default": null,
28
+ "description": "Nilai tab yang sedang aktif (v-model / controlled value).",
29
+ "example": null,
30
+ "param": null
31
+ },
32
+ "defaultValue": {
33
+ "name": "defaultValue",
34
+ "type": "string",
35
+ "optional": true,
36
+ "default": null,
37
+ "description": "Nilai tab default saat pertama kali dirender (uncontrolled initial value).",
38
+ "example": null,
39
+ "param": null
40
+ },
41
+ "orientation": {
42
+ "name": "orientation",
43
+ "type": "'horizontal' | 'vertical'",
44
+ "optional": true,
45
+ "default": "horizontal",
46
+ "description": "Orientasi tata letak tab. - `'horizontal'`: Tab berjejer ke samping dengan garis indikator di bawah (standar). - `'vertical'`: Tab berjejer ke bawah dalam kolom dengan garis indikator di samping.",
47
+ "example": null,
48
+ "param": null
49
+ },
50
+ "variant": {
51
+ "name": "variant",
52
+ "type": "'line' | 'pills'",
53
+ "optional": true,
54
+ "default": "line",
55
+ "description": "Varian visual tampilan tab. - `'line'`: Garis indikator bawah/samping minimalis elegan (sesuai desain TTDK). - `'pills'`: Bentuk kapsul/tombol latar belakang penuh ala Shadcn / Nuxt UI.",
56
+ "example": null,
57
+ "param": null
58
+ },
59
+ "size": {
60
+ "name": "size",
61
+ "type": "'sm' | 'md' | 'lg'",
62
+ "optional": true,
63
+ "default": "md",
64
+ "description": "Ukuran proporsional tab (tinggi, padding, ukuran font). - `'sm'`: Kecil (12px font, compact padding). - `'md'`: Standar (14px font, regular padding). - `'lg'`: Besar (16px font, spacious padding).",
65
+ "example": null,
66
+ "param": null
67
+ },
68
+ "fitted": {
69
+ "name": "fitted",
70
+ "type": "boolean",
71
+ "optional": true,
72
+ "default": "false",
73
+ "description": "Membuat lebar setiap tab mengisi rata seluruh ruang container (full width / equal width).",
74
+ "example": null,
75
+ "param": null
76
+ },
77
+ "rounded": {
78
+ "name": "rounded",
79
+ "type": "'none' | 'sm' | 'md' | 'lg' | 'full'",
80
+ "optional": true,
81
+ "default": "md",
82
+ "description": "Sudut kelengkungan (khusus untuk varian 'pills'). - `'none'`: Sudut 0px. - `'sm'`: Sudut 4px. - `'md'`: Sudut 6px. - `'lg'`: Sudut 8px. - `'full'`: Sudut 9999px.",
83
+ "example": null,
84
+ "param": null
85
+ },
86
+ "lazy": {
87
+ "name": "lazy",
88
+ "type": "boolean",
89
+ "optional": true,
90
+ "default": "false",
91
+ "description": "Hanya me-render konten panel tab saat tab tersebut aktif (lazy rendering). Berguna untuk menghemat performa saat tab memiliki konten berat.",
92
+ "example": null,
93
+ "param": null
94
+ },
95
+ "style": {
96
+ "name": "style",
97
+ "type": "any",
98
+ "optional": true,
99
+ "default": null,
100
+ "description": "Inline CSS style object native.",
101
+ "example": null,
102
+ "param": null
103
+ },
104
+ "ui": {
105
+ "name": "ui",
106
+ "type": "UiProps",
107
+ "optional": true,
108
+ "default": null,
109
+ "description": "Kustomisasi visual granular root container (UiProps).",
110
+ "example": null,
111
+ "param": null
112
+ },
113
+ "listStyle": {
114
+ "name": "listStyle",
115
+ "type": "UiProps",
116
+ "optional": true,
117
+ "default": null,
118
+ "description": "Style kustom untuk container tab list (UiProps).",
119
+ "example": null,
120
+ "param": null
121
+ },
122
+ "listClass": {
123
+ "name": "listClass",
124
+ "type": "string",
125
+ "optional": true,
126
+ "default": null,
127
+ "description": "Class CSS kustom untuk container tab list.",
128
+ "example": null,
129
+ "param": null
130
+ },
131
+ "tabStyle": {
132
+ "name": "tabStyle",
133
+ "type": "UiProps",
134
+ "optional": true,
135
+ "default": null,
136
+ "description": "Style kustom untuk tombol tab item (UiProps).",
137
+ "example": null,
138
+ "param": null
139
+ },
140
+ "tabClass": {
141
+ "name": "tabClass",
142
+ "type": "string",
143
+ "optional": true,
144
+ "default": null,
145
+ "description": "Class CSS kustom untuk tombol tab item.",
146
+ "example": null,
147
+ "param": null
148
+ },
149
+ "activeTabStyle": {
150
+ "name": "activeTabStyle",
151
+ "type": "UiProps",
152
+ "optional": true,
153
+ "default": null,
154
+ "description": "Style kustom untuk tombol tab item saat aktif (UiProps).",
155
+ "example": null,
156
+ "param": null
157
+ },
158
+ "activeTabClass": {
159
+ "name": "activeTabClass",
160
+ "type": "string",
161
+ "optional": true,
162
+ "default": null,
163
+ "description": "Class CSS kustom untuk tombol tab item saat aktif.",
164
+ "example": null,
165
+ "param": null
166
+ },
167
+ "indicatorStyle": {
168
+ "name": "indicatorStyle",
169
+ "type": "UiProps",
170
+ "optional": true,
171
+ "default": null,
172
+ "description": "Style kustom untuk garis indikator tab (UiProps).",
173
+ "example": null,
174
+ "param": null
175
+ },
176
+ "indicatorClass": {
177
+ "name": "indicatorClass",
178
+ "type": "string",
179
+ "optional": true,
180
+ "default": null,
181
+ "description": "Class CSS kustom untuk garis indikator tab.",
182
+ "example": null,
183
+ "param": null
184
+ },
185
+ "badgeStyle": {
186
+ "name": "badgeStyle",
187
+ "type": "UiProps",
188
+ "optional": true,
189
+ "default": null,
190
+ "description": "Style kustom untuk badge tab (UiProps).",
191
+ "example": null,
192
+ "param": null
193
+ },
194
+ "badgeClass": {
195
+ "name": "badgeClass",
196
+ "type": "string",
197
+ "optional": true,
198
+ "default": null,
199
+ "description": "Class CSS kustom untuk badge tab.",
200
+ "example": null,
201
+ "param": null
202
+ },
203
+ "panelStyle": {
204
+ "name": "panelStyle",
205
+ "type": "UiProps",
206
+ "optional": true,
207
+ "default": null,
208
+ "description": "Style kustom untuk panel konten tab (UiProps).",
209
+ "example": null,
210
+ "param": null
211
+ },
212
+ "panelClass": {
213
+ "name": "panelClass",
214
+ "type": "string",
215
+ "optional": true,
216
+ "default": null,
217
+ "description": "Class CSS kustom untuk panel konten tab.",
218
+ "example": null,
219
+ "param": null
220
+ },
221
+ "onChange": {
222
+ "name": "onChange",
223
+ "type": "(value: string) => void",
224
+ "optional": true,
225
+ "default": null,
226
+ "description": "Callback event saat pengguna mengganti tab aktif.",
227
+ "example": null,
228
+ "param": "value Nilai/identifier tab yang dipilih."
229
+ },
230
+ "className": {
231
+ "name": "className",
232
+ "type": "string",
233
+ "optional": true,
234
+ "default": null,
235
+ "description": "Class CSS kustom.",
236
+ "example": null,
237
+ "param": null
238
+ },
239
+ "children": {
240
+ "name": "children",
241
+ "type": "any",
242
+ "optional": true,
243
+ "default": null,
244
+ "description": "Konten kustom atau slot anak.",
245
+ "example": null,
246
+ "param": null
247
+ }
248
+ },
249
+ "subInterfaces": {
250
+ "TabItem": {
251
+ "label": {
252
+ "name": "label",
253
+ "type": "string",
254
+ "optional": false,
255
+ "default": null,
256
+ "description": "Label teks yang ditampilkan pada tab item.",
257
+ "example": null,
258
+ "param": null
259
+ },
260
+ "value": {
261
+ "name": "value",
262
+ "type": "string",
263
+ "optional": false,
264
+ "default": null,
265
+ "description": "Identifier unik / nilai aktif untuk tab ini.",
266
+ "example": null,
267
+ "param": null
268
+ },
269
+ "icon": {
270
+ "name": "icon",
271
+ "type": "any",
272
+ "optional": true,
273
+ "default": null,
274
+ "description": "Ikon atau emoji opsional di samping label teks.",
275
+ "example": null,
276
+ "param": null
277
+ },
278
+ "badge": {
279
+ "name": "badge",
280
+ "type": "string | number",
281
+ "optional": true,
282
+ "default": null,
283
+ "description": "Badge teks atau counter angka opsional (misal: '12', 'Baru').",
284
+ "example": null,
285
+ "param": null
286
+ },
287
+ "disabled": {
288
+ "name": "disabled",
289
+ "type": "boolean",
290
+ "optional": true,
291
+ "default": "false",
292
+ "description": "Menonaktifkan tab item dari interaksi klik.",
293
+ "example": null,
294
+ "param": null
295
+ },
296
+ "content": {
297
+ "name": "content",
298
+ "type": "any",
299
+ "optional": true,
300
+ "default": null,
301
+ "description": "Konten tab opsional jika ingin menggunakan panel terintegrasi.",
302
+ "example": null,
303
+ "param": null
304
+ }
305
+ }
306
+ },
307
+ "supportedFrameworks": [
308
+ "react",
309
+ "vue",
310
+ "svelte"
311
+ ],
312
+ "themes": [
313
+ "tw"
314
+ ]
315
+ }
@@ -0,0 +1 @@
1
+ .ttdk-tabs{--tab-radius:var(--ttdk-radius-input-md);box-sizing:border-box;font-family:var(--ttdk-font-sans);color:var(--ttdk-ui-color,var(--ttdk-text-icon-primary));width:100%;min-width:0;max-width:100%;display:flex;&[data-orientation=horizontal]{flex-direction:column;width:100%;min-width:0;max-width:100%;& .ttdk-tabs-list{box-sizing:border-box;scrollbar-width:none;-webkit-overflow-scrolling:touch;flex-direction:row;align-items:center;width:100%;min-width:0;max-width:100%;display:flex;overflow:auto hidden;&::-webkit-scrollbar{display:none}}}&[data-orientation=vertical]{align-items:stretch;gap:var(--ttdk-spacing-md);flex-direction:row;width:100%;min-width:0;max-width:100%;& .ttdk-tabs-list{flex-direction:column;flex-shrink:0;min-width:180px;max-width:260px;display:flex;@media (width<=640px){min-width:120px;max-width:160px}}& .ttdk-tabs-panels{flex:1;min-width:0}}&[data-fitted=true][data-orientation=horizontal]{& .ttdk-tabs-list{width:100%}& .ttdk-tab-item{text-align:center;flex:1;justify-content:center}}&[data-size=sm]{font-size:var(--ttdk-font-size-12);& .ttdk-tab-item{font-size:var(--ttdk-font-size-12);gap:6px}&[data-variant=line][data-orientation=horizontal] .ttdk-tab-item{gap:6px;padding:8px 2px}&[data-variant=line][data-orientation=vertical] .ttdk-tab-item{padding:8px 12px}&[data-variant=pills] .ttdk-tab-item{font-size:var(--ttdk-font-size-12);padding:5px 10px}& .ttdk-tab-icon{font-size:var(--ttdk-font-size-14)}}&[data-size=md]{font-size:var(--ttdk-font-size-14);& .ttdk-tab-item{font-size:var(--ttdk-font-size-14);gap:var(--ttdk-spacing-xs)}&[data-variant=line][data-orientation=horizontal] .ttdk-tab-item{padding:12px 4px}&[data-variant=line][data-orientation=vertical] .ttdk-tab-item{padding:10px 14px}&[data-variant=pills] .ttdk-tab-item{padding:6px 14px;font-size:13px}& .ttdk-tab-icon{font-size:var(--ttdk-font-size-16)}}&[data-size=lg]{font-size:var(--ttdk-font-size-16);& .ttdk-tab-item{font-size:var(--ttdk-font-size-16);gap:10px}&[data-variant=line][data-orientation=horizontal] .ttdk-tab-item{padding:14px 6px}&[data-variant=line][data-orientation=vertical] .ttdk-tab-item{padding:12px 16px}&[data-variant=pills] .ttdk-tab-item{font-size:var(--ttdk-font-size-14);padding:8px 18px}& .ttdk-tab-icon{font-size:var(--ttdk-font-size-18)}}&[data-rounded=none]{--tab-radius:var(--ttdk-radius-none)}&[data-rounded=sm]{--tab-radius:var(--ttdk-radius-input-sm)}&[data-rounded=md]{--tab-radius:var(--ttdk-radius-input-md)}&[data-rounded=lg]{--tab-radius:var(--ttdk-radius-input-lg)}&[data-rounded=full]{--tab-radius:var(--ttdk-radius-full)}}.ttdk-tab-item{font-family:inherit;font-weight:var(--ttdk-font-weight-medium);color:var(--ttdk-ui-color,var(--ttdk-text-icon-secondary));cursor:pointer;white-space:nowrap;user-select:none;background:0 0;border:none;outline:none;flex-shrink:0;justify-content:flex-start;align-items:center;transition:color .15s,background-color .15s,border-color .15s;display:inline-flex;position:relative;&:hover:not(:disabled){color:var(--ttdk-text-icon-primary)}&:focus-visible{box-shadow:0 0 0 2px color-mix(in oklab, var(--ttdk-brand-default) 40%, transparent);border-radius:var(--ttdk-radius-input-sm)}&[data-state=active]{color:var(--ttdk-brand-default);font-weight:var(--ttdk-font-weight-semibold)}&:disabled,&[data-disabled=true]{opacity:.45;cursor:not-allowed;pointer-events:none}}.ttdk-tab-icon{flex-shrink:0;justify-content:center;align-items:center;display:inline-flex}.ttdk-tab-label{display:inline-block}.ttdk-tab-badge{font-size:11px;font-weight:var(--ttdk-font-weight-semibold);border-radius:var(--ttdk-radius-full);background:var(--ttdk-bg-secondary-disabled);color:var(--ttdk-text-icon-secondary);justify-content:center;align-items:center;padding:1px 6px;line-height:1.2;transition:all .15s;display:inline-flex}.ttdk-tab-item[data-state=active] .ttdk-tab-badge{background:color-mix(in oklab, var(--ttdk-brand-default) 15%, transparent);color:var(--ttdk-brand-default)}.ttdk-tabs[data-variant=line]{&[data-orientation=horizontal]{& .ttdk-tabs-list{border-bottom:var(--ttdk-stroke-width-thin) solid var(--ttdk-stroke-subtle);gap:var(--ttdk-spacing-lg)}& .ttdk-tab-indicator{background-color:var(--ttdk-brand-default);border-radius:var(--ttdk-radius-input-xs) var(--ttdk-radius-input-xs) 0 0;height:2px;animation:.2s ease-in-out ttdkTabFadeIn;position:absolute;bottom:-1px;left:0;right:0}}&[data-orientation=vertical]{& .ttdk-tabs-list{border-right:var(--ttdk-stroke-width-thin) solid var(--ttdk-stroke-subtle);gap:var(--ttdk-spacing-xxs);padding-right:var(--ttdk-spacing-xxs)}& .ttdk-tab-item{border-radius:var(--tab-radius);&[data-state=active]{background:color-mix(in oklab, var(--ttdk-brand-default) 8%, transparent)}}& .ttdk-tab-indicator{background-color:var(--ttdk-brand-default);border-radius:var(--ttdk-radius-input-xs);width:2px;animation:.2s ease-in-out ttdkTabFadeIn;position:absolute;top:4px;bottom:4px;right:-5px}}}.ttdk-tabs[data-variant=pills]{width:100%;min-width:0;max-width:100%;& .ttdk-tabs-list{box-sizing:border-box;background:var(--ttdk-bg-secondary-disabled);width:100%;min-width:0;max-width:100%;padding:var(--ttdk-spacing-xxs);border-radius:var(--tab-radius);gap:var(--ttdk-spacing-xxs)}& .ttdk-tab-item{border-radius:var(--tab-radius);color:var(--ttdk-text-icon-secondary);&:hover:not(:disabled){color:var(--ttdk-text-icon-primary)}&[data-state=active]{background:var(--ttdk-bg-card);color:var(--ttdk-text-icon-primary);box-shadow:var(--ttdk-shadow-sm)}}}.ttdk-tabs-panels{width:100%}.ttdk-tab-panel{padding:var(--ttdk-spacing-md) 0;color:var(--ttdk-text-icon-primary);animation:.2s ease-in-out ttdkTabFadeIn}@keyframes ttdkTabFadeIn{0%{opacity:0}to{opacity:1}}
@@ -0,0 +1,164 @@
1
+ <template>
2
+ <div
3
+ :class="['ttdk-tabs', className, $attrs.class]"
4
+ :data-orientation="orientation || 'horizontal'"
5
+ :data-variant="variant || 'line'"
6
+ :data-size="size || 'md'"
7
+ :data-fitted="fitted ? 'true' : 'false'"
8
+ :data-rounded="rounded || 'md'"
9
+ :style="{
10
+ ...buildResponsiveVars(ui, {
11
+ activeColor: '--ttdk-ui-tabs-active-color',
12
+ indicatorColor: '--ttdk-ui-tabs-indicator-color',
13
+ inactiveColor: '--ttdk-ui-tabs-inactive-color',
14
+ bg: '--ttdk-ui-tabs-bg',
15
+ borderColor: '--ttdk-ui-tabs-border-color',
16
+ textSize: '--ttdk-ui-tabs-font-size',
17
+ fontSize: '--ttdk-ui-tabs-font-size',
18
+ padding: '--ttdk-ui-tabs-padding',
19
+ gap: '--ttdk-ui-tabs-gap',
20
+ }),
21
+ }"
22
+ >
23
+ <div
24
+ class="ttdk-tabs-list"
25
+ role="tablist"
26
+ :aria-orientation="orientation || 'horizontal'"
27
+ >
28
+ <template :key="item.value" v-for="item in items">
29
+ <button
30
+ type="button"
31
+ role="tab"
32
+ class="ttdk-tab-item"
33
+ :data-state="getCurrentActive() === item.value ? 'active' : 'inactive'"
34
+ :data-disabled="item.disabled ? 'true' : 'false'"
35
+ :disabled="item.disabled"
36
+ :aria-selected="getCurrentActive() === item.value"
37
+ @click="handleTabClick(item.value, item.disabled)"
38
+ >
39
+ <template v-if="item.icon">
40
+ <span class="ttdk-tab-icon">{{ item.icon }}</span>
41
+ </template>
42
+
43
+ <span class="ttdk-tab-label">{{ item.label }}</span>
44
+ <template v-if="item.badge !== undefined && item.badge !== null">
45
+ <span class="ttdk-tab-badge">{{ item.badge }}</span>
46
+ </template>
47
+
48
+ <template
49
+ v-if="
50
+ getCurrentActive() === item.value &&
51
+ (!variant || variant === 'line')
52
+ "
53
+ >
54
+ <span class="ttdk-tab-indicator"></span>
55
+ </template>
56
+ </button>
57
+ </template>
58
+ </div>
59
+ <div class="ttdk-tabs-panels">
60
+ <template v-if="getActiveItem() && getActiveItem()?.content">
61
+ <div class="ttdk-tab-panel" role="tabpanel">
62
+ {{ getActiveItem()?.content }}
63
+ </div>
64
+ </template>
65
+
66
+ <template v-if="$slots.default">
67
+ <div class="ttdk-tab-children"><slot /></div>
68
+ </template>
69
+ </div>
70
+ </div>
71
+ </template>
72
+
73
+ <script setup lang="ts">
74
+ import { ref } from "vue";
75
+ import { buildResponsiveVars, type ResponsiveBreakpoints } from "../../core/types";
76
+
77
+ export interface TabItem {
78
+ label: string;
79
+ value: string;
80
+ icon?: any;
81
+ badge?: string | number;
82
+ disabled?: boolean;
83
+ content?: any;
84
+ }
85
+
86
+ export interface TabsUiBaseProps {
87
+ activeColor?: string;
88
+ indicatorColor?: string;
89
+ inactiveColor?: string;
90
+ bg?: string;
91
+ borderColor?: string;
92
+ textSize?: string;
93
+ fontSize?: string;
94
+ padding?: string;
95
+ gap?: string;
96
+ }
97
+
98
+ export interface TabsUiProps extends TabsUiBaseProps, ResponsiveBreakpoints<TabsUiBaseProps> {
99
+ sm?: Partial<TabsUiBaseProps>;
100
+ lt_sm?: Partial<TabsUiBaseProps>;
101
+ md?: Partial<TabsUiBaseProps>;
102
+ lt_md?: Partial<TabsUiBaseProps>;
103
+ lg?: Partial<TabsUiBaseProps>;
104
+ lt_lg?: Partial<TabsUiBaseProps>;
105
+ xl?: Partial<TabsUiBaseProps>;
106
+ lt_xl?: Partial<TabsUiBaseProps>;
107
+ }
108
+
109
+ export interface TabsProps {
110
+ items: TabItem[];
111
+ modelValue?: string;
112
+ value?: string;
113
+ defaultValue?: string;
114
+ orientation?: "horizontal" | "vertical";
115
+ variant?: "line" | "pills";
116
+ size?: "sm" | "md" | "lg";
117
+ fitted?: boolean;
118
+ rounded?: "none" | "sm" | "md" | "lg" | "full";
119
+ ui?: TabsUiProps;
120
+ className?: string;
121
+ class?: string;
122
+ onChange?: (value: string) => void;
123
+ children?: any;
124
+ }
125
+
126
+ const props = defineProps<TabsProps>();
127
+ const emit = defineEmits<{
128
+ (e: "update:modelValue", value: string): void;
129
+ (e: "update:value", value: string): void;
130
+ (e: "change", value: string): void;
131
+ }>();
132
+
133
+ const internalActive = ref(
134
+ props.modelValue ||
135
+ props.value ||
136
+ props.defaultValue ||
137
+ (props.items && props.items[0]?.value) ||
138
+ ""
139
+ );
140
+
141
+ function getCurrentActive() {
142
+ if (props.modelValue !== undefined) return props.modelValue;
143
+ if (props.value !== undefined) return props.value;
144
+ return internalActive.value;
145
+ }
146
+
147
+ function handleTabClick(itemValue: string, disabled?: boolean) {
148
+ if (disabled) return;
149
+ internalActive.value = itemValue;
150
+ emit("update:modelValue", itemValue);
151
+ emit("update:value", itemValue);
152
+ emit("change", itemValue);
153
+ if (props.onChange) {
154
+ props.onChange(itemValue);
155
+ }
156
+ }
157
+
158
+ function getActiveItem() {
159
+ const current = getCurrentActive();
160
+ return props.items
161
+ ? props.items.find((item: any) => item.value === current)
162
+ : null;
163
+ }
164
+ </script>