xegavnj 0.1.8
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/README.md +36 -0
- package/components/AppFooter.jsx +294 -0
- package/components/AppHeader.jsx +407 -0
- package/components/AppSidebar.jsx +405 -0
- package/components/Button.jsx +244 -0
- package/components/Card.jsx +388 -0
- package/components/DemoSidebar.jsx +112 -0
- package/components/ThemeSwitcher.jsx +29 -0
- package/index.js +8 -0
- package/package.json +45 -0
- package/theme/ThemeContext.js +42 -0
- package/theme.js +23 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import styled, { css } from "styled-components";
|
|
4
|
+
import { useTheme } from "./ThemeSwitcher.jsx";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Play,
|
|
8
|
+
Square,
|
|
9
|
+
CreditCard,
|
|
10
|
+
PanelTop,
|
|
11
|
+
PanelBottom,
|
|
12
|
+
Sidebar as SidebarIcon,
|
|
13
|
+
Layout,
|
|
14
|
+
Copy,
|
|
15
|
+
Check,
|
|
16
|
+
Code as CodeIcon,
|
|
17
|
+
Home,
|
|
18
|
+
Settings,
|
|
19
|
+
User
|
|
20
|
+
} from "lucide-react";
|
|
21
|
+
|
|
22
|
+
// Base styles for the wrapper
|
|
23
|
+
const SidebarWrapper = styled.aside`
|
|
24
|
+
width: 250px;
|
|
25
|
+
min-height: 100vh;
|
|
26
|
+
background-color: ${({ theme }) => theme.background};
|
|
27
|
+
border-right: 1px solid ${({ theme }) => theme.border || "#e5e7eb"};
|
|
28
|
+
padding: 1.5rem 1rem;
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-direction: column;
|
|
31
|
+
transition: all 0.3s ease;
|
|
32
|
+
|
|
33
|
+
${({ variant, theme }) => variant === 'floating' && css`
|
|
34
|
+
min-height: auto;
|
|
35
|
+
height: calc(100vh - 2rem);
|
|
36
|
+
margin: 1rem;
|
|
37
|
+
border-radius: 16px;
|
|
38
|
+
border: 1px solid ${theme.border || "#e5e7eb"};
|
|
39
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.05);
|
|
40
|
+
`}
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
const SidebarTitle = styled.h2`
|
|
44
|
+
font-size: 0.85rem;
|
|
45
|
+
text-transform: uppercase;
|
|
46
|
+
letter-spacing: 0.05em;
|
|
47
|
+
font-weight: 700;
|
|
48
|
+
color: ${({ theme }) => theme.secondary || "#6b7280"};
|
|
49
|
+
margin-bottom: 1rem;
|
|
50
|
+
padding-left: 0.75rem;
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
const GroupTitle = styled.h3`
|
|
54
|
+
font-size: 0.8rem;
|
|
55
|
+
font-weight: 600;
|
|
56
|
+
color: ${({ theme }) => theme.primary || "#3b82f6"};
|
|
57
|
+
margin-top: 1.5rem;
|
|
58
|
+
margin-bottom: 0.5rem;
|
|
59
|
+
padding-left: 0.75rem;
|
|
60
|
+
opacity: 0.9;
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
const Menu = styled.ul`
|
|
64
|
+
list-style: none;
|
|
65
|
+
padding: 0;
|
|
66
|
+
margin: 0;
|
|
67
|
+
display: flex;
|
|
68
|
+
flex-direction: column;
|
|
69
|
+
gap: 0.25rem;
|
|
70
|
+
`;
|
|
71
|
+
|
|
72
|
+
const MenuItem = styled.li`
|
|
73
|
+
padding: 0.6rem 0.75rem;
|
|
74
|
+
border-radius: 6px;
|
|
75
|
+
font-size: 0.95rem;
|
|
76
|
+
color: ${({ theme }) => theme.text};
|
|
77
|
+
cursor: pointer;
|
|
78
|
+
display: flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
gap: 0.75rem;
|
|
81
|
+
transition: all 0.2s;
|
|
82
|
+
font-weight: 500;
|
|
83
|
+
position: relative;
|
|
84
|
+
|
|
85
|
+
&:hover {
|
|
86
|
+
background-color: ${({ theme }) => theme.border || "#e5e7eb"};
|
|
87
|
+
color: ${({ theme }) => theme.primary || "#3b82f6"};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
svg {
|
|
91
|
+
opacity: 0.7;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
&:hover svg {
|
|
95
|
+
opacity: 1;
|
|
96
|
+
}
|
|
97
|
+
`;
|
|
98
|
+
|
|
99
|
+
// Helper to render a list of items
|
|
100
|
+
const MenuList = ({ items, currentTheme, onMenuClick }) => (
|
|
101
|
+
<Menu>
|
|
102
|
+
{items.map((item, index) => {
|
|
103
|
+
const label = typeof item === 'string' ? item : item.label;
|
|
104
|
+
const Icon = item.icon || Layout;
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<MenuItem
|
|
108
|
+
key={index}
|
|
109
|
+
theme={currentTheme}
|
|
110
|
+
onClick={() => onMenuClick && onMenuClick(label)}
|
|
111
|
+
>
|
|
112
|
+
<Icon size={18} />
|
|
113
|
+
{label}
|
|
114
|
+
</MenuItem>
|
|
115
|
+
);
|
|
116
|
+
})}
|
|
117
|
+
</Menu>
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const defaultItems = [
|
|
121
|
+
{ label: "Get Started", icon: Play },
|
|
122
|
+
{ label: "Button", icon: Square },
|
|
123
|
+
{ label: "Card", icon: CreditCard },
|
|
124
|
+
{ label: "AppHeader", icon: PanelTop },
|
|
125
|
+
{ label: "AppFooter", icon: PanelBottom },
|
|
126
|
+
{ label: "AppSidebar", icon: SidebarIcon },
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
export default function AppSidebar({
|
|
130
|
+
title = "Documentation",
|
|
131
|
+
items = defaultItems,
|
|
132
|
+
groups = [], // Array of { title: string, items: [] }
|
|
133
|
+
variant = "default", // default | grouped | floating
|
|
134
|
+
onMenuClick,
|
|
135
|
+
}) {
|
|
136
|
+
const { theme: themeName } = useTheme();
|
|
137
|
+
|
|
138
|
+
// Use a fallback if theme context isn't available
|
|
139
|
+
// e.g. when used in isolation or passed explicitly
|
|
140
|
+
const themesLib = require("../theme.js").themes;
|
|
141
|
+
const currentTheme = themesLib[themeName] || themesLib.light;
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<SidebarWrapper theme={currentTheme} variant={variant}>
|
|
145
|
+
<SidebarTitle theme={currentTheme}>{title}</SidebarTitle>
|
|
146
|
+
|
|
147
|
+
{variant === 'grouped' && groups.length > 0 ? (
|
|
148
|
+
// Grouped Layout
|
|
149
|
+
groups.map((group, idx) => (
|
|
150
|
+
<div key={idx}>
|
|
151
|
+
<GroupTitle theme={currentTheme}>{group.title}</GroupTitle>
|
|
152
|
+
<MenuList
|
|
153
|
+
items={group.items}
|
|
154
|
+
currentTheme={currentTheme}
|
|
155
|
+
onMenuClick={onMenuClick}
|
|
156
|
+
/>
|
|
157
|
+
</div>
|
|
158
|
+
))
|
|
159
|
+
) : (
|
|
160
|
+
// Default Layout
|
|
161
|
+
<MenuList
|
|
162
|
+
items={items}
|
|
163
|
+
currentTheme={currentTheme}
|
|
164
|
+
onMenuClick={onMenuClick}
|
|
165
|
+
/>
|
|
166
|
+
)}
|
|
167
|
+
</SidebarWrapper>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// =============================================================================
|
|
172
|
+
// SHOWCASE & DOCUMENTATION WRAPPER
|
|
173
|
+
// =============================================================================
|
|
174
|
+
|
|
175
|
+
const ShowcaseContainer = styled.div`
|
|
176
|
+
width: 100%;
|
|
177
|
+
padding: 2rem;
|
|
178
|
+
display: flex;
|
|
179
|
+
flex-direction: column;
|
|
180
|
+
gap: 3rem;
|
|
181
|
+
background-color: #f8fafc;
|
|
182
|
+
min-height: 100vh;
|
|
183
|
+
`;
|
|
184
|
+
|
|
185
|
+
const Title = styled.h2`
|
|
186
|
+
text-align: center;
|
|
187
|
+
font-size: 2rem;
|
|
188
|
+
color: #1e293b;
|
|
189
|
+
margin-bottom: 2rem;
|
|
190
|
+
font-weight: 800;
|
|
191
|
+
`;
|
|
192
|
+
|
|
193
|
+
const ShowcaseBlock = styled.div`
|
|
194
|
+
background: white;
|
|
195
|
+
border-radius: 16px;
|
|
196
|
+
overflow: hidden;
|
|
197
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.05);
|
|
198
|
+
border: 1px solid #e2e8f0;
|
|
199
|
+
`;
|
|
200
|
+
|
|
201
|
+
const PreviewArea = styled.div`
|
|
202
|
+
width: 100%;
|
|
203
|
+
padding: 2rem;
|
|
204
|
+
display: flex;
|
|
205
|
+
justify-content: center;
|
|
206
|
+
align-items: flex-start;
|
|
207
|
+
background-color: #f3f4f6;
|
|
208
|
+
min-height: 500px;
|
|
209
|
+
overflow: hidden;
|
|
210
|
+
`;
|
|
211
|
+
|
|
212
|
+
const ActionSelect = styled.div`
|
|
213
|
+
display: flex;
|
|
214
|
+
justify-content: flex-end;
|
|
215
|
+
padding: 0.5rem;
|
|
216
|
+
background: #f1f5f9;
|
|
217
|
+
border-top: 1px solid #e2e8f0;
|
|
218
|
+
`;
|
|
219
|
+
|
|
220
|
+
const ActionButton = styled.button`
|
|
221
|
+
display: flex;
|
|
222
|
+
align-items: center;
|
|
223
|
+
gap: 0.5rem;
|
|
224
|
+
padding: 0.5rem 1rem;
|
|
225
|
+
border-radius: 6px;
|
|
226
|
+
border: 1px solid #cbd5e1;
|
|
227
|
+
background: white;
|
|
228
|
+
font-size: 0.85rem;
|
|
229
|
+
color: #475569;
|
|
230
|
+
cursor: pointer;
|
|
231
|
+
font-weight: 500;
|
|
232
|
+
transition: all 0.2s;
|
|
233
|
+
|
|
234
|
+
&:hover {
|
|
235
|
+
background: #f8fafc;
|
|
236
|
+
border-color: #94a3b8;
|
|
237
|
+
}
|
|
238
|
+
`;
|
|
239
|
+
|
|
240
|
+
const CodeBlock = styled.div`
|
|
241
|
+
background: #1e1e1e;
|
|
242
|
+
color: #d4d4d4;
|
|
243
|
+
padding: 1rem;
|
|
244
|
+
overflow-x: auto;
|
|
245
|
+
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
246
|
+
font-size: 0.85rem;
|
|
247
|
+
line-height: 1.5;
|
|
248
|
+
border-top: 1px solid #333;
|
|
249
|
+
`;
|
|
250
|
+
|
|
251
|
+
function SidebarVariantDisplay({ title, description, code, children }) {
|
|
252
|
+
const [showCode, setShowCode] = useState(false);
|
|
253
|
+
const [copied, setCopied] = useState(false);
|
|
254
|
+
|
|
255
|
+
const handleCopy = () => {
|
|
256
|
+
navigator.clipboard.writeText(code);
|
|
257
|
+
setCopied(true);
|
|
258
|
+
setTimeout(() => setCopied(false), 2000);
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
return (
|
|
262
|
+
<div>
|
|
263
|
+
<h3 style={{ textAlign: 'center', marginBottom: '0.5rem', color: '#1e293b', fontSize: '1.5rem', fontWeight: 700 }}>{title}</h3>
|
|
264
|
+
<p style={{ textAlign: 'center', marginBottom: '1.5rem', color: '#64748b' }}>{description}</p>
|
|
265
|
+
<ShowcaseBlock>
|
|
266
|
+
<PreviewArea>
|
|
267
|
+
<div style={{ transform: "scale(0.85)", height: '100%', maxHeight: '400px', border: "1px solid #e5e7eb", boxShadow: "0 10px 15px -3px rgba(0,0,0,0.1)", background: 'white' }}>
|
|
268
|
+
{children}
|
|
269
|
+
</div>
|
|
270
|
+
</PreviewArea>
|
|
271
|
+
<ActionSelect>
|
|
272
|
+
<div style={{ display: 'flex', gap: '0.5rem' }}>
|
|
273
|
+
<ActionButton onClick={() => setShowCode(!showCode)}>
|
|
274
|
+
<CodeIcon size={16} />
|
|
275
|
+
{showCode ? "Sembunyikan Kode" : "Lihat Kode"}
|
|
276
|
+
</ActionButton>
|
|
277
|
+
{showCode && (
|
|
278
|
+
<ActionButton onClick={handleCopy}>
|
|
279
|
+
{copied ? <Check size={16} /> : <Copy size={16} />}
|
|
280
|
+
{copied ? "Disalin!" : "Salin"}
|
|
281
|
+
</ActionButton>
|
|
282
|
+
)}
|
|
283
|
+
</div>
|
|
284
|
+
</ActionSelect>
|
|
285
|
+
{showCode && (
|
|
286
|
+
<CodeBlock>
|
|
287
|
+
<pre>{code}</pre>
|
|
288
|
+
</CodeBlock>
|
|
289
|
+
)}
|
|
290
|
+
</ShowcaseBlock>
|
|
291
|
+
</div>
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function SidebarShowcase() {
|
|
296
|
+
return (
|
|
297
|
+
<ShowcaseContainer>
|
|
298
|
+
<Title>Sidebar Variants</Title>
|
|
299
|
+
<p style={{ textAlign: 'center', color: '#666', marginBottom: '2rem' }}>
|
|
300
|
+
Komponen Sidebar dengan berbagai varian tata letak.
|
|
301
|
+
</p>
|
|
302
|
+
|
|
303
|
+
<SidebarVariantDisplay
|
|
304
|
+
title="1. Sidebar Default"
|
|
305
|
+
description="Navigasi daftar datar standar."
|
|
306
|
+
code={`import { AppSidebar } from "uts-ds";
|
|
307
|
+
import { Home, Settings } from "lucide-react";
|
|
308
|
+
|
|
309
|
+
export default function MyLayout() {
|
|
310
|
+
const items = [
|
|
311
|
+
{ label: "Home", icon: Home },
|
|
312
|
+
{ label: "Settings", icon: Settings }
|
|
313
|
+
];
|
|
314
|
+
|
|
315
|
+
return (
|
|
316
|
+
<AppSidebar
|
|
317
|
+
title="My App"
|
|
318
|
+
items={items}
|
|
319
|
+
onMenuClick={(label) => console.log(label)}
|
|
320
|
+
/>
|
|
321
|
+
);
|
|
322
|
+
}`}
|
|
323
|
+
>
|
|
324
|
+
<AppSidebar
|
|
325
|
+
title="My App"
|
|
326
|
+
items={[
|
|
327
|
+
{ label: "Home", icon: Home },
|
|
328
|
+
{ label: "Settings", icon: Settings }
|
|
329
|
+
]}
|
|
330
|
+
/>
|
|
331
|
+
</SidebarVariantDisplay>
|
|
332
|
+
|
|
333
|
+
<SidebarVariantDisplay
|
|
334
|
+
title="2. Sidebar Terkelompok"
|
|
335
|
+
description="Atur item ke dalam bagian berjudul menggunakan props groups."
|
|
336
|
+
code={`import { AppSidebar } from "uts-ds";
|
|
337
|
+
import { Play, Square, User, CreditCard } from "lucide-react";
|
|
338
|
+
|
|
339
|
+
export default function MyGroupedLayout() {
|
|
340
|
+
const groups = [
|
|
341
|
+
{
|
|
342
|
+
title: "Main",
|
|
343
|
+
items: [{ label: "Overview", icon: Square }, { label: "Analytics", icon: Play }]
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
title: "Settings",
|
|
347
|
+
items: [{ label: "Account", icon: User }, { label: "Billing", icon: CreditCard }]
|
|
348
|
+
}
|
|
349
|
+
];
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
<AppSidebar
|
|
353
|
+
variant="grouped"
|
|
354
|
+
title="Dashboard"
|
|
355
|
+
groups={groups}
|
|
356
|
+
/>
|
|
357
|
+
);
|
|
358
|
+
}`}
|
|
359
|
+
>
|
|
360
|
+
<AppSidebar
|
|
361
|
+
variant="grouped"
|
|
362
|
+
title="Dashboard"
|
|
363
|
+
groups={[
|
|
364
|
+
{ title: "Main", items: [{ label: "Overview", icon: Square }, { label: "Analytics", icon: Play }] },
|
|
365
|
+
{ title: "Settings", items: [{ label: "Account", icon: User }, { label: "Billing", icon: CreditCard }] }
|
|
366
|
+
]}
|
|
367
|
+
/>
|
|
368
|
+
</SidebarVariantDisplay>
|
|
369
|
+
|
|
370
|
+
<SidebarVariantDisplay
|
|
371
|
+
title="3. Sidebar Melayang"
|
|
372
|
+
description="Gaya mengambang yang terpisah, cocok untuk dasbor modern."
|
|
373
|
+
code={`import { AppSidebar } from "uts-ds";
|
|
374
|
+
import { Square, Play, User } from "lucide-react";
|
|
375
|
+
|
|
376
|
+
export default function MyFloatingLayout() {
|
|
377
|
+
return (
|
|
378
|
+
<div style={{ background: "#f3f4f6", height: "100vh", padding: "1rem" }}>
|
|
379
|
+
<AppSidebar
|
|
380
|
+
variant="floating"
|
|
381
|
+
title="App"
|
|
382
|
+
items={[
|
|
383
|
+
{ label: "Dashboard", icon: Square },
|
|
384
|
+
{ label: "Messages", icon: Play },
|
|
385
|
+
{ label: "Profile", icon: User }
|
|
386
|
+
]}
|
|
387
|
+
/>
|
|
388
|
+
</div>
|
|
389
|
+
);
|
|
390
|
+
}`}
|
|
391
|
+
>
|
|
392
|
+
<AppSidebar
|
|
393
|
+
variant="floating"
|
|
394
|
+
title="App"
|
|
395
|
+
items={[
|
|
396
|
+
{ label: "Dashboard", icon: Square },
|
|
397
|
+
{ label: "Messages", icon: Play },
|
|
398
|
+
{ label: "Profile", icon: User }
|
|
399
|
+
]}
|
|
400
|
+
/>
|
|
401
|
+
</SidebarVariantDisplay>
|
|
402
|
+
|
|
403
|
+
</ShowcaseContainer>
|
|
404
|
+
);
|
|
405
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import styled from "styled-components";
|
|
4
|
+
import { Copy, Check, Code as CodeIcon, Save, Trash2, Edit } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
// =============================================================================
|
|
7
|
+
// REUSABLE BUTTON COMPONENT
|
|
8
|
+
// =============================================================================
|
|
9
|
+
|
|
10
|
+
const StyledButton = styled.button`
|
|
11
|
+
padding: 12px 24px;
|
|
12
|
+
background-color: ${(props) => props.$bg || "#4f46e5"};
|
|
13
|
+
color: ${(props) => props.$color || "white"};
|
|
14
|
+
border: ${(props) => props.$border || "none"};
|
|
15
|
+
border-radius: ${(props) => props.$radius || "8px"};
|
|
16
|
+
font-weight: 600;
|
|
17
|
+
cursor: pointer;
|
|
18
|
+
display: inline-flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
gap: 0.5rem;
|
|
21
|
+
width: fit-content;
|
|
22
|
+
transition: all 0.2s ease;
|
|
23
|
+
font-size: 0.95rem;
|
|
24
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
25
|
+
|
|
26
|
+
&:hover {
|
|
27
|
+
background-color: ${(props) => props.$hoverBg || props.$bg};
|
|
28
|
+
filter: brightness(110%);
|
|
29
|
+
transform: translateY(-1px);
|
|
30
|
+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&:active {
|
|
34
|
+
transform: translateY(0);
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
export function Button({
|
|
39
|
+
label,
|
|
40
|
+
children,
|
|
41
|
+
onClick,
|
|
42
|
+
bg,
|
|
43
|
+
hoverBg,
|
|
44
|
+
color,
|
|
45
|
+
border,
|
|
46
|
+
radius,
|
|
47
|
+
icon: Icon
|
|
48
|
+
}) {
|
|
49
|
+
return (
|
|
50
|
+
<StyledButton
|
|
51
|
+
onClick={onClick}
|
|
52
|
+
$bg={bg}
|
|
53
|
+
$hoverBg={hoverBg}
|
|
54
|
+
$color={color}
|
|
55
|
+
$border={border}
|
|
56
|
+
$radius={radius}
|
|
57
|
+
>
|
|
58
|
+
{Icon && <Icon size={18} />}
|
|
59
|
+
{label || children}
|
|
60
|
+
</StyledButton>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// =============================================================================
|
|
65
|
+
// SHOWCASE & DOCUMENTATION WRAPPER
|
|
66
|
+
// =============================================================================
|
|
67
|
+
|
|
68
|
+
const ShowcaseContainer = styled.div`
|
|
69
|
+
width: 100%;
|
|
70
|
+
padding: 2rem;
|
|
71
|
+
display: flex;
|
|
72
|
+
flex-direction: column;
|
|
73
|
+
gap: 3rem;
|
|
74
|
+
background-color: #f8fafc;
|
|
75
|
+
min-height: 100vh;
|
|
76
|
+
`;
|
|
77
|
+
|
|
78
|
+
const Title = styled.h2`
|
|
79
|
+
text-align: center;
|
|
80
|
+
font-size: 2rem;
|
|
81
|
+
color: #1e293b;
|
|
82
|
+
margin-bottom: 2rem;
|
|
83
|
+
font-weight: 800;
|
|
84
|
+
`;
|
|
85
|
+
|
|
86
|
+
const ShowcaseBlock = styled.div`
|
|
87
|
+
background: white;
|
|
88
|
+
border-radius: 16px;
|
|
89
|
+
overflow: hidden;
|
|
90
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.05);
|
|
91
|
+
border: 1px solid #e2e8f0;
|
|
92
|
+
`;
|
|
93
|
+
|
|
94
|
+
const PreviewArea = styled.div`
|
|
95
|
+
width: 100%;
|
|
96
|
+
padding: 2rem;
|
|
97
|
+
background: #fff;
|
|
98
|
+
display: flex;
|
|
99
|
+
justify-content: center;
|
|
100
|
+
align-items: center;
|
|
101
|
+
gap: 1rem;
|
|
102
|
+
flex-wrap: wrap;
|
|
103
|
+
`;
|
|
104
|
+
|
|
105
|
+
const ActionSelect = styled.div`
|
|
106
|
+
display: flex;
|
|
107
|
+
justify-content: flex-end;
|
|
108
|
+
padding: 0.5rem;
|
|
109
|
+
background: #f1f5f9;
|
|
110
|
+
border-top: 1px solid #e2e8f0;
|
|
111
|
+
`;
|
|
112
|
+
|
|
113
|
+
const ActionButton = styled.button`
|
|
114
|
+
display: flex;
|
|
115
|
+
align-items: center;
|
|
116
|
+
gap: 0.5rem;
|
|
117
|
+
padding: 0.5rem 1rem;
|
|
118
|
+
border-radius: 6px;
|
|
119
|
+
border: 1px solid #cbd5e1;
|
|
120
|
+
background: white;
|
|
121
|
+
font-size: 0.85rem;
|
|
122
|
+
color: #475569;
|
|
123
|
+
cursor: pointer;
|
|
124
|
+
font-weight: 500;
|
|
125
|
+
transition: all 0.2s;
|
|
126
|
+
|
|
127
|
+
&:hover {
|
|
128
|
+
background: #f8fafc;
|
|
129
|
+
border-color: #94a3b8;
|
|
130
|
+
}
|
|
131
|
+
`;
|
|
132
|
+
|
|
133
|
+
const CodeBlock = styled.div`
|
|
134
|
+
background: #1e1e1e;
|
|
135
|
+
color: #d4d4d4;
|
|
136
|
+
padding: 1rem;
|
|
137
|
+
overflow-x: auto;
|
|
138
|
+
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
139
|
+
font-size: 0.85rem;
|
|
140
|
+
line-height: 1.5;
|
|
141
|
+
border-top: 1px solid #333;
|
|
142
|
+
`;
|
|
143
|
+
|
|
144
|
+
function ButtonVariantDisplay({ title, code, children }) {
|
|
145
|
+
const [showCode, setShowCode] = useState(false);
|
|
146
|
+
const [copied, setCopied] = useState(false);
|
|
147
|
+
|
|
148
|
+
const handleCopy = () => {
|
|
149
|
+
navigator.clipboard.writeText(code);
|
|
150
|
+
setCopied(true);
|
|
151
|
+
setTimeout(() => setCopied(false), 2000);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<div>
|
|
156
|
+
<h3 style={{ textAlign: 'center', marginBottom: '1rem', color: '#64748b', textTransform: 'uppercase', letterSpacing: '0.1em', fontSize: '1rem' }}>{title}</h3>
|
|
157
|
+
<ShowcaseBlock>
|
|
158
|
+
<PreviewArea>
|
|
159
|
+
{children}
|
|
160
|
+
</PreviewArea>
|
|
161
|
+
<ActionSelect>
|
|
162
|
+
<div style={{ display: 'flex', gap: '0.5rem' }}>
|
|
163
|
+
<ActionButton onClick={() => setShowCode(!showCode)}>
|
|
164
|
+
<CodeIcon size={16} />
|
|
165
|
+
{showCode ? "Hide Code" : "View Use Code"}
|
|
166
|
+
</ActionButton>
|
|
167
|
+
{showCode && (
|
|
168
|
+
<ActionButton onClick={handleCopy}>
|
|
169
|
+
{copied ? <Check size={16} /> : <Copy size={16} />}
|
|
170
|
+
{copied ? "Copied!" : "Copy"}
|
|
171
|
+
</ActionButton>
|
|
172
|
+
)}
|
|
173
|
+
</div>
|
|
174
|
+
</ActionSelect>
|
|
175
|
+
{showCode && (
|
|
176
|
+
<CodeBlock>
|
|
177
|
+
<pre>{code}</pre>
|
|
178
|
+
</CodeBlock>
|
|
179
|
+
)}
|
|
180
|
+
</ShowcaseBlock>
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function ButtonShowcase() {
|
|
186
|
+
return (
|
|
187
|
+
<ShowcaseContainer>
|
|
188
|
+
<Title>Button Component Variants</Title>
|
|
189
|
+
<p style={{ textAlign: 'center', color: '#666', marginBottom: '2rem' }}>
|
|
190
|
+
Komponen tombol yang dapat disesuaikan. Copy kodenya untuk menggunakan.
|
|
191
|
+
</p>
|
|
192
|
+
|
|
193
|
+
<ButtonVariantDisplay
|
|
194
|
+
title="Primary Blue"
|
|
195
|
+
code={`<Button label="Save Changes" bg="#4f46e5" icon={Save} />`}
|
|
196
|
+
>
|
|
197
|
+
<Button label="Save Changes" bg="#4f46e5" icon={Save} />
|
|
198
|
+
</ButtonVariantDisplay>
|
|
199
|
+
|
|
200
|
+
<ButtonVariantDisplay
|
|
201
|
+
title="Danger Red"
|
|
202
|
+
code={`<Button
|
|
203
|
+
label="Delete Item"
|
|
204
|
+
bg="#ef4444"
|
|
205
|
+
hoverBg="#dc2626"
|
|
206
|
+
icon={Trash2}
|
|
207
|
+
/>`}
|
|
208
|
+
>
|
|
209
|
+
<Button label="Delete Item" bg="#ef4444" hoverBg="#dc2626" icon={Trash2} />
|
|
210
|
+
</ButtonVariantDisplay>
|
|
211
|
+
|
|
212
|
+
<ButtonVariantDisplay
|
|
213
|
+
title="Outline / Secondary"
|
|
214
|
+
code={`<Button
|
|
215
|
+
label="Edit Profile"
|
|
216
|
+
bg="transparent"
|
|
217
|
+
color="#4f46e5"
|
|
218
|
+
border="2px solid #4f46e5"
|
|
219
|
+
hoverBg="#eef2ff"
|
|
220
|
+
icon={Edit}
|
|
221
|
+
/>`}
|
|
222
|
+
>
|
|
223
|
+
<Button
|
|
224
|
+
label="Edit Profile"
|
|
225
|
+
bg="transparent"
|
|
226
|
+
color="#4f46e5"
|
|
227
|
+
border="2px solid #4f46e5"
|
|
228
|
+
hoverBg="#eef2ff"
|
|
229
|
+
icon={Edit}
|
|
230
|
+
/>
|
|
231
|
+
</ButtonVariantDisplay>
|
|
232
|
+
|
|
233
|
+
</ShowcaseContainer>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export default function AppButton(props) {
|
|
238
|
+
// If no props are passed or just child props that imply showcase context in docs (this is heuristic),
|
|
239
|
+
// but better to rely on explicit usage.
|
|
240
|
+
// However, to mimic Header pattern, the default export is the Reusable Component
|
|
241
|
+
// BUT we might want to default export the Component for standard usage.
|
|
242
|
+
|
|
243
|
+
return <Button {...props} />;
|
|
244
|
+
}
|