@ttdk-design-system/library-react 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 (184) hide show
  1. package/AI_GUIDE.md +6518 -0
  2. package/llms.txt +6518 -0
  3. package/package.json +1098 -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.tsx +430 -0
  16. package/src/web/accordion/index.ts +3 -0
  17. package/src/web/accordion/manifest.json +351 -0
  18. package/src/web/alert/AI_GUIDE.md +139 -0
  19. package/src/web/alert/alert.css +1 -0
  20. package/src/web/alert/alert.tsx +318 -0
  21. package/src/web/alert/index.ts +3 -0
  22. package/src/web/alert/manifest.json +240 -0
  23. package/src/web/avatar/AI_GUIDE.md +118 -0
  24. package/src/web/avatar/avatar.css +1 -0
  25. package/src/web/avatar/avatar.tsx +233 -0
  26. package/src/web/avatar/index.ts +3 -0
  27. package/src/web/avatar/manifest.json +195 -0
  28. package/src/web/badge/AI_GUIDE.md +94 -0
  29. package/src/web/badge/badge.css +1 -0
  30. package/src/web/badge/badge.tsx +140 -0
  31. package/src/web/badge/index.ts +2 -0
  32. package/src/web/badge/manifest.json +141 -0
  33. package/src/web/banner/AI_GUIDE.md +130 -0
  34. package/src/web/banner/banner.css +1 -0
  35. package/src/web/banner/banner.tsx +334 -0
  36. package/src/web/banner/index.ts +3 -0
  37. package/src/web/banner/manifest.json +240 -0
  38. package/src/web/breadcrumb/AI_GUIDE.md +120 -0
  39. package/src/web/breadcrumb/breadcrumb.css +1 -0
  40. package/src/web/breadcrumb/breadcrumb.tsx +306 -0
  41. package/src/web/breadcrumb/index.ts +3 -0
  42. package/src/web/breadcrumb/manifest.json +225 -0
  43. package/src/web/button/AI_GUIDE.md +121 -0
  44. package/src/web/button/button.css +1 -0
  45. package/src/web/button/button.tsx +287 -0
  46. package/src/web/button/index.ts +2 -0
  47. package/src/web/button/manifest.json +177 -0
  48. package/src/web/calendar/AI_GUIDE.md +151 -0
  49. package/src/web/calendar/calendar.css +1 -0
  50. package/src/web/calendar/calendar.tsx +1611 -0
  51. package/src/web/calendar/index.ts +3 -0
  52. package/src/web/calendar/manifest.json +470 -0
  53. package/src/web/card/AI_GUIDE.md +133 -0
  54. package/src/web/card/card.css +1 -0
  55. package/src/web/card/card.tsx +408 -0
  56. package/src/web/card/index.ts +4 -0
  57. package/src/web/card/manifest.json +357 -0
  58. package/src/web/checkbox/AI_GUIDE.md +146 -0
  59. package/src/web/checkbox/checkbox.css +1 -0
  60. package/src/web/checkbox/checkbox.tsx +307 -0
  61. package/src/web/checkbox/index.ts +2 -0
  62. package/src/web/checkbox/manifest.json +294 -0
  63. package/src/web/chip/AI_GUIDE.md +186 -0
  64. package/src/web/chip/chip.css +1 -0
  65. package/src/web/chip/chip.tsx +230 -0
  66. package/src/web/chip/index.ts +2 -0
  67. package/src/web/chip/manifest.json +186 -0
  68. package/src/web/dropdown/AI_GUIDE.md +167 -0
  69. package/src/web/dropdown/dropdown.css +1 -0
  70. package/src/web/dropdown/dropdown.tsx +1274 -0
  71. package/src/web/dropdown/index.ts +3 -0
  72. package/src/web/dropdown/manifest.json +576 -0
  73. package/src/web/flex/AI_GUIDE.md +113 -0
  74. package/src/web/flex/flex-item.tsx +97 -0
  75. package/src/web/flex/flex.css +1 -0
  76. package/src/web/flex/flex.tsx +108 -0
  77. package/src/web/flex/index.ts +5 -0
  78. package/src/web/flex/manifest.json +162 -0
  79. package/src/web/form/index.ts +7 -0
  80. package/src/web/grid/AI_GUIDE.md +112 -0
  81. package/src/web/grid/grid-item.tsx +112 -0
  82. package/src/web/grid/grid.css +1 -0
  83. package/src/web/grid/grid.tsx +99 -0
  84. package/src/web/grid/index.ts +5 -0
  85. package/src/web/grid/manifest.json +153 -0
  86. package/src/web/index.ts +86 -0
  87. package/src/web/input/AI_GUIDE.md +159 -0
  88. package/src/web/input/index.ts +3 -0
  89. package/src/web/input/input.css +1 -0
  90. package/src/web/input/input.tsx +537 -0
  91. package/src/web/input/manifest.json +465 -0
  92. package/src/web/input-number/AI_GUIDE.md +164 -0
  93. package/src/web/input-number/index.ts +3 -0
  94. package/src/web/input-number/input-number.css +1 -0
  95. package/src/web/input-number/input-number.tsx +1193 -0
  96. package/src/web/input-number/manifest.json +575 -0
  97. package/src/web/modal/AI_GUIDE.md +171 -0
  98. package/src/web/modal/index.ts +3 -0
  99. package/src/web/modal/manifest.json +375 -0
  100. package/src/web/modal/modal.css +1 -0
  101. package/src/web/modal/modal.tsx +571 -0
  102. package/src/web/pagination/AI_GUIDE.md +148 -0
  103. package/src/web/pagination/index.ts +21 -0
  104. package/src/web/pagination/manifest.json +432 -0
  105. package/src/web/pagination/pagination-content.tsx +45 -0
  106. package/src/web/pagination/pagination-ellipsis.tsx +54 -0
  107. package/src/web/pagination/pagination-first.tsx +155 -0
  108. package/src/web/pagination/pagination-item.tsx +45 -0
  109. package/src/web/pagination/pagination-last.tsx +155 -0
  110. package/src/web/pagination/pagination-link.tsx +121 -0
  111. package/src/web/pagination/pagination-next.tsx +158 -0
  112. package/src/web/pagination/pagination-previous.tsx +158 -0
  113. package/src/web/pagination/pagination.css +1 -0
  114. package/src/web/pagination/pagination.tsx +687 -0
  115. package/src/web/popover/AI_GUIDE.md +163 -0
  116. package/src/web/popover/index.ts +3 -0
  117. package/src/web/popover/manifest.json +396 -0
  118. package/src/web/popover/popover.css +1 -0
  119. package/src/web/popover/popover.tsx +702 -0
  120. package/src/web/radio/AI_GUIDE.md +155 -0
  121. package/src/web/radio/index.ts +2 -0
  122. package/src/web/radio/manifest.json +294 -0
  123. package/src/web/radio/radio.css +1 -0
  124. package/src/web/radio/radio.tsx +290 -0
  125. package/src/web/selectbox/AI_GUIDE.md +216 -0
  126. package/src/web/selectbox/index.ts +3 -0
  127. package/src/web/selectbox/manifest.json +477 -0
  128. package/src/web/selectbox/selectbox.css +1 -0
  129. package/src/web/selectbox/selectbox.tsx +1048 -0
  130. package/src/web/sonner/AI_GUIDE.md +98 -0
  131. package/src/web/sonner/index.ts +114 -0
  132. package/src/web/sonner/manifest.json +132 -0
  133. package/src/web/sonner/sonner.css +1 -0
  134. package/src/web/sonner/sonner.tsx +177 -0
  135. package/src/web/table/AI_GUIDE.md +145 -0
  136. package/src/web/table/index.ts +35 -0
  137. package/src/web/table/manifest.json +168 -0
  138. package/src/web/table/table-body.tsx +45 -0
  139. package/src/web/table/table-caption.tsx +45 -0
  140. package/src/web/table/table-cell.tsx +74 -0
  141. package/src/web/table/table-empty.tsx +108 -0
  142. package/src/web/table/table-footer.tsx +45 -0
  143. package/src/web/table/table-head.tsx +80 -0
  144. package/src/web/table/table-header.tsx +45 -0
  145. package/src/web/table/table-row.tsx +69 -0
  146. package/src/web/table/table.css +1 -0
  147. package/src/web/table/table.tsx +305 -0
  148. package/src/web/tabs/AI_GUIDE.md +150 -0
  149. package/src/web/tabs/index.ts +3 -0
  150. package/src/web/tabs/manifest.json +315 -0
  151. package/src/web/tabs/tabs.css +1 -0
  152. package/src/web/tabs/tabs.tsx +332 -0
  153. package/src/web/text/AI_GUIDE.md +129 -0
  154. package/src/web/text/index.ts +3 -0
  155. package/src/web/text/manifest.json +195 -0
  156. package/src/web/text/text.css +1 -0
  157. package/src/web/text/text.tsx +689 -0
  158. package/src/web/textarea/AI_GUIDE.md +141 -0
  159. package/src/web/textarea/index.ts +3 -0
  160. package/src/web/textarea/manifest.json +438 -0
  161. package/src/web/textarea/textarea.css +1 -0
  162. package/src/web/textarea/textarea.tsx +467 -0
  163. package/src/web/tooltip/AI_GUIDE.md +110 -0
  164. package/src/web/tooltip/index.ts +3 -0
  165. package/src/web/tooltip/manifest.json +222 -0
  166. package/src/web/tooltip/tooltip.css +1 -0
  167. package/src/web/tooltip/tooltip.tsx +423 -0
  168. package/src/webview/bottomsheet/AI_GUIDE.md +141 -0
  169. package/src/webview/bottomsheet/bottomsheet.css +1 -0
  170. package/src/webview/bottomsheet/bottomsheet.tsx +603 -0
  171. package/src/webview/bottomsheet/index.ts +4 -0
  172. package/src/webview/bottomsheet/manifest.json +377 -0
  173. package/src/webview/button/AI_GUIDE.md +79 -0
  174. package/src/webview/button/button-ios.css +1 -0
  175. package/src/webview/button/button-ios.tsx +138 -0
  176. package/src/webview/button/button-material.css +1 -0
  177. package/src/webview/button/button-material.tsx +138 -0
  178. package/src/webview/button/index.ts +7 -0
  179. package/src/webview/button/manifest.json +148 -0
  180. package/src/webview/index.ts +7 -0
  181. package/tailwind-preset.cjs +62 -0
  182. package/tailwind-preset.js +62 -0
  183. package/ttdk-manifest.json +10188 -0
  184. package/README.md +0 -3
