@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.
- package/AI_GUIDE.md +6518 -0
- package/llms.txt +6518 -0
- package/package.json +1098 -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.tsx +430 -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.tsx +318 -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.tsx +233 -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.tsx +140 -0
- package/src/web/badge/index.ts +2 -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.tsx +334 -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.tsx +306 -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.tsx +287 -0
- package/src/web/button/index.ts +2 -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.tsx +1611 -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.tsx +408 -0
- package/src/web/card/index.ts +4 -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.tsx +307 -0
- package/src/web/checkbox/index.ts +2 -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.tsx +230 -0
- package/src/web/chip/index.ts +2 -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.tsx +1274 -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.tsx +97 -0
- package/src/web/flex/flex.css +1 -0
- package/src/web/flex/flex.tsx +108 -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.tsx +112 -0
- package/src/web/grid/grid.css +1 -0
- package/src/web/grid/grid.tsx +99 -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.tsx +537 -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.tsx +1193 -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.tsx +571 -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.tsx +45 -0
- package/src/web/pagination/pagination-ellipsis.tsx +54 -0
- package/src/web/pagination/pagination-first.tsx +155 -0
- package/src/web/pagination/pagination-item.tsx +45 -0
- package/src/web/pagination/pagination-last.tsx +155 -0
- package/src/web/pagination/pagination-link.tsx +121 -0
- package/src/web/pagination/pagination-next.tsx +158 -0
- package/src/web/pagination/pagination-previous.tsx +158 -0
- package/src/web/pagination/pagination.css +1 -0
- package/src/web/pagination/pagination.tsx +687 -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.tsx +702 -0
- package/src/web/radio/AI_GUIDE.md +155 -0
- package/src/web/radio/index.ts +2 -0
- package/src/web/radio/manifest.json +294 -0
- package/src/web/radio/radio.css +1 -0
- package/src/web/radio/radio.tsx +290 -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.tsx +1048 -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.tsx +177 -0
- package/src/web/table/AI_GUIDE.md +145 -0
- package/src/web/table/index.ts +35 -0
- package/src/web/table/manifest.json +168 -0
- package/src/web/table/table-body.tsx +45 -0
- package/src/web/table/table-caption.tsx +45 -0
- package/src/web/table/table-cell.tsx +74 -0
- package/src/web/table/table-empty.tsx +108 -0
- package/src/web/table/table-footer.tsx +45 -0
- package/src/web/table/table-head.tsx +80 -0
- package/src/web/table/table-header.tsx +45 -0
- package/src/web/table/table-row.tsx +69 -0
- package/src/web/table/table.css +1 -0
- package/src/web/table/table.tsx +305 -0
- package/src/web/tabs/AI_GUIDE.md +150 -0
- package/src/web/tabs/index.ts +3 -0
- package/src/web/tabs/manifest.json +315 -0
- package/src/web/tabs/tabs.css +1 -0
- package/src/web/tabs/tabs.tsx +332 -0
- package/src/web/text/AI_GUIDE.md +129 -0
- package/src/web/text/index.ts +3 -0
- package/src/web/text/manifest.json +195 -0
- package/src/web/text/text.css +1 -0
- package/src/web/text/text.tsx +689 -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.tsx +467 -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.tsx +423 -0
- package/src/webview/bottomsheet/AI_GUIDE.md +141 -0
- package/src/webview/bottomsheet/bottomsheet.css +1 -0
- package/src/webview/bottomsheet/bottomsheet.tsx +603 -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.tsx +138 -0
- package/src/webview/button/button-material.css +1 -0
- package/src/webview/button/button-material.tsx +138 -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 @@
|
|
|
1
|
+
.ttdk-breadcrumb{box-sizing:border-box;font-family:var(--ttdk-font-sans);align-items:center;max-width:100%;line-height:1.5;display:flex}.ttdk-breadcrumb[data-size=sm]{font-size:var(--custom-text-size,12px)}.ttdk-breadcrumb[data-size=sm] .ttdk-breadcrumb-list,.ttdk-breadcrumb[data-size=sm] .ttdk-breadcrumb-item{gap:var(--custom-gap,6px)}.ttdk-breadcrumb[data-size=sm] .ttdk-separator-icon{width:12px;height:12px}.ttdk-breadcrumb[data-size=sm] .ttdk-breadcrumb-icon{font-size:13px}.ttdk-breadcrumb[data-size=md]{font-size:var(--custom-text-size,14px)}.ttdk-breadcrumb[data-size=md] .ttdk-breadcrumb-list,.ttdk-breadcrumb[data-size=md] .ttdk-breadcrumb-item{gap:var(--custom-gap,8px)}.ttdk-breadcrumb[data-size=md] .ttdk-separator-icon{width:14px;height:14px}.ttdk-breadcrumb[data-size=md] .ttdk-breadcrumb-icon{font-size:15px}.ttdk-breadcrumb[data-size=lg]{font-size:var(--custom-text-size,16px)}.ttdk-breadcrumb[data-size=lg] .ttdk-breadcrumb-list,.ttdk-breadcrumb[data-size=lg] .ttdk-breadcrumb-item{gap:var(--custom-gap,10px)}.ttdk-breadcrumb[data-size=lg] .ttdk-separator-icon{width:16px;height:16px}.ttdk-breadcrumb[data-size=lg] .ttdk-breadcrumb-icon{font-size:17px}.ttdk-breadcrumb[data-size=lg] .ttdk-breadcrumb-ellipsis{height:24px;padding:0 8px;font-size:15px}.ttdk-breadcrumb-list{flex-wrap:wrap;align-items:center;margin:0;padding:0;list-style:none;display:flex}.ttdk-breadcrumb-item{align-items:center;display:inline-flex}.ttdk-breadcrumb-link{color:var(--custom-link-color,var(--ttdk-ui-color,var(--ttdk-text-icon-secondary)));cursor:pointer;font-family:inherit;font-weight:400;font-size:inherit;line-height:inherit;background:0 0;border:none;align-items:center;gap:4px;padding:0;text-decoration:none;transition:color .15s,opacity .15s;display:inline-flex}.ttdk-breadcrumb-link:hover:not(:disabled){color:var(--ttdk-text-icon-primary);text-underline-offset:2px;text-decoration:underline}.ttdk-breadcrumb-link:focus-visible{box-shadow:0 0 0 2px var(--ttdk-stroke-border-focus);border-radius:2px;outline:none}.ttdk-breadcrumb-link:disabled,.ttdk-breadcrumb-link[data-disabled=true]{opacity:.45;cursor:not-allowed;pointer-events:none}.ttdk-breadcrumb-btn{user-select:none}.ttdk-breadcrumb-current{color:var(--custom-current-color,var(--ttdk-ui-color,var(--ttdk-text-icon-primary)));user-select:text;align-items:center;gap:4px;font-weight:600;display:inline-flex}.ttdk-breadcrumb-current[data-disabled=true]{opacity:.45}.ttdk-breadcrumb-separator{color:var(--ttdk-stroke-divider);user-select:none;justify-content:center;align-items:center;font-weight:400;display:inline-flex}.ttdk-separator-char{line-height:1}.ttdk-separator-icon{fill:currentColor;display:block;transform:rotate(270deg)}.ttdk-breadcrumb-label{vertical-align:middle;display:inline-block}.ttdk-breadcrumb-ellipsis{color:var(--ttdk-text-icon-primary);letter-spacing:1.5px;user-select:none;justify-content:center;align-items:center;font-weight:600;line-height:1;display:inline-flex}.ttdk-breadcrumb-slot{margin-left:12px}.dark .ttdk-breadcrumb-link,html.dark .ttdk-breadcrumb-link,[data-mode=dark] .ttdk-breadcrumb-link,[data-theme=dark] .ttdk-breadcrumb-link{color:var(--ttdk-text-icon-secondary)}.dark .ttdk-breadcrumb-link:hover:not(:disabled),html.dark .ttdk-breadcrumb-link:hover:not(:disabled),[data-mode=dark] .ttdk-breadcrumb-link:hover:not(:disabled),[data-theme=dark] .ttdk-breadcrumb-link:hover:not(:disabled),.dark .ttdk-breadcrumb-current,html.dark .ttdk-breadcrumb-current,[data-mode=dark] .ttdk-breadcrumb-current,[data-theme=dark] .ttdk-breadcrumb-current{color:var(--ttdk-text-icon-primary)}.dark .ttdk-breadcrumb-separator,html.dark .ttdk-breadcrumb-separator,[data-mode=dark] .ttdk-breadcrumb-separator,[data-theme=dark] .ttdk-breadcrumb-separator{color:var(--ttdk-stroke-divider)}.dark .ttdk-breadcrumb-ellipsis,html.dark .ttdk-breadcrumb-ellipsis,[data-mode=dark] .ttdk-breadcrumb-ellipsis,[data-theme=dark] .ttdk-breadcrumb-ellipsis{color:var(--ttdk-text-icon-primary)}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
export interface BreadcrumbItem {
|
|
5
|
+
/**
|
|
6
|
+
* Label teks yang ditampilkan pada breadcrumb item.
|
|
7
|
+
*/
|
|
8
|
+
label: string;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* URL tautan navigasi tujuan (href / path).
|
|
12
|
+
*/
|
|
13
|
+
href?: string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Ikon atau emoji opsional di samping label teks.
|
|
17
|
+
*/
|
|
18
|
+
icon?: any;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Menandai item ini secara eksplisit sebagai halaman saat ini (current page).
|
|
22
|
+
*/
|
|
23
|
+
current?: boolean;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Menonaktifkan interaksi klik pada item ini.
|
|
27
|
+
* @default false
|
|
28
|
+
*/
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Menandai item sebagai representasi ellipsis penyingkat item yang tersembunyi.
|
|
33
|
+
*/
|
|
34
|
+
isEllipsis?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export type BreadcrumbUiProps = UiProps;
|
|
37
|
+
export type BreadcrumbSeparator = "chevron";
|
|
38
|
+
export interface BreadcrumbProps {
|
|
39
|
+
/**
|
|
40
|
+
* Daftar item navigasi breadcrumb dari tingkat teratas hingga halaman saat ini.
|
|
41
|
+
*/
|
|
42
|
+
items: BreadcrumbItem[];
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Jenis karakter atau ikon pemisah antar item:
|
|
46
|
+
* - `'arrow'`: Ikon panah kanan `→`.
|
|
47
|
+
*
|
|
48
|
+
* @default 'arrow'
|
|
49
|
+
*/
|
|
50
|
+
separator?: BreadcrumbSeparator;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Karakter atau string teks khusus untuk pemisah antarelemen.
|
|
54
|
+
*/
|
|
55
|
+
customSeparator?: string;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Skala ukuran tampilan breadcrumb:
|
|
59
|
+
* - `'sm'`: Kompak (12px, gap 6px).
|
|
60
|
+
* - `'md'`: Standar (14px, gap 8px).
|
|
61
|
+
* - `'lg'`: Besar (16px, gap 12px).
|
|
62
|
+
*
|
|
63
|
+
* @default 'md'
|
|
64
|
+
*/
|
|
65
|
+
size?: "sm" | "md" | "lg";
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Batas maksimum panjang karakter label sebelum dipotong (truncate).
|
|
69
|
+
* @default 20
|
|
70
|
+
*/
|
|
71
|
+
maxLabelLength?: number;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Kustomisasi visual granular melalui style-like object (+ support pseudo-class: hover, active).
|
|
75
|
+
*/
|
|
76
|
+
ui?: UiProps;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Inline CSS style object native.
|
|
80
|
+
*/
|
|
81
|
+
style?: React.CSSProperties;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Style kustom untuk teks tautan breadcrumb / item non-aktif (UiProps).
|
|
85
|
+
*/
|
|
86
|
+
textLinkStyle?: UiProps;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Class CSS kustom untuk teks tautan breadcrumb / item non-aktif (Tailwind / class kustom).
|
|
90
|
+
*/
|
|
91
|
+
textLinkClass?: string;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Style kustom untuk teks item aktif / halaman saat ini (UiProps).
|
|
95
|
+
*/
|
|
96
|
+
textActiveStyle?: UiProps;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Class CSS kustom untuk teks item aktif / halaman saat ini (Tailwind / class kustom).
|
|
100
|
+
*/
|
|
101
|
+
textActiveClass?: string;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Style kustom untuk karakter atau ikon pemisah (UiProps).
|
|
105
|
+
*/
|
|
106
|
+
separatorStyle?: UiProps;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Class CSS kustom untuk karakter atau ikon pemisah (Tailwind / class kustom).
|
|
110
|
+
*/
|
|
111
|
+
separatorClass?: string;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Handler event klik yang dipanggil saat suatu item breadcrumb diklik.
|
|
115
|
+
*
|
|
116
|
+
* @param item Data item yang diklik.
|
|
117
|
+
* @param index Indeks urutan item dalam array.
|
|
118
|
+
* @param event Objek synthetic DOM event.
|
|
119
|
+
*/
|
|
120
|
+
onItemClick?: (item: BreadcrumbItem, index: number, event: any) => void;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Class CSS kustom.
|
|
124
|
+
*/
|
|
125
|
+
className?: string;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Slot konten tambahan di ujung kanan breadcrumb.
|
|
129
|
+
*/
|
|
130
|
+
children?: any;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Komponen Breadcrumb untuk menampilkan hierarki navigasi lokasi halaman saat ini dengan dukungan penyingkatan (collapse ellipsis).
|
|
135
|
+
*/
|
|
136
|
+
|
|
137
|
+
import { buildUiVars, UiProps } from "../../core/types";
|
|
138
|
+
|
|
139
|
+
function Breadcrumb(props: BreadcrumbProps) {
|
|
140
|
+
function formatLabel(label: any) {
|
|
141
|
+
if (!label && label !== 0) return "";
|
|
142
|
+
const str = String(label);
|
|
143
|
+
const max = props.maxLabelLength !== undefined ? props.maxLabelLength : 20;
|
|
144
|
+
if (max > 0 && str.length > max) {
|
|
145
|
+
return `${str.slice(0, max)}...`;
|
|
146
|
+
}
|
|
147
|
+
return str;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function getVisibleItems() {
|
|
151
|
+
if (!props.items || props.items.length === 0) return [];
|
|
152
|
+
const total = props.items.length;
|
|
153
|
+
if (total <= 3) {
|
|
154
|
+
return props.items;
|
|
155
|
+
}
|
|
156
|
+
const before = Math.max(1, Math.min(3, total - 2));
|
|
157
|
+
const startItems = props.items.slice(0, before);
|
|
158
|
+
const endItems = props.items.slice(total - 1);
|
|
159
|
+
const ellipsisItem: BreadcrumbItem = {
|
|
160
|
+
label: "...",
|
|
161
|
+
isEllipsis: true,
|
|
162
|
+
};
|
|
163
|
+
return [...startItems, ellipsisItem, ...endItems];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<nav
|
|
168
|
+
aria-label="Breadcrumb"
|
|
169
|
+
className={`ttdk-breadcrumb ${props.className || ""}`.trim()}
|
|
170
|
+
data-size={props.size || "md"}
|
|
171
|
+
style={{
|
|
172
|
+
...buildUiVars(props.ui),
|
|
173
|
+
...((props as any)["style"] || {}),
|
|
174
|
+
...props.style,
|
|
175
|
+
}}
|
|
176
|
+
>
|
|
177
|
+
<ol className="ttdk-breadcrumb-list">
|
|
178
|
+
{getVisibleItems()?.map((item, index) => (
|
|
179
|
+
<li className="ttdk-breadcrumb-item" key={index}>
|
|
180
|
+
{item.isEllipsis ? (
|
|
181
|
+
<span className="ttdk-breadcrumb-ellipsis" aria-hidden="true">
|
|
182
|
+
···
|
|
183
|
+
</span>
|
|
184
|
+
) : null}
|
|
185
|
+
{!item.isEllipsis &&
|
|
186
|
+
(item.current || index === getVisibleItems().length - 1) ? (
|
|
187
|
+
<span
|
|
188
|
+
aria-current="page"
|
|
189
|
+
className={`ttdk-breadcrumb-current ${
|
|
190
|
+
props.textActiveClass || ""
|
|
191
|
+
}`.trim()}
|
|
192
|
+
data-disabled={item.disabled ? "true" : "false"}
|
|
193
|
+
style={{
|
|
194
|
+
...buildUiVars(props.textActiveStyle),
|
|
195
|
+
}}
|
|
196
|
+
>
|
|
197
|
+
{item.icon ? (
|
|
198
|
+
<span className="ttdk-breadcrumb-icon">{item.icon}</span>
|
|
199
|
+
) : null}
|
|
200
|
+
<span className="ttdk-breadcrumb-label" title={item.label}>
|
|
201
|
+
{formatLabel(item.label)}
|
|
202
|
+
</span>
|
|
203
|
+
</span>
|
|
204
|
+
) : null}
|
|
205
|
+
{!item.isEllipsis &&
|
|
206
|
+
!item.current &&
|
|
207
|
+
index !== getVisibleItems().length - 1 ? (
|
|
208
|
+
<>
|
|
209
|
+
{item.href ? (
|
|
210
|
+
<a
|
|
211
|
+
href={item.href}
|
|
212
|
+
className={`ttdk-breadcrumb-link ${
|
|
213
|
+
props.textLinkClass || ""
|
|
214
|
+
}`.trim()}
|
|
215
|
+
data-disabled={item.disabled ? "true" : "false"}
|
|
216
|
+
title={item.label}
|
|
217
|
+
style={{
|
|
218
|
+
...buildUiVars(props.textLinkStyle),
|
|
219
|
+
}}
|
|
220
|
+
onClick={(e) => {
|
|
221
|
+
if (item.disabled) {
|
|
222
|
+
e.preventDefault();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (props.onItemClick) {
|
|
226
|
+
props.onItemClick(item, index, e);
|
|
227
|
+
}
|
|
228
|
+
}}
|
|
229
|
+
>
|
|
230
|
+
{item.icon ? (
|
|
231
|
+
<span className="ttdk-breadcrumb-icon">{item.icon}</span>
|
|
232
|
+
) : null}
|
|
233
|
+
<span className="ttdk-breadcrumb-label">
|
|
234
|
+
{formatLabel(item.label)}
|
|
235
|
+
</span>
|
|
236
|
+
</a>
|
|
237
|
+
) : null}
|
|
238
|
+
{!item.href ? (
|
|
239
|
+
<button
|
|
240
|
+
type="button"
|
|
241
|
+
className={`ttdk-breadcrumb-link ttdk-breadcrumb-btn ${
|
|
242
|
+
props.textLinkClass || ""
|
|
243
|
+
}`.trim()}
|
|
244
|
+
data-disabled={item.disabled ? "true" : "false"}
|
|
245
|
+
disabled={item.disabled}
|
|
246
|
+
title={item.label}
|
|
247
|
+
style={{
|
|
248
|
+
...buildUiVars(props.textLinkStyle),
|
|
249
|
+
}}
|
|
250
|
+
onClick={(e) => {
|
|
251
|
+
if (props.onItemClick) {
|
|
252
|
+
props.onItemClick(item, index, e);
|
|
253
|
+
}
|
|
254
|
+
}}
|
|
255
|
+
>
|
|
256
|
+
{item.icon ? (
|
|
257
|
+
<span className="ttdk-breadcrumb-icon">{item.icon}</span>
|
|
258
|
+
) : null}
|
|
259
|
+
<span className="ttdk-breadcrumb-label">
|
|
260
|
+
{formatLabel(item.label)}
|
|
261
|
+
</span>
|
|
262
|
+
</button>
|
|
263
|
+
) : null}
|
|
264
|
+
</>
|
|
265
|
+
) : null}
|
|
266
|
+
{index !== getVisibleItems().length - 1 ? (
|
|
267
|
+
<span
|
|
268
|
+
aria-hidden="true"
|
|
269
|
+
className={`ttdk-breadcrumb-separator ${
|
|
270
|
+
props.separatorClass || ""
|
|
271
|
+
}`.trim()}
|
|
272
|
+
style={{
|
|
273
|
+
...buildUiVars(props.separatorStyle),
|
|
274
|
+
}}
|
|
275
|
+
>
|
|
276
|
+
{props.customSeparator ? (
|
|
277
|
+
<span className="ttdk-separator-char">
|
|
278
|
+
{props.customSeparator}
|
|
279
|
+
</span>
|
|
280
|
+
) : null}
|
|
281
|
+
{!props.customSeparator ? (
|
|
282
|
+
<svg
|
|
283
|
+
className="ttdk-separator-icon"
|
|
284
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
285
|
+
viewBox="0 0 640 640"
|
|
286
|
+
>
|
|
287
|
+
<path d="M297.4 470.6C309.9 483.1 330.2 483.1 342.7 470.6L534.7 278.6C547.2 266.1 547.2 245.8 534.7 233.3C522.2 220.8 501.9 220.8 489.4 233.3L320 402.7L150.6 233.4C138.1 220.9 117.8 220.9 105.3 233.4C92.8 245.9 92.8 266.2 105.3 278.7L297.3 470.7z" />
|
|
288
|
+
</svg>
|
|
289
|
+
) : null}
|
|
290
|
+
</span>
|
|
291
|
+
) : null}
|
|
292
|
+
</li>
|
|
293
|
+
))}
|
|
294
|
+
</ol>
|
|
295
|
+
{props.children ? (
|
|
296
|
+
<div className="ttdk-breadcrumb-slot">
|
|
297
|
+
<>{props.children}</>
|
|
298
|
+
</div>
|
|
299
|
+
) : null}
|
|
300
|
+
</nav>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export default Breadcrumb;
|
|
305
|
+
|
|
306
|
+
export { Breadcrumb };
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ttdk-design-system/breadcrumb",
|
|
3
|
+
"component": "Breadcrumb",
|
|
4
|
+
"folder": "breadcrumb",
|
|
5
|
+
"description": "Komponen Breadcrumb untuk menampilkan hierarki navigasi lokasi halaman saat ini dengan dukungan penyingkatan (collapse ellipsis).",
|
|
6
|
+
"stylesheets": [
|
|
7
|
+
{
|
|
8
|
+
"theme": "common",
|
|
9
|
+
"file": "breadcrumb.css",
|
|
10
|
+
"importPath": "web/breadcrumb/breadcrumb.css"
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"props": {
|
|
14
|
+
"items": {
|
|
15
|
+
"name": "items",
|
|
16
|
+
"type": "BreadcrumbItem[]",
|
|
17
|
+
"optional": false,
|
|
18
|
+
"default": null,
|
|
19
|
+
"description": "Daftar item navigasi breadcrumb dari tingkat teratas hingga halaman saat ini.",
|
|
20
|
+
"example": null,
|
|
21
|
+
"param": null
|
|
22
|
+
},
|
|
23
|
+
"separator": {
|
|
24
|
+
"name": "separator",
|
|
25
|
+
"type": "BreadcrumbSeparator",
|
|
26
|
+
"optional": true,
|
|
27
|
+
"default": "arrow",
|
|
28
|
+
"description": "Jenis karakter atau ikon pemisah antar item: - `'arrow'`: Ikon panah kanan `→`.",
|
|
29
|
+
"example": null,
|
|
30
|
+
"param": null
|
|
31
|
+
},
|
|
32
|
+
"customSeparator": {
|
|
33
|
+
"name": "customSeparator",
|
|
34
|
+
"type": "string",
|
|
35
|
+
"optional": true,
|
|
36
|
+
"default": null,
|
|
37
|
+
"description": "Karakter atau string teks khusus untuk pemisah antarelemen.",
|
|
38
|
+
"example": null,
|
|
39
|
+
"param": null
|
|
40
|
+
},
|
|
41
|
+
"size": {
|
|
42
|
+
"name": "size",
|
|
43
|
+
"type": "'sm' | 'md' | 'lg'",
|
|
44
|
+
"optional": true,
|
|
45
|
+
"default": "md",
|
|
46
|
+
"description": "Skala ukuran tampilan breadcrumb: - `'sm'`: Kompak (12px, gap 6px). - `'md'`: Standar (14px, gap 8px). - `'lg'`: Besar (16px, gap 12px).",
|
|
47
|
+
"example": null,
|
|
48
|
+
"param": null
|
|
49
|
+
},
|
|
50
|
+
"maxLabelLength": {
|
|
51
|
+
"name": "maxLabelLength",
|
|
52
|
+
"type": "number",
|
|
53
|
+
"optional": true,
|
|
54
|
+
"default": "20",
|
|
55
|
+
"description": "Batas maksimum panjang karakter label sebelum dipotong (truncate).",
|
|
56
|
+
"example": null,
|
|
57
|
+
"param": null
|
|
58
|
+
},
|
|
59
|
+
"ui": {
|
|
60
|
+
"name": "ui",
|
|
61
|
+
"type": "UiProps",
|
|
62
|
+
"optional": true,
|
|
63
|
+
"default": null,
|
|
64
|
+
"description": "Kustomisasi visual granular melalui style-like object (+ support pseudo-class: hover, active).",
|
|
65
|
+
"example": null,
|
|
66
|
+
"param": null
|
|
67
|
+
},
|
|
68
|
+
"style": {
|
|
69
|
+
"name": "style",
|
|
70
|
+
"type": "any",
|
|
71
|
+
"optional": true,
|
|
72
|
+
"default": null,
|
|
73
|
+
"description": "Inline CSS style object native.",
|
|
74
|
+
"example": null,
|
|
75
|
+
"param": null
|
|
76
|
+
},
|
|
77
|
+
"textLinkStyle": {
|
|
78
|
+
"name": "textLinkStyle",
|
|
79
|
+
"type": "UiProps",
|
|
80
|
+
"optional": true,
|
|
81
|
+
"default": null,
|
|
82
|
+
"description": "Style kustom untuk teks tautan breadcrumb / item non-aktif (UiProps).",
|
|
83
|
+
"example": null,
|
|
84
|
+
"param": null
|
|
85
|
+
},
|
|
86
|
+
"textLinkClass": {
|
|
87
|
+
"name": "textLinkClass",
|
|
88
|
+
"type": "string",
|
|
89
|
+
"optional": true,
|
|
90
|
+
"default": null,
|
|
91
|
+
"description": "Class CSS kustom untuk teks tautan breadcrumb / item non-aktif (Tailwind / class kustom).",
|
|
92
|
+
"example": null,
|
|
93
|
+
"param": null
|
|
94
|
+
},
|
|
95
|
+
"textActiveStyle": {
|
|
96
|
+
"name": "textActiveStyle",
|
|
97
|
+
"type": "UiProps",
|
|
98
|
+
"optional": true,
|
|
99
|
+
"default": null,
|
|
100
|
+
"description": "Style kustom untuk teks item aktif / halaman saat ini (UiProps).",
|
|
101
|
+
"example": null,
|
|
102
|
+
"param": null
|
|
103
|
+
},
|
|
104
|
+
"textActiveClass": {
|
|
105
|
+
"name": "textActiveClass",
|
|
106
|
+
"type": "string",
|
|
107
|
+
"optional": true,
|
|
108
|
+
"default": null,
|
|
109
|
+
"description": "Class CSS kustom untuk teks item aktif / halaman saat ini (Tailwind / class kustom).",
|
|
110
|
+
"example": null,
|
|
111
|
+
"param": null
|
|
112
|
+
},
|
|
113
|
+
"separatorStyle": {
|
|
114
|
+
"name": "separatorStyle",
|
|
115
|
+
"type": "UiProps",
|
|
116
|
+
"optional": true,
|
|
117
|
+
"default": null,
|
|
118
|
+
"description": "Style kustom untuk karakter atau ikon pemisah (UiProps).",
|
|
119
|
+
"example": null,
|
|
120
|
+
"param": null
|
|
121
|
+
},
|
|
122
|
+
"separatorClass": {
|
|
123
|
+
"name": "separatorClass",
|
|
124
|
+
"type": "string",
|
|
125
|
+
"optional": true,
|
|
126
|
+
"default": null,
|
|
127
|
+
"description": "Class CSS kustom untuk karakter atau ikon pemisah (Tailwind / class kustom).",
|
|
128
|
+
"example": null,
|
|
129
|
+
"param": null
|
|
130
|
+
},
|
|
131
|
+
"onItemClick": {
|
|
132
|
+
"name": "onItemClick",
|
|
133
|
+
"type": "(item: BreadcrumbItem, index: number, event: any) => void",
|
|
134
|
+
"optional": true,
|
|
135
|
+
"default": null,
|
|
136
|
+
"description": "Handler event klik yang dipanggil saat suatu item breadcrumb diklik.",
|
|
137
|
+
"example": null,
|
|
138
|
+
"param": "event Objek synthetic DOM event."
|
|
139
|
+
},
|
|
140
|
+
"className": {
|
|
141
|
+
"name": "className",
|
|
142
|
+
"type": "string",
|
|
143
|
+
"optional": true,
|
|
144
|
+
"default": null,
|
|
145
|
+
"description": "Class CSS kustom.",
|
|
146
|
+
"example": null,
|
|
147
|
+
"param": null
|
|
148
|
+
},
|
|
149
|
+
"children": {
|
|
150
|
+
"name": "children",
|
|
151
|
+
"type": "any",
|
|
152
|
+
"optional": true,
|
|
153
|
+
"default": null,
|
|
154
|
+
"description": "Slot konten tambahan di ujung kanan breadcrumb.",
|
|
155
|
+
"example": null,
|
|
156
|
+
"param": null
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
"subInterfaces": {
|
|
160
|
+
"BreadcrumbItem": {
|
|
161
|
+
"label": {
|
|
162
|
+
"name": "label",
|
|
163
|
+
"type": "string",
|
|
164
|
+
"optional": false,
|
|
165
|
+
"default": null,
|
|
166
|
+
"description": "Label teks yang ditampilkan pada breadcrumb item.",
|
|
167
|
+
"example": null,
|
|
168
|
+
"param": null
|
|
169
|
+
},
|
|
170
|
+
"href": {
|
|
171
|
+
"name": "href",
|
|
172
|
+
"type": "string",
|
|
173
|
+
"optional": true,
|
|
174
|
+
"default": null,
|
|
175
|
+
"description": "URL tautan navigasi tujuan (href / path).",
|
|
176
|
+
"example": null,
|
|
177
|
+
"param": null
|
|
178
|
+
},
|
|
179
|
+
"icon": {
|
|
180
|
+
"name": "icon",
|
|
181
|
+
"type": "any",
|
|
182
|
+
"optional": true,
|
|
183
|
+
"default": null,
|
|
184
|
+
"description": "Ikon atau emoji opsional di samping label teks.",
|
|
185
|
+
"example": null,
|
|
186
|
+
"param": null
|
|
187
|
+
},
|
|
188
|
+
"current": {
|
|
189
|
+
"name": "current",
|
|
190
|
+
"type": "boolean",
|
|
191
|
+
"optional": true,
|
|
192
|
+
"default": null,
|
|
193
|
+
"description": "Menandai item ini secara eksplisit sebagai halaman saat ini (current page).",
|
|
194
|
+
"example": null,
|
|
195
|
+
"param": null
|
|
196
|
+
},
|
|
197
|
+
"disabled": {
|
|
198
|
+
"name": "disabled",
|
|
199
|
+
"type": "boolean",
|
|
200
|
+
"optional": true,
|
|
201
|
+
"default": "false",
|
|
202
|
+
"description": "Menonaktifkan interaksi klik pada item ini.",
|
|
203
|
+
"example": null,
|
|
204
|
+
"param": null
|
|
205
|
+
},
|
|
206
|
+
"isEllipsis": {
|
|
207
|
+
"name": "isEllipsis",
|
|
208
|
+
"type": "boolean",
|
|
209
|
+
"optional": true,
|
|
210
|
+
"default": null,
|
|
211
|
+
"description": "Menandai item sebagai representasi ellipsis penyingkat item yang tersembunyi.",
|
|
212
|
+
"example": null,
|
|
213
|
+
"param": null
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
"supportedFrameworks": [
|
|
218
|
+
"react",
|
|
219
|
+
"vue",
|
|
220
|
+
"svelte"
|
|
221
|
+
],
|
|
222
|
+
"themes": [
|
|
223
|
+
"tw"
|
|
224
|
+
]
|
|
225
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# <Button /> — TTDK Component Reference
|
|
2
|
+
|
|
3
|
+
TTDK Button component with multi-theme support.
|
|
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/button/button.css";`
|
|
8
|
+
|
|
9
|
+
## 2. Subpath Component Import
|
|
10
|
+
```ts
|
|
11
|
+
// React
|
|
12
|
+
import Button from "@ttdk-design-system/library-react/web/button";
|
|
13
|
+
|
|
14
|
+
// Vue 3
|
|
15
|
+
import Button from "@ttdk-design-system/library-vue/web/button";
|
|
16
|
+
|
|
17
|
+
// Svelte
|
|
18
|
+
import Button from "@ttdk-design-system/library-svelte/web/button";
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 3. Props & API Reference
|
|
22
|
+
|
|
23
|
+
| Prop | Type | Default | Description |
|
|
24
|
+
| :--- | :--- | :--- | :--- |
|
|
25
|
+
| `style?` | `any` | `-` | Inline CSS style kustom. |
|
|
26
|
+
| `variant?` | `| 'primary'
|
|
27
|
+
| 'info'
|
|
28
|
+
| 'warning'
|
|
29
|
+
| 'danger'
|
|
30
|
+
| 'neutral'
|
|
31
|
+
| 'disabled'` | `primary` | Varian warna semantik tombol: - `'primary'`: Teal utama. - `'info'`: Biru informasi. - `'warning'`: Kuning peringatan. - `'danger'`: Merah bahaya. - `'neutral'`: Abu-abu netral. - `'disabled'`: Tombol non-aktif. |
|
|
32
|
+
| `appearance?` | `'solid' | 'soft' | 'outline' | 'link' | 'ghost'` | `solid` | Gaya tampilan visual tombol: - `'solid'`: Latar belakang warna solid pekat (default). - `'soft'`: Latar belakang warna transparan lembut. - `'outline'`: Garis tepi dengan latar belakang transparan. - `'link'`: Teks tautan tanpa border/latar, dengan garis bawah saat hover. - `'ghost'`: Transparan tanpa outline/border, dengan latar lembut saat hover. |
|
|
33
|
+
| `type?` | `'button' | 'submit' | 'reset'` | `button` | Tipe tombol HTML native. |
|
|
34
|
+
| `size?` | `'sm' | 'md' | 'lg'` | `md` | Skala ukuran tombol: - `'sm'`: 36px - `'md'`: 48px (standar) - `'lg'`: 56px |
|
|
35
|
+
| `negative?` | `boolean` | `false` | Mode negatif — kontras tinggi di atas latar gelap. |
|
|
36
|
+
| `rounded?` | `'none' | 'sm' | 'md' | 'lg' | 'full'` | `md` | Radius sudut tombol. - `'none'`: 0px - `'sm'`: 4px - `'md'`: 8px (standar) - `'lg'`: 12px - `'full'`: 9999px (pill) |
|
|
37
|
+
| `iconOnly?` | `boolean` | `false` | Mode tombol ikon persegi (aspek rasio 1:1). |
|
|
38
|
+
| `block?` | `boolean` | `false` | Full-width — melebar 100% dari container. |
|
|
39
|
+
| `loading?` | `boolean` | `false` | Tampilkan spinner & nonaktifkan tombol saat proses async. |
|
|
40
|
+
| `disabled?` | `boolean` | `false` | Nonaktifkan tombol dari interaksi pengguna. |
|
|
41
|
+
| `iconColor?` | `string` | `-` | Warna kustom untuk ikon di dalam tombol (misal: '#fff', '#0968F6'). |
|
|
42
|
+
| `iconSize?` | `string` | `-` | Ukuran kustom untuk ikon di dalam tombol (misal: '16px', '20px'). |
|
|
43
|
+
| `ui?` | `UiProps` | `-` | Kustomisasi visual — seperti style object tapi dengan support pseudo-class. ```tsx <Button ui={{ background: '#7c3aed', color: 'white', borderRadius: '99px', hover: { background: '#6d28d9', transform: 'scale(1.02)', }, active: { opacity: '0.9', } }} /> ``` |
|
|
44
|
+
| `onClick?` | `(event?: any) => void` | `-` | Event handler saat tombol diklik. |
|
|
45
|
+
| `className?` | `string` | `-` | Class CSS kustom. |
|
|
46
|
+
| `children?` | `any` | `-` | Konten tombol — teks, ikon, atau JSX. |
|
|
47
|
+
|
|
48
|
+
## 4. Usage Examples
|
|
49
|
+
|
|
50
|
+
### React Example
|
|
51
|
+
```tsx
|
|
52
|
+
import Button from "@ttdk-design-system/library-react/web/button";
|
|
53
|
+
import "@ttdk-design-system/library-react/web/button/button.css";
|
|
54
|
+
|
|
55
|
+
export function Example() {
|
|
56
|
+
return (
|
|
57
|
+
<div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
|
|
58
|
+
<Button variant="primary" onClick={() => alert("Simpan data")}>
|
|
59
|
+
Simpan
|
|
60
|
+
</Button>
|
|
61
|
+
<Button variant="secondary">
|
|
62
|
+
Batal
|
|
63
|
+
</Button>
|
|
64
|
+
<Button variant="outline">
|
|
65
|
+
Ekspor CSV
|
|
66
|
+
</Button>
|
|
67
|
+
<Button variant="danger">
|
|
68
|
+
Hapus
|
|
69
|
+
</Button>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Vue 3 Example
|
|
76
|
+
```vue
|
|
77
|
+
<script setup lang="ts">
|
|
78
|
+
import Button from "@ttdk-design-system/library-vue/web/button";
|
|
79
|
+
import "@ttdk-design-system/library-vue/web/button/button.css";
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
<template>
|
|
83
|
+
<div style="display: flex; gap: 10px; align-items: center;">
|
|
84
|
+
<Button variant="primary" @click="() => alert('Simpan data')">
|
|
85
|
+
Simpan
|
|
86
|
+
</Button>
|
|
87
|
+
<Button variant="secondary">
|
|
88
|
+
Batal
|
|
89
|
+
</Button>
|
|
90
|
+
<Button variant="outline">
|
|
91
|
+
Ekspor CSV
|
|
92
|
+
</Button>
|
|
93
|
+
<Button variant="danger">
|
|
94
|
+
Hapus
|
|
95
|
+
</Button>
|
|
96
|
+
</div>
|
|
97
|
+
</template>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Svelte Example
|
|
101
|
+
```svelte
|
|
102
|
+
<script lang="ts">
|
|
103
|
+
import Button from "@ttdk-design-system/library-svelte/web/button";
|
|
104
|
+
import "@ttdk-design-system/library-svelte/web/button/button.css";
|
|
105
|
+
</script>
|
|
106
|
+
|
|
107
|
+
<div style="display: flex; gap: 10px; align-items: center;">
|
|
108
|
+
<Button variant="primary" on:click={() => alert('Simpan data')}>
|
|
109
|
+
Simpan
|
|
110
|
+
</Button>
|
|
111
|
+
<Button variant="secondary">
|
|
112
|
+
Batal
|
|
113
|
+
</Button>
|
|
114
|
+
<Button variant="outline">
|
|
115
|
+
Ekspor CSV
|
|
116
|
+
</Button>
|
|
117
|
+
<Button variant="danger">
|
|
118
|
+
Hapus
|
|
119
|
+
</Button>
|
|
120
|
+
</div>
|
|
121
|
+
```
|