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.
@@ -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
+ }