@@ -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,332 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ import { useState } from "react";
4
+
5
+ export interface TabItem {
6
+ /**
7
+ * Label teks yang ditampilkan pada tab item.
8
+ */
9
+ label: string;
10
+
11
+ /**
12
+ * Identifier unik / nilai aktif untuk tab ini.
13
+ */
14
+ value: string;
15
+
16
+ /**
17
+ * Ikon atau emoji opsional di samping label teks.
18
+ */
19
+ icon?: any;
20
+
21
+ /**
22
+ * Badge teks atau counter angka opsional (misal: '12', 'Baru').
23
+ */
24
+ badge?: string | number;
25
+
26
+ /**
27
+ * Menonaktifkan tab item dari interaksi klik.
28
+ * @default false
29
+ */
30
+ disabled?: boolean;
31
+
32
+ /**
33
+ * Konten tab opsional jika ingin menggunakan panel terintegrasi.
34
+ */
35
+ content?: any;
36
+ }
37
+ export type TabsUiProps = UiProps;
38
+ export interface TabsProps {
39
+ /**
40
+ * Daftar tab items yang akan dirender.
41
+ */
42
+ items: TabItem[];
43
+
44
+ /**
45
+ * Nilai tab yang sedang aktif (v-model / controlled value).
46
+ */
47
+ value?: string;
48
+
49
+ /**
50
+ * Nilai tab default saat pertama kali dirender (uncontrolled initial value).
51
+ */
52
+ defaultValue?: string;
53
+
54
+ /**
55
+ * Orientasi tata letak tab.
56
+ * - `'horizontal'`: Tab berjejer ke samping dengan garis indikator di bawah (standar).
57
+ * - `'vertical'`: Tab berjejer ke bawah dalam kolom dengan garis indikator di samping.
58
+ *
59
+ * @default 'horizontal'
60
+ */
61
+ orientation?: "horizontal" | "vertical";
62
+
63
+ /**
64
+ * Varian visual tampilan tab.
65
+ * - `'line'`: Garis indikator bawah/samping minimalis elegan (sesuai desain TTDK).
66
+ * - `'pills'`: Bentuk kapsul/tombol latar belakang penuh ala Shadcn / Nuxt UI.
67
+ *
68
+ * @default 'line'
69
+ */
70
+ variant?: "line" | "pills";
71
+
72
+ /**
73
+ * Ukuran proporsional tab (tinggi, padding, ukuran font).
74
+ * - `'sm'`: Kecil (12px font, compact padding).
75
+ * - `'md'`: Standar (14px font, regular padding).
76
+ * - `'lg'`: Besar (16px font, spacious padding).
77
+ *
78
+ * @default 'md'
79
+ */
80
+ size?: "sm" | "md" | "lg";
81
+
82
+ /**
83
+ * Membuat lebar setiap tab mengisi rata seluruh ruang container (full width / equal width).
84
+ *
85
+ * @default false
86
+ */
87
+ fitted?: boolean;
88
+
89
+ /**
90
+ * Sudut kelengkungan (khusus untuk varian 'pills').
91
+ * - `'none'`: Sudut 0px.
92
+ * - `'sm'`: Sudut 4px.
93
+ * - `'md'`: Sudut 6px.
94
+ * - `'lg'`: Sudut 8px.
95
+ * - `'full'`: Sudut 9999px.
96
+ *
97
+ * @default 'md'
98
+ */
99
+ rounded?: "none" | "sm" | "md" | "lg" | "full";
100
+
101
+ /**
102
+ * Hanya me-render konten panel tab saat tab tersebut aktif (lazy rendering).
103
+ * Berguna untuk menghemat performa saat tab memiliki konten berat.
104
+ * @default false
105
+ */
106
+ lazy?: boolean;
107
+
108
+ /**
109
+ * Inline CSS style object native.
110
+ */
111
+ style?: React.CSSProperties;
112
+
113
+ /**
114
+ * Kustomisasi visual granular root container (UiProps).
115
+ */
116
+ ui?: UiProps;
117
+
118
+ /**
119
+ * Style kustom untuk container tab list (UiProps).
120
+ */
121
+ listStyle?: UiProps;
122
+
123
+ /**
124
+ * Class CSS kustom untuk container tab list.
125
+ */
126
+ listClass?: string;
127
+
128
+ /**
129
+ * Style kustom untuk tombol tab item (UiProps).
130
+ */
131
+ tabStyle?: UiProps;
132
+
133
+ /**
134
+ * Class CSS kustom untuk tombol tab item.
135
+ */
136
+ tabClass?: string;
137
+
138
+ /**
139
+ * Style kustom untuk tombol tab item saat aktif (UiProps).
140
+ */
141
+ activeTabStyle?: UiProps;
142
+
143
+ /**
144
+ * Class CSS kustom untuk tombol tab item saat aktif.
145
+ */
146
+ activeTabClass?: string;
147
+
148
+ /**
149
+ * Style kustom untuk garis indikator tab (UiProps).
150
+ */
151
+ indicatorStyle?: UiProps;
152
+
153
+ /**
154
+ * Class CSS kustom untuk garis indikator tab.
155
+ */
156
+ indicatorClass?: string;
157
+
158
+ /**
159
+ * Style kustom untuk badge tab (UiProps).
160
+ */
161
+ badgeStyle?: UiProps;
162
+
163
+ /**
164
+ * Class CSS kustom untuk badge tab.
165
+ */
166
+ badgeClass?: string;
167
+
168
+ /**
169
+ * Style kustom untuk panel konten tab (UiProps).
170
+ */
171
+ panelStyle?: UiProps;
172
+
173
+ /**
174
+ * Class CSS kustom untuk panel konten tab.
175
+ */
176
+ panelClass?: string;
177
+
178
+ /**
179
+ * Callback event saat pengguna mengganti tab aktif.
180
+ *
181
+ * @param value Nilai/identifier tab yang dipilih.
182
+ */
183
+ onChange?: (value: string) => void;
184
+
185
+ /**
186
+ * Class CSS kustom.
187
+ */
188
+ className?: string;
189
+
190
+ /**
191
+ * Konten kustom atau slot anak.
192
+ */
193
+ children?: any;
194
+ }
195
+
196
+ /**
197
+ * Komponen Tabs untuk navigasi multi-panel dengan orientasi Horizontal & Vertikal, varian Line, Pills, dan Enclosed.
198
+ */
199
+
200
+ import { buildUiVars, UiProps } from "../../core/types";
201
+
202
+ function Tabs(props: TabsProps) {
203
+ const [activeVal, setActiveVal] = useState(
204
+ () =>
205
+ props.value ||
206
+ props.defaultValue ||
207
+ (props.items && props.items[0]?.value) ||
208
+ ""
209
+ );
210
+
211
+ function getCurrentActive() {
212
+ return props.value !== undefined ? props.value : activeVal;
213
+ }
214
+
215
+ function handleTabClick(itemValue: string, disabled?: boolean) {
216
+ if (disabled) return;
217
+ setActiveVal(itemValue);
218
+ if (props.onChange) {
219
+ props.onChange(itemValue);
220
+ }
221
+ }
222
+
223
+ function getActiveItem() {
224
+ const current = getCurrentActive();
225
+ return props.items
226
+ ? props.items.find((item: any) => item.value === current)
227
+ : null;
228
+ }
229
+
230
+ return (
231
+ <div
232
+ className={`ttdk-tabs ${props.className || ""}`.trim()}
233
+ data-orientation={props.orientation || "horizontal"}
234
+ data-variant={props.variant || "line"}
235
+ data-size={props.size || "md"}
236
+ data-fitted={props.fitted ? "true" : "false"}
237
+ data-rounded={props.rounded || "md"}
238
+ style={{
239
+ ...buildUiVars(props.ui),
240
+ ...((props as any)["style"] || {}),
241
+ ...props.style,
242
+ }}
243
+ >
244
+ <div
245
+ role="tablist"
246
+ className={`ttdk-tabs-list ${props.listClass || ""}`.trim()}
247
+ aria-orientation={props.orientation || "horizontal"}
248
+ style={{
249
+ ...buildUiVars(props.listStyle),
250
+ }}
251
+ >
252
+ {props.items?.map((item) => (
253
+ <button
254
+ type="button"
255
+ role="tab"
256
+ key={item.value}
257
+ className={`ttdk-tab-item ${props.tabClass || ""} ${
258
+ getCurrentActive() === item.value
259
+ ? props.activeTabClass || ""
260
+ : ""
261
+ }`.trim()}
262
+ data-state={
263
+ getCurrentActive() === item.value ? "active" : "inactive"
264
+ }
265
+ data-disabled={item.disabled ? "true" : "false"}
266
+ disabled={item.disabled}
267
+ aria-selected={getCurrentActive() === item.value}
268
+ onClick={(event) => handleTabClick(item.value, item.disabled)}
269
+ style={{
270
+ ...buildUiVars(props.tabStyle),
271
+ ...(getCurrentActive() === item.value
272
+ ? buildUiVars(props.activeTabStyle)
273
+ : {}),
274
+ }}
275
+ >
276
+ {item.icon ? (
277
+ <span className="ttdk-tab-icon">{item.icon}</span>
278
+ ) : null}
279
+ <span className="ttdk-tab-label">{item.label}</span>
280
+ {item.badge !== undefined && item.badge !== null ? (
281
+ <span
282
+ className={`ttdk-tab-badge ${props.badgeClass || ""}`.trim()}
283
+ style={{
284
+ ...buildUiVars(props.badgeStyle),
285
+ }}
286
+ >
287
+ {item.badge}
288
+ </span>
289
+ ) : null}
290
+ {getCurrentActive() === item.value &&
291
+ (!props.variant || props.variant === "line") ? (
292
+ <span
293
+ className={`ttdk-tab-indicator ${
294
+ props.indicatorClass || ""
295
+ }`.trim()}
296
+ style={{
297
+ ...buildUiVars(props.indicatorStyle),
298
+ }}
299
+ />
300
+ ) : null}
301
+ </button>
302
+ ))}
303
+ </div>
304
+ <div className="ttdk-tabs-panels">
305
+ {getActiveItem() && getActiveItem()?.content ? (
306
+ <>
307
+ {!props.lazy || getCurrentActive() === getActiveItem()?.value ? (
308
+ <div
309
+ role="tabpanel"
310
+ className={`ttdk-tab-panel ${props.panelClass || ""}`.trim()}
311
+ style={{
312
+ ...buildUiVars(props.panelStyle),
313
+ }}
314
+ >
315
+ {getActiveItem()?.content}
316
+ </div>
317
+ ) : null}
318
+ </>
319
+ ) : null}
320
+ {props.children ? (
321
+ <div className="ttdk-tab-children">
322
+ <>{props.children}</>
323
+ </div>
324
+ ) : null}
325
+ </div>
326
+ </div>
327
+ );
328
+ }
329
+
330
+ export default Tabs;
331
+
332
+ export { Tabs };