mertani-web-toolkit 0.1.36 → 0.1.38
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/dist/components/Table/Table.svelte +225 -179
- package/dist/components/Table/TableBadge.svelte +7 -8
- package/dist/components/Table/TablePagination.svelte +119 -15
- package/dist/components/Table/TableShimmer.svelte +6 -7
- package/dist/components/Table/types.d.ts +2 -2
- package/dist/stores/ThemeStore.d.ts +4 -0
- package/dist/stores/ThemeStore.js +55 -0
- package/dist/themes/mertani.json +104 -0
- package/dist/utils/themeLoader.d.ts +9 -0
- package/dist/utils/themeLoader.js +13 -0
- package/package.json +3 -2
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import TableBadge from './TableBadge.svelte';
|
|
4
4
|
import TablePagination from './TablePagination.svelte';
|
|
5
5
|
import Icon from '../Icon/Icon.svelte';
|
|
6
|
-
import type { Column,
|
|
6
|
+
import type { Column, BadgeStatusMapping } from './types.js';
|
|
7
7
|
|
|
8
8
|
export type { Column } from './types';
|
|
9
9
|
|
|
@@ -39,11 +39,41 @@
|
|
|
39
39
|
defaultBadgeStatusMappings?: BadgeStatusMapping[];
|
|
40
40
|
} = $props();
|
|
41
41
|
|
|
42
|
-
const
|
|
42
|
+
const EMPTY_MESSAGE = 'Tidak ada data';
|
|
43
|
+
const HEADER_BASE_CLASS =
|
|
44
|
+
'bg-bg-surface-subtle px-4 py-3 text-left text-xs font-bold whitespace-nowrap text-text-primary';
|
|
45
|
+
const CELL_BASE_CLASS =
|
|
46
|
+
'px-4 py-3 text-sm whitespace-nowrap text-text-primary bg-bg-surface group-hover:bg-bg-surface-subtle';
|
|
47
|
+
const ROW_BASE_CLASS =
|
|
48
|
+
'group border-b border-border-outline bg-bg-surface hover:bg-bg-surface-subtle';
|
|
43
49
|
|
|
44
50
|
let scrollContainer: HTMLElement | null = $state(null);
|
|
45
51
|
let internalHasHorizontalScroll = $state(false);
|
|
52
|
+
let isMobile = $state(false);
|
|
46
53
|
|
|
54
|
+
$effect(() => {
|
|
55
|
+
if (typeof window === 'undefined') return;
|
|
56
|
+
|
|
57
|
+
const mediaQuery = window.matchMedia('(max-width: 768px)');
|
|
58
|
+
isMobile = mediaQuery.matches;
|
|
59
|
+
|
|
60
|
+
const handleChange = (e: MediaQueryListEvent | MediaQueryList) => {
|
|
61
|
+
isMobile = e.matches;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Modern browsers
|
|
65
|
+
if (mediaQuery.addEventListener) {
|
|
66
|
+
mediaQuery.addEventListener('change', handleChange);
|
|
67
|
+
return () => mediaQuery.removeEventListener('change', handleChange);
|
|
68
|
+
}
|
|
69
|
+
// Fallback for older browsers
|
|
70
|
+
else if (mediaQuery.addListener) {
|
|
71
|
+
mediaQuery.addListener(handleChange);
|
|
72
|
+
return () => mediaQuery.removeListener(handleChange);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// ========== SCROLL MANAGEMENT ==========
|
|
47
77
|
function updateScrollState() {
|
|
48
78
|
internalHasHorizontalScroll = scrollContainer
|
|
49
79
|
? scrollContainer.scrollWidth > scrollContainer.clientWidth
|
|
@@ -54,10 +84,8 @@
|
|
|
54
84
|
if (scrollContainer) {
|
|
55
85
|
const updateWithDelay = () => setTimeout(updateScrollState, 100);
|
|
56
86
|
updateWithDelay();
|
|
57
|
-
|
|
58
87
|
const handleResize = () => updateWithDelay();
|
|
59
88
|
window.addEventListener('resize', handleResize);
|
|
60
|
-
|
|
61
89
|
return () => window.removeEventListener('resize', handleResize);
|
|
62
90
|
}
|
|
63
91
|
});
|
|
@@ -70,55 +98,13 @@
|
|
|
70
98
|
|
|
71
99
|
const hasHorizontalScroll = $derived(internalHasHorizontalScroll);
|
|
72
100
|
|
|
73
|
-
|
|
74
|
-
let left = 0;
|
|
75
|
-
const colsToUse = useFlatColumns ? flatColumns : columns;
|
|
76
|
-
|
|
77
|
-
for (let i = 0; i < index; i++) {
|
|
78
|
-
const prevCol = colsToUse[i];
|
|
79
|
-
if (prevCol.sticky === 'left') {
|
|
80
|
-
const width = prevCol.width || prevCol.minWidth || '160px';
|
|
81
|
-
const widthValue = parseFloat(width.toString().replace('px', '')) || 160;
|
|
82
|
-
left += widthValue;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
if (column.stickyOffset) {
|
|
86
|
-
left += column.stickyOffset;
|
|
87
|
-
}
|
|
88
|
-
return left;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function getStickyStyle(column: Column, index: number, useFlatColumns: boolean = false): string {
|
|
92
|
-
if (!column.sticky) return '';
|
|
93
|
-
if (column.sticky === 'left') {
|
|
94
|
-
return `position: sticky; left: ${calculateStickyLeft(column, index, useFlatColumns)}px; z-index: 10;`;
|
|
95
|
-
}
|
|
96
|
-
if (column.sticky === 'right') {
|
|
97
|
-
return `position: sticky; right: 0; z-index: 10;`;
|
|
98
|
-
}
|
|
99
|
-
return '';
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function getHeaderStickyStyle(column: Column, index: number): string {
|
|
103
|
-
if (!column.sticky) return '';
|
|
104
|
-
if (column.sticky === 'left') {
|
|
105
|
-
return `position: sticky; left: ${calculateStickyLeft(column, index)}px; z-index: 20;`;
|
|
106
|
-
}
|
|
107
|
-
if (column.sticky === 'right') {
|
|
108
|
-
return `position: sticky; right: 0; z-index: 20;`;
|
|
109
|
-
}
|
|
110
|
-
return '';
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Flatten columns to get all leaf columns (for body rendering)
|
|
114
|
-
// Also preserves parent borderRight info for child columns
|
|
101
|
+
// ========== COLUMN UTILITIES ==========
|
|
115
102
|
function flattenColumns(cols: Column[], parentBorderRight?: boolean): Column[] {
|
|
116
103
|
const result: Column[] = [];
|
|
117
104
|
for (const col of cols) {
|
|
118
105
|
if (col.children && col.children.length > 0) {
|
|
119
106
|
const childBorderRight = col.borderRight || parentBorderRight;
|
|
120
107
|
const children = flattenColumns(col.children, childBorderRight);
|
|
121
|
-
// Mark last child if parent has borderRight
|
|
122
108
|
if (childBorderRight && children.length > 0) {
|
|
123
109
|
children[children.length - 1] = { ...children[children.length - 1], borderRight: true };
|
|
124
110
|
}
|
|
@@ -130,7 +116,6 @@
|
|
|
130
116
|
return result;
|
|
131
117
|
}
|
|
132
118
|
|
|
133
|
-
// Get total column count (including children)
|
|
134
119
|
function getTotalColumnCount(cols: Column[]): number {
|
|
135
120
|
let count = 0;
|
|
136
121
|
for (const col of cols) {
|
|
@@ -143,23 +128,152 @@
|
|
|
143
128
|
return count;
|
|
144
129
|
}
|
|
145
130
|
|
|
146
|
-
|
|
147
|
-
const hasNestedColumns = $derived(columns.some(col => col.children && col.children.length > 0));
|
|
148
|
-
|
|
149
|
-
// Flattened columns for body rendering
|
|
131
|
+
const hasNestedColumns = $derived(columns.some((col) => col.children && col.children.length > 0));
|
|
150
132
|
const flatColumns = $derived(flattenColumns(columns, false));
|
|
151
|
-
|
|
152
|
-
// Total column count for colspan
|
|
153
133
|
const totalColumnCount = $derived(getTotalColumnCount(columns));
|
|
134
|
+
|
|
135
|
+
// ========== STICKY COLUMN UTILITIES ==========
|
|
136
|
+
function parseWidth(width: string | undefined): number {
|
|
137
|
+
if (!width) return 160;
|
|
138
|
+
return parseFloat(width.toString().replace('px', '')) || 160;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function calculateStickyLeft(
|
|
142
|
+
column: Column,
|
|
143
|
+
index: number,
|
|
144
|
+
useFlatColumns: boolean = false
|
|
145
|
+
): number {
|
|
146
|
+
let left = 0;
|
|
147
|
+
const colsToUse = useFlatColumns ? flatColumns : columns;
|
|
148
|
+
|
|
149
|
+
for (let i = 0; i < index; i++) {
|
|
150
|
+
const prevCol = colsToUse[i];
|
|
151
|
+
if (prevCol.sticky === 'left') {
|
|
152
|
+
left += parseWidth(prevCol.width || prevCol.minWidth);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (column.stickyOffset) {
|
|
156
|
+
left += column.stickyOffset;
|
|
157
|
+
}
|
|
158
|
+
return left;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function getStickyStyle(
|
|
162
|
+
column: Column,
|
|
163
|
+
index: number,
|
|
164
|
+
useFlatColumns: boolean = false,
|
|
165
|
+
zIndex: number = 10
|
|
166
|
+
): string {
|
|
167
|
+
// Disable sticky on mobile
|
|
168
|
+
if (isMobile || !column.sticky) return '';
|
|
169
|
+
if (column.sticky === 'left') {
|
|
170
|
+
return `position: sticky; left: ${calculateStickyLeft(column, index, useFlatColumns)}px; z-index: ${zIndex};`;
|
|
171
|
+
}
|
|
172
|
+
if (column.sticky === 'right') {
|
|
173
|
+
return `position: sticky; right: 0; z-index: ${zIndex};`;
|
|
174
|
+
}
|
|
175
|
+
return '';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function getHeaderStickyStyle(column: Column, index: number): string {
|
|
179
|
+
return getStickyStyle(column, index, false, 20);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ========== STYLE UTILITIES ==========
|
|
183
|
+
function getColumnStyles(column: Column): string {
|
|
184
|
+
const styles: string[] = [];
|
|
185
|
+
if (column.width) styles.push(`width: ${column.width};`);
|
|
186
|
+
if (column.minWidth) styles.push(`min-width: ${column.minWidth};`);
|
|
187
|
+
if (column.maxWidth) styles.push(`max-width: ${column.maxWidth};`);
|
|
188
|
+
if (column.align === 'center') styles.push('text-align: center;');
|
|
189
|
+
if (column.align === 'right') styles.push('text-align: right;');
|
|
190
|
+
return styles.join(' ');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function getHeaderClasses(column: Column): string {
|
|
194
|
+
const classes: string[] = [HEADER_BASE_CLASS];
|
|
195
|
+
if (column.borderRight) classes.push('border-r border-border-outline');
|
|
196
|
+
if (column.headerClass) classes.push(column.headerClass);
|
|
197
|
+
return classes.join(' ');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function getHeaderShadow(column: Column): string | null {
|
|
201
|
+
if (isMobile) return null;
|
|
202
|
+
if (column.sticky === 'left' && hasHorizontalScroll) return 'right';
|
|
203
|
+
if (column.sticky === 'right' && hasHorizontalScroll) return 'left';
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function getCellClasses(column: Column): string {
|
|
208
|
+
const classes: string[] = [CELL_BASE_CLASS];
|
|
209
|
+
if (column.borderRight) classes.push('border-r border-border-outline');
|
|
210
|
+
if (column.cellClass) classes.push(column.cellClass);
|
|
211
|
+
return classes.join(' ');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function getCellShadow(column: Column): string | null {
|
|
215
|
+
if (isMobile) return null;
|
|
216
|
+
if (column.sticky === 'left' && hasHorizontalScroll) return 'right';
|
|
217
|
+
if (column.sticky === 'right' && hasHorizontalScroll) return 'left';
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function getRowClasses(): string {
|
|
222
|
+
const classes: string[] = [ROW_BASE_CLASS];
|
|
223
|
+
if (onRowClick !== undefined) classes.push('cursor-pointer');
|
|
224
|
+
return classes.join(' ');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function getAlignClasses(align?: 'left' | 'center' | 'right'): string {
|
|
228
|
+
if (align === 'center') return 'justify-center';
|
|
229
|
+
if (align === 'right') return 'justify-end';
|
|
230
|
+
return 'justify-start';
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ========== ACTION HANDLERS ==========
|
|
234
|
+
function replacePlaceholders(template: string, row: any, rowIndex: number): string {
|
|
235
|
+
return template.replace(/\{(\w+)\}/g, (match, key) => String(row[key] ?? rowIndex));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function handleAction(action: any, column: Column, row: any, rowIndex: number) {
|
|
239
|
+
const onClickHandler = column.onActions?.[action.actionKey];
|
|
240
|
+
const actionType = action.actionType || 'function';
|
|
241
|
+
|
|
242
|
+
switch (actionType) {
|
|
243
|
+
case 'function':
|
|
244
|
+
if (onClickHandler) onClickHandler(row, rowIndex);
|
|
245
|
+
break;
|
|
246
|
+
case 'alert':
|
|
247
|
+
if (action.actionValue) {
|
|
248
|
+
alert(replacePlaceholders(action.actionValue, row, rowIndex));
|
|
249
|
+
}
|
|
250
|
+
break;
|
|
251
|
+
case 'dialog':
|
|
252
|
+
case 'modal':
|
|
253
|
+
if (action.actionValue) {
|
|
254
|
+
window.dispatchEvent(
|
|
255
|
+
new CustomEvent('table-action-dialog', {
|
|
256
|
+
detail: { dialogId: action.actionValue, actionKey: action.actionKey, row, rowIndex }
|
|
257
|
+
})
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
break;
|
|
261
|
+
case 'navigate':
|
|
262
|
+
if (action.actionValue) {
|
|
263
|
+
window.location.href = replacePlaceholders(action.actionValue, row, rowIndex);
|
|
264
|
+
}
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
154
268
|
</script>
|
|
155
269
|
|
|
156
270
|
<div
|
|
157
|
-
class="overflow-x-auto overflow-y-auto
|
|
271
|
+
class="table-scroll-container mx-4 overflow-x-auto overflow-y-auto"
|
|
158
272
|
style="height: {height}; min-height: {minHeight};"
|
|
159
273
|
bind:this={scrollContainer}
|
|
160
274
|
>
|
|
161
275
|
<table class="w-full border-collapse">
|
|
162
|
-
<thead class="
|
|
276
|
+
<thead class="sticky top-0 z-30">
|
|
163
277
|
{#if hasNestedColumns}
|
|
164
278
|
<!-- Parent header row -->
|
|
165
279
|
<tr>
|
|
@@ -168,15 +282,9 @@
|
|
|
168
282
|
<th
|
|
169
283
|
colspan={childCount}
|
|
170
284
|
rowspan={column.children && column.children.length > 0 ? 1 : 2}
|
|
171
|
-
class=
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
{column.minWidth ? `min-width: ${column.minWidth};` : ''}
|
|
175
|
-
{column.maxWidth ? `max-width: ${column.maxWidth};` : ''}
|
|
176
|
-
{column.align === 'center' ? 'text-align: center;' : ''}
|
|
177
|
-
{column.align === 'right' ? 'text-align: right;' : ''}
|
|
178
|
-
{getHeaderStickyStyle(column, index)}
|
|
179
|
-
"
|
|
285
|
+
class={getHeaderClasses(column)}
|
|
286
|
+
data-shadow={getHeaderShadow(column)}
|
|
287
|
+
style="{getColumnStyles(column)} {getHeaderStickyStyle(column, index)}"
|
|
180
288
|
>
|
|
181
289
|
{#if loading}
|
|
182
290
|
<div class="shimmer-skeleton h-4 w-24"></div>
|
|
@@ -192,16 +300,18 @@
|
|
|
192
300
|
{#if column.children && column.children.length > 0}
|
|
193
301
|
{#each column.children as childColumn, childIndex}
|
|
194
302
|
{@const isLastChild = childIndex === column.children.length - 1}
|
|
195
|
-
{@const shouldShowBorder =
|
|
303
|
+
{@const shouldShowBorder =
|
|
304
|
+
childColumn.borderRight || (column.borderRight && isLastChild)}
|
|
196
305
|
<th
|
|
197
|
-
class="
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
306
|
+
class="{HEADER_BASE_CLASS} {shouldShowBorder
|
|
307
|
+
? 'border-r border-border-outline'
|
|
308
|
+
: ''} {childColumn.headerClass || ''}"
|
|
309
|
+
data-shadow={!isMobile && childColumn.sticky === 'left' && hasHorizontalScroll
|
|
310
|
+
? 'right'
|
|
311
|
+
: !isMobile && childColumn.sticky === 'right' && hasHorizontalScroll
|
|
312
|
+
? 'left'
|
|
313
|
+
: null}
|
|
314
|
+
style={getColumnStyles(childColumn)}
|
|
205
315
|
>
|
|
206
316
|
{#if loading}
|
|
207
317
|
<div class="shimmer-skeleton h-4 w-24"></div>
|
|
@@ -214,19 +324,13 @@
|
|
|
214
324
|
{/each}
|
|
215
325
|
</tr>
|
|
216
326
|
{:else}
|
|
217
|
-
<!-- Simple header row
|
|
327
|
+
<!-- Simple header row -->
|
|
218
328
|
<tr>
|
|
219
329
|
{#each columns as column, index}
|
|
220
330
|
<th
|
|
221
|
-
class=
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
{column.minWidth ? `min-width: ${column.minWidth};` : ''}
|
|
225
|
-
{column.maxWidth ? `max-width: ${column.maxWidth};` : ''}
|
|
226
|
-
{column.align === 'center' ? 'text-align: center;' : ''}
|
|
227
|
-
{column.align === 'right' ? 'text-align: right;' : ''}
|
|
228
|
-
{getHeaderStickyStyle(column, index)}
|
|
229
|
-
"
|
|
331
|
+
class={getHeaderClasses(column)}
|
|
332
|
+
data-shadow={getHeaderShadow(column)}
|
|
333
|
+
style="{getColumnStyles(column)} {getHeaderStickyStyle(column, index)}"
|
|
230
334
|
>
|
|
231
335
|
{#if loading}
|
|
232
336
|
<div class="shimmer-skeleton h-4 w-24"></div>
|
|
@@ -243,70 +347,28 @@
|
|
|
243
347
|
<TableShimmer rows={5} columns={totalColumnCount} />
|
|
244
348
|
{:else if data.length === 0}
|
|
245
349
|
<tr>
|
|
246
|
-
<td colspan={totalColumnCount} class="px-4 py-8 text-center text-sm text-
|
|
247
|
-
{
|
|
350
|
+
<td colspan={totalColumnCount} class="px-4 py-8 text-center text-sm text-text-tertiary">
|
|
351
|
+
{EMPTY_MESSAGE}
|
|
248
352
|
</td>
|
|
249
353
|
</tr>
|
|
250
354
|
{:else}
|
|
251
355
|
{#each data as row, rowIndex}
|
|
252
|
-
<tr
|
|
253
|
-
class="group border-b border-[#EAECF0] hover:bg-[#F9FAFB] {rowIndex % 2 === 0 ? 'bg-white' : 'bg-[#F8F9FA]'} {onRowClick !== undefined ? 'cursor-pointer' : ''}"
|
|
254
|
-
onclick={() => onRowClick?.(row, rowIndex)}
|
|
255
|
-
>
|
|
356
|
+
<tr class={getRowClasses()} onclick={() => onRowClick?.(row, rowIndex)}>
|
|
256
357
|
{#each flatColumns as column, colIndex}
|
|
257
358
|
<td
|
|
258
|
-
class=
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
{column.minWidth ? `min-width: ${column.minWidth};` : ''}
|
|
262
|
-
{column.maxWidth ? `max-width: ${column.maxWidth};` : ''}
|
|
263
|
-
{column.align === 'center' ? 'text-align: center;' : ''}
|
|
264
|
-
{column.align === 'right' ? 'text-align: right;' : ''}
|
|
265
|
-
{getStickyStyle(column, colIndex, true)}
|
|
266
|
-
{column.sticky ? `background-color: ${rowIndex % 2 === 0 ? 'white' : '#F8F9FA'};` : ''}
|
|
267
|
-
"
|
|
359
|
+
class={getCellClasses(column)}
|
|
360
|
+
data-shadow={getCellShadow(column)}
|
|
361
|
+
style="{getColumnStyles(column)} {getStickyStyle(column, colIndex, true)}"
|
|
268
362
|
>
|
|
269
363
|
{#if column.actions && column.actions.length > 0}
|
|
270
|
-
<div class="flex items-center gap-2 {column.align
|
|
364
|
+
<div class="flex items-center gap-2 {getAlignClasses(column.align)}">
|
|
271
365
|
{#each column.actions as action}
|
|
272
|
-
{@const handleAction = () => {
|
|
273
|
-
const onClickHandler = column.onActions?.[action.actionKey];
|
|
274
|
-
const actionType = action.actionType || 'function';
|
|
275
|
-
|
|
276
|
-
if (actionType === 'function' && onClickHandler) {
|
|
277
|
-
onClickHandler(row, rowIndex);
|
|
278
|
-
} else if (actionType === 'alert' && action.actionValue) {
|
|
279
|
-
let message = action.actionValue;
|
|
280
|
-
// Replace placeholders like {id}, {name}, etc. with row data
|
|
281
|
-
message = message.replace(/\{(\w+)\}/g, (match, key) => {
|
|
282
|
-
return String(row[key] ?? rowIndex);
|
|
283
|
-
});
|
|
284
|
-
alert(message);
|
|
285
|
-
} else if ((actionType === 'dialog' || actionType === 'modal') && action.actionValue) {
|
|
286
|
-
// Dispatch custom event for dialog/modal
|
|
287
|
-
window.dispatchEvent(new CustomEvent('table-action-dialog', {
|
|
288
|
-
detail: {
|
|
289
|
-
dialogId: action.actionValue,
|
|
290
|
-
actionKey: action.actionKey,
|
|
291
|
-
row,
|
|
292
|
-
rowIndex
|
|
293
|
-
}
|
|
294
|
-
}));
|
|
295
|
-
} else if (actionType === 'navigate' && action.actionValue) {
|
|
296
|
-
let url = action.actionValue;
|
|
297
|
-
// Replace placeholders like {id}, {name}, etc. with row data
|
|
298
|
-
url = url.replace(/\{(\w+)\}/g, (match, key) => {
|
|
299
|
-
return String(row[key] ?? rowIndex);
|
|
300
|
-
});
|
|
301
|
-
window.location.href = url;
|
|
302
|
-
}
|
|
303
|
-
}}
|
|
304
366
|
<Icon
|
|
305
367
|
name={action.icon as any}
|
|
306
368
|
width={18}
|
|
307
369
|
height={18}
|
|
308
|
-
color={action.color || '
|
|
309
|
-
onclick={handleAction}
|
|
370
|
+
color={action.color || 'text-text-primary'}
|
|
371
|
+
onclick={() => handleAction(action, column, row, rowIndex)}
|
|
310
372
|
style="cursor: pointer;"
|
|
311
373
|
/>
|
|
312
374
|
{/each}
|
|
@@ -315,7 +377,7 @@
|
|
|
315
377
|
{@const badgeStatus = String(row[column.badgeKey || column.key || ''] || '')}
|
|
316
378
|
{@const badgeMappings = column.badgeStatusMappings || defaultBadgeStatusMappings}
|
|
317
379
|
{#if badgeMappings && badgeMappings.length > 0}
|
|
318
|
-
<div class="flex {column.align
|
|
380
|
+
<div class="flex {getAlignClasses(column.align)}">
|
|
319
381
|
<TableBadge
|
|
320
382
|
status={badgeStatus}
|
|
321
383
|
label={column.badgeLabel}
|
|
@@ -323,8 +385,10 @@
|
|
|
323
385
|
/>
|
|
324
386
|
</div>
|
|
325
387
|
{:else}
|
|
326
|
-
<div class="flex {column.align
|
|
327
|
-
<span
|
|
388
|
+
<div class="flex {getAlignClasses(column.align)}">
|
|
389
|
+
<span
|
|
390
|
+
class="inline-block rounded border border-border-divider-base bg-bg-surface-raised px-2 text-sm leading-[23px] font-normal text-text-primary"
|
|
391
|
+
>
|
|
328
392
|
{column.badgeLabel || badgeStatus}
|
|
329
393
|
</span>
|
|
330
394
|
</div>
|
|
@@ -355,63 +419,46 @@
|
|
|
355
419
|
{/if}
|
|
356
420
|
|
|
357
421
|
<style>
|
|
358
|
-
|
|
359
|
-
scrollbar-width: thin;
|
|
360
|
-
scrollbar-color: rgba(152, 162, 179, 0.3) transparent;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
.table-scroll-container::-webkit-scrollbar {
|
|
364
|
-
height: 6px;
|
|
365
|
-
width: 6px;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
.table-scroll-container::-webkit-scrollbar-track {
|
|
369
|
-
background: transparent;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
.table-scroll-container::-webkit-scrollbar-thumb {
|
|
373
|
-
background-color: rgba(152, 162, 179, 0.3);
|
|
374
|
-
border-radius: 3px;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
.table-scroll-container::-webkit-scrollbar-thumb:hover {
|
|
378
|
-
background-color: rgba(80, 103, 142, 0.5);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
.table-scroll-container::-webkit-scrollbar-corner {
|
|
382
|
-
background: transparent;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
:global(.shadow-right) {
|
|
422
|
+
[data-shadow='right'] {
|
|
386
423
|
position: relative;
|
|
387
|
-
box-shadow: 2px 0 16px -2px
|
|
424
|
+
box-shadow: 2px 0 16px -2px color-mix(in srgb, var(--color-text-primary) 10%, transparent) !important;
|
|
388
425
|
}
|
|
389
426
|
|
|
390
|
-
|
|
427
|
+
[data-shadow='right']::after {
|
|
391
428
|
content: '';
|
|
392
429
|
position: absolute;
|
|
393
430
|
top: 0;
|
|
394
431
|
right: -12px;
|
|
395
432
|
width: 12px;
|
|
396
433
|
height: 100%;
|
|
397
|
-
background: linear-gradient(
|
|
434
|
+
background: linear-gradient(
|
|
435
|
+
to right,
|
|
436
|
+
color-mix(in srgb, var(--color-text-primary) 8%, transparent),
|
|
437
|
+
color-mix(in srgb, var(--color-text-primary) 4%, transparent),
|
|
438
|
+
transparent
|
|
439
|
+
);
|
|
398
440
|
pointer-events: none;
|
|
399
441
|
z-index: -1;
|
|
400
442
|
}
|
|
401
443
|
|
|
402
|
-
|
|
444
|
+
[data-shadow='left'] {
|
|
403
445
|
position: relative;
|
|
404
|
-
box-shadow: -2px 0 16px -2px
|
|
446
|
+
box-shadow: -2px 0 16px -2px color-mix(in srgb, var(--color-text-primary) 10%, transparent) !important;
|
|
405
447
|
}
|
|
406
448
|
|
|
407
|
-
|
|
449
|
+
[data-shadow='left']::before {
|
|
408
450
|
content: '';
|
|
409
451
|
position: absolute;
|
|
410
452
|
top: 0;
|
|
411
453
|
left: -12px;
|
|
412
454
|
width: 12px;
|
|
413
455
|
height: 100%;
|
|
414
|
-
background: linear-gradient(
|
|
456
|
+
background: linear-gradient(
|
|
457
|
+
to left,
|
|
458
|
+
color-mix(in srgb, var(--color-text-primary) 8%, transparent),
|
|
459
|
+
color-mix(in srgb, var(--color-text-primary) 4%, transparent),
|
|
460
|
+
transparent
|
|
461
|
+
);
|
|
415
462
|
pointer-events: none;
|
|
416
463
|
z-index: -1;
|
|
417
464
|
}
|
|
@@ -420,7 +467,7 @@
|
|
|
420
467
|
position: relative;
|
|
421
468
|
overflow: hidden;
|
|
422
469
|
border-radius: 4px;
|
|
423
|
-
background-color:
|
|
470
|
+
background-color: var(--color-bg-surface-subtle);
|
|
424
471
|
}
|
|
425
472
|
|
|
426
473
|
.shimmer-skeleton::after {
|
|
@@ -432,10 +479,10 @@
|
|
|
432
479
|
transform: translateX(-100%);
|
|
433
480
|
background-image: linear-gradient(
|
|
434
481
|
90deg,
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
482
|
+
color-mix(in srgb, var(--color-text-primary) 0%, transparent) 0,
|
|
483
|
+
color-mix(in srgb, var(--color-text-primary) 20%, transparent) 20%,
|
|
484
|
+
color-mix(in srgb, var(--color-text-primary) 50%, transparent) 80%,
|
|
485
|
+
color-mix(in srgb, var(--color-text-primary) 100%, transparent) 100%
|
|
439
486
|
);
|
|
440
487
|
animation: shimmer 3s infinite;
|
|
441
488
|
content: '';
|
|
@@ -447,4 +494,3 @@
|
|
|
447
494
|
}
|
|
448
495
|
}
|
|
449
496
|
</style>
|
|
450
|
-
|
|
@@ -5,21 +5,21 @@
|
|
|
5
5
|
function getVariantClass(variant: BadgeVariant): string {
|
|
6
6
|
switch (variant) {
|
|
7
7
|
case 'success':
|
|
8
|
-
return 'bg-
|
|
8
|
+
return 'bg-bg-success-subtle text-text-success-ti border border-border-success-bor';
|
|
9
9
|
case 'error':
|
|
10
|
-
return 'bg-
|
|
10
|
+
return 'bg-bg-error-subtle text-text-error-ti border border-border-error-bor';
|
|
11
11
|
case 'warning':
|
|
12
|
-
return 'bg-
|
|
12
|
+
return 'bg-bg-warning-subtle text-text-warning-ti border border-border-warning-bor';
|
|
13
13
|
case 'info':
|
|
14
|
-
return 'bg-
|
|
14
|
+
return 'bg-bg-info-subtle text-text-info-ti border border-border-info-bor';
|
|
15
15
|
case 'neutral':
|
|
16
16
|
default:
|
|
17
|
-
return 'bg-
|
|
17
|
+
return 'bg-bg-surface-subtle text-text-secondary border border-border-divider-base';
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
function getStatusBadgeClass(status: string, mappings: BadgeStatusMapping[]): string {
|
|
22
|
-
const mapping = mappings.find(m => m.status === status);
|
|
22
|
+
const mapping = mappings.find((m) => m.status === status);
|
|
23
23
|
const variant = mapping?.variant || 'neutral';
|
|
24
24
|
return getVariantClass(variant);
|
|
25
25
|
}
|
|
@@ -36,11 +36,10 @@
|
|
|
36
36
|
</script>
|
|
37
37
|
|
|
38
38
|
<span
|
|
39
|
-
class="inline-block rounded border px-2 text-sm
|
|
39
|
+
class="inline-block rounded border px-2 text-sm leading-[23px] font-normal {getStatusBadgeClass(
|
|
40
40
|
status,
|
|
41
41
|
statusMappings
|
|
42
42
|
)}"
|
|
43
43
|
>
|
|
44
44
|
{label || status}
|
|
45
45
|
</span>
|
|
46
|
-
|
|
@@ -16,6 +16,30 @@
|
|
|
16
16
|
} = $props();
|
|
17
17
|
|
|
18
18
|
const limitValues = [10, 25, 50, 100, 200];
|
|
19
|
+
let isMobile = $state(false);
|
|
20
|
+
|
|
21
|
+
// ========== MOBILE DETECTION ==========
|
|
22
|
+
$effect(() => {
|
|
23
|
+
if (typeof window === 'undefined') return;
|
|
24
|
+
|
|
25
|
+
const mediaQuery = window.matchMedia('(max-width: 768px)');
|
|
26
|
+
isMobile = mediaQuery.matches;
|
|
27
|
+
|
|
28
|
+
const handleChange = (e: MediaQueryListEvent | MediaQueryList) => {
|
|
29
|
+
isMobile = e.matches;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Modern browsers
|
|
33
|
+
if (mediaQuery.addEventListener) {
|
|
34
|
+
mediaQuery.addEventListener('change', handleChange);
|
|
35
|
+
return () => mediaQuery.removeEventListener('change', handleChange);
|
|
36
|
+
}
|
|
37
|
+
// Fallback for older browsers
|
|
38
|
+
else if (mediaQuery.addListener) {
|
|
39
|
+
mediaQuery.addListener(handleChange);
|
|
40
|
+
return () => mediaQuery.removeListener(handleChange);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
19
43
|
|
|
20
44
|
function handlePrevPage() {
|
|
21
45
|
if (currentPage > 1 && onPageChange) {
|
|
@@ -42,19 +66,19 @@
|
|
|
42
66
|
</script>
|
|
43
67
|
|
|
44
68
|
{#if shouldShow}
|
|
45
|
-
<div class="pagination">
|
|
69
|
+
<div class="pagination" class:mobile={isMobile}>
|
|
46
70
|
<div class="limit">
|
|
47
71
|
<p>Tampilkan</p>
|
|
48
72
|
<select
|
|
49
73
|
value={itemsPerPage.toString()}
|
|
50
74
|
onchange={handleItemsPerPageChange}
|
|
51
|
-
class="items-per-page-select"
|
|
75
|
+
class="items-per-page-select text-text-primary"
|
|
52
76
|
>
|
|
53
77
|
{#each limitValues as value}
|
|
54
|
-
<option value={value.toString()}>{value}</option>
|
|
78
|
+
<option value={value.toString()} class="text-text-primary">{value}</option>
|
|
55
79
|
{/each}
|
|
56
80
|
</select>
|
|
57
|
-
<p>{currentPage} dari {totalPages} Halaman</p>
|
|
81
|
+
<p class="page-info">{currentPage} dari {totalPages} Halaman</p>
|
|
58
82
|
</div>
|
|
59
83
|
<div class="navigation">
|
|
60
84
|
<button
|
|
@@ -64,7 +88,13 @@
|
|
|
64
88
|
disabled={currentPage <= 1}
|
|
65
89
|
aria-label="Halaman Sebelumnya"
|
|
66
90
|
>
|
|
67
|
-
<svg
|
|
91
|
+
<svg
|
|
92
|
+
width="16"
|
|
93
|
+
height="16"
|
|
94
|
+
viewBox="0 0 16 16"
|
|
95
|
+
fill="none"
|
|
96
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
97
|
+
>
|
|
68
98
|
<path
|
|
69
99
|
d="M10 12L6 8L10 4"
|
|
70
100
|
stroke="currentColor"
|
|
@@ -81,7 +111,13 @@
|
|
|
81
111
|
disabled={currentPage >= totalPages}
|
|
82
112
|
aria-label="Halaman Selanjutnya"
|
|
83
113
|
>
|
|
84
|
-
<svg
|
|
114
|
+
<svg
|
|
115
|
+
width="16"
|
|
116
|
+
height="16"
|
|
117
|
+
viewBox="0 0 16 16"
|
|
118
|
+
fill="none"
|
|
119
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
120
|
+
>
|
|
85
121
|
<path
|
|
86
122
|
d="M6 4L10 8L6 12"
|
|
87
123
|
stroke="currentColor"
|
|
@@ -99,14 +135,24 @@
|
|
|
99
135
|
.pagination {
|
|
100
136
|
position: sticky;
|
|
101
137
|
bottom: 0;
|
|
102
|
-
background-color:
|
|
138
|
+
background-color: var(--color-bg-surface);
|
|
103
139
|
z-index: 30;
|
|
104
140
|
padding: 8px 16px;
|
|
105
|
-
border-top: 1px solid
|
|
141
|
+
border-top: 1px solid var(--color-border-outline);
|
|
106
142
|
display: flex;
|
|
107
143
|
justify-content: space-between;
|
|
108
144
|
align-items: center;
|
|
109
145
|
margin: 0 16px;
|
|
146
|
+
width: 100%;
|
|
147
|
+
box-sizing: border-box;
|
|
148
|
+
max-width: 100%;
|
|
149
|
+
overflow: hidden;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.pagination.mobile {
|
|
153
|
+
flex-direction: column;
|
|
154
|
+
gap: 12px;
|
|
155
|
+
padding: 12px 16px;
|
|
110
156
|
}
|
|
111
157
|
|
|
112
158
|
.limit {
|
|
@@ -116,23 +162,47 @@
|
|
|
116
162
|
gap: 12px;
|
|
117
163
|
position: relative;
|
|
118
164
|
z-index: 31;
|
|
165
|
+
flex-wrap: wrap;
|
|
166
|
+
min-width: 0;
|
|
167
|
+
flex: 1;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.pagination.mobile .limit {
|
|
171
|
+
width: 100%;
|
|
172
|
+
justify-content: center;
|
|
173
|
+
gap: 8px;
|
|
119
174
|
}
|
|
120
175
|
|
|
121
176
|
.limit p {
|
|
122
177
|
font-size: 0.875rem;
|
|
123
|
-
color:
|
|
178
|
+
color: var(--color-text-primary);
|
|
124
179
|
margin: 0;
|
|
180
|
+
white-space: nowrap;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.pagination.mobile .limit p {
|
|
184
|
+
font-size: 0.8125rem;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.page-info {
|
|
188
|
+
white-space: nowrap;
|
|
125
189
|
}
|
|
126
190
|
|
|
127
191
|
.items-per-page-select {
|
|
128
192
|
padding: 4px 8px;
|
|
129
|
-
border: 1px solid
|
|
193
|
+
border: 1px solid var(--color-border-divider-base);
|
|
130
194
|
border-radius: 6px;
|
|
131
195
|
font-size: 0.875rem;
|
|
132
|
-
color:
|
|
133
|
-
background-color:
|
|
196
|
+
color: var(--color-text-primary);
|
|
197
|
+
background-color: var(--color-bg-surface);
|
|
134
198
|
cursor: pointer;
|
|
135
199
|
min-width: 64px;
|
|
200
|
+
flex-shrink: 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.pagination.mobile .items-per-page-select {
|
|
204
|
+
font-size: 0.8125rem;
|
|
205
|
+
padding: 6px 8px;
|
|
136
206
|
}
|
|
137
207
|
|
|
138
208
|
.items-per-page-select:focus {
|
|
@@ -144,6 +214,13 @@
|
|
|
144
214
|
.navigation {
|
|
145
215
|
display: flex;
|
|
146
216
|
gap: 12px;
|
|
217
|
+
flex-shrink: 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.pagination.mobile .navigation {
|
|
221
|
+
width: 100%;
|
|
222
|
+
justify-content: center;
|
|
223
|
+
gap: 16px;
|
|
147
224
|
}
|
|
148
225
|
|
|
149
226
|
.nav-button {
|
|
@@ -152,12 +229,18 @@
|
|
|
152
229
|
justify-content: center;
|
|
153
230
|
height: 28px;
|
|
154
231
|
width: 28px;
|
|
155
|
-
color:
|
|
232
|
+
color: var(--color-text-secondary);
|
|
156
233
|
cursor: default;
|
|
157
234
|
border-radius: 6px;
|
|
158
235
|
border: none;
|
|
159
236
|
background: transparent;
|
|
160
237
|
padding: 0;
|
|
238
|
+
flex-shrink: 0;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.pagination.mobile .nav-button {
|
|
242
|
+
height: 32px;
|
|
243
|
+
width: 32px;
|
|
161
244
|
}
|
|
162
245
|
|
|
163
246
|
.nav-button.active {
|
|
@@ -172,7 +255,28 @@
|
|
|
172
255
|
}
|
|
173
256
|
|
|
174
257
|
.nav-button:not(:disabled):hover.active {
|
|
175
|
-
background-color:
|
|
258
|
+
background-color: color-mix(in srgb, var(--color-bg-act-primary) 10%, transparent);
|
|
176
259
|
}
|
|
177
|
-
</style>
|
|
178
260
|
|
|
261
|
+
/* Mobile responsive adjustments */
|
|
262
|
+
@media (max-width: 768px) {
|
|
263
|
+
.pagination {
|
|
264
|
+
margin: 0;
|
|
265
|
+
padding: 12px 8px;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.limit {
|
|
269
|
+
gap: 6px;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.limit p {
|
|
273
|
+
font-size: 0.75rem;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.items-per-page-select {
|
|
277
|
+
font-size: 0.75rem;
|
|
278
|
+
padding: 6px 6px;
|
|
279
|
+
min-width: 56px;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
</style>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
</script>
|
|
4
4
|
|
|
5
5
|
{#each { length: rows } as _}
|
|
6
|
-
<tr class="border-b border-
|
|
6
|
+
<tr class="border-b border-border-divider-subtle">
|
|
7
7
|
{#each { length: columns } as _}
|
|
8
8
|
<td class="px-4 py-3">
|
|
9
9
|
<div class="shimmer-skeleton h-4 w-24"></div>
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
position: relative;
|
|
18
18
|
overflow: hidden;
|
|
19
19
|
border-radius: 4px;
|
|
20
|
-
background-color:
|
|
20
|
+
background-color: var(--color-bg-surface-subtle);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
.shimmer-skeleton::after {
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
transform: translateX(-100%);
|
|
30
30
|
background-image: linear-gradient(
|
|
31
31
|
90deg,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
color-mix(in srgb, var(--color-bg-surface-subtle) 0%, transparent) 0,
|
|
33
|
+
color-mix(in srgb, var(--color-bg-surface-subtle) 20%, transparent) 20%,
|
|
34
|
+
color-mix(in srgb, var(--color-bg-surface-subtle) 50%, transparent) 80%,
|
|
35
|
+
color-mix(in srgb, var(--color-bg-surface-subtle) 100%, transparent) 100%
|
|
36
36
|
);
|
|
37
37
|
animation: shimmer 3s infinite;
|
|
38
38
|
content: '';
|
|
@@ -44,4 +44,3 @@
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
</style>
|
|
47
|
-
|
|
@@ -12,7 +12,7 @@ export type TableAction = {
|
|
|
12
12
|
actionType?: 'function' | 'alert' | 'dialog' | 'navigate' | 'modal';
|
|
13
13
|
actionValue?: string;
|
|
14
14
|
};
|
|
15
|
-
export type Column<T =
|
|
15
|
+
export type Column<T = unknown> = {
|
|
16
16
|
key?: string;
|
|
17
17
|
label: string;
|
|
18
18
|
width?: string;
|
|
@@ -21,7 +21,7 @@ export type Column<T = any> = {
|
|
|
21
21
|
sticky?: 'left' | 'right';
|
|
22
22
|
stickyOffset?: number;
|
|
23
23
|
align?: 'left' | 'center' | 'right';
|
|
24
|
-
render?: (row: T, index: number) => string | number | null | undefined |
|
|
24
|
+
render?: (row: T, index: number) => string | number | null | undefined | unknown;
|
|
25
25
|
headerClass?: string;
|
|
26
26
|
cellClass?: string;
|
|
27
27
|
showBadge?: boolean;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { writable } from 'svelte/store';
|
|
2
|
+
export const isDarkMode = writable(false);
|
|
3
|
+
export function setDarkMode(value) {
|
|
4
|
+
if (typeof document !== 'undefined') {
|
|
5
|
+
if (value) {
|
|
6
|
+
document.documentElement.setAttribute('data-theme', 'dark');
|
|
7
|
+
localStorage.setItem('theme', 'dark');
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
document.documentElement.removeAttribute('data-theme');
|
|
11
|
+
localStorage.setItem('theme', 'light');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
isDarkMode.set(value);
|
|
15
|
+
}
|
|
16
|
+
export function toggleDarkMode() {
|
|
17
|
+
isDarkMode.update((current) => {
|
|
18
|
+
const newValue = !current;
|
|
19
|
+
if (typeof document !== 'undefined') {
|
|
20
|
+
if (newValue) {
|
|
21
|
+
document.documentElement.setAttribute('data-theme', 'dark');
|
|
22
|
+
localStorage.setItem('theme', 'dark');
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
document.documentElement.removeAttribute('data-theme');
|
|
26
|
+
localStorage.setItem('theme', 'light');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return newValue;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export function initTheme() {
|
|
33
|
+
if (typeof window === 'undefined')
|
|
34
|
+
return;
|
|
35
|
+
const savedTheme = localStorage.getItem('theme');
|
|
36
|
+
// Jika tidak ada item theme maka default jadi light
|
|
37
|
+
if (!savedTheme) {
|
|
38
|
+
document.documentElement.removeAttribute('data-theme'); // light
|
|
39
|
+
localStorage.setItem('theme', 'light');
|
|
40
|
+
isDarkMode.set(false);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
44
|
+
const shouldBeDark = savedTheme === 'dark' || (!savedTheme && prefersDark);
|
|
45
|
+
if (shouldBeDark) {
|
|
46
|
+
document.documentElement.setAttribute('data-theme', 'dark');
|
|
47
|
+
localStorage.setItem('theme', 'dark');
|
|
48
|
+
isDarkMode.set(true);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
document.documentElement.removeAttribute('data-theme');
|
|
52
|
+
localStorage.setItem('theme', 'light');
|
|
53
|
+
isDarkMode.set(false);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
{
|
|
2
|
+
"light": {
|
|
3
|
+
"--color-bg-canvas": "#F4F4F5",
|
|
4
|
+
"--color-bg-surface": "#FFFFFF",
|
|
5
|
+
"--color-bg-surface-raised": "#FFFFFF",
|
|
6
|
+
"--color-bg-surface-subtle": "#F4F4F5",
|
|
7
|
+
"--color-bg-inverse": "#09090B",
|
|
8
|
+
"--color-bg-act-primary": "#FF6900",
|
|
9
|
+
"--color-bg-act-hover": "#F54900",
|
|
10
|
+
"--color-bg-act-pressed": "#CA3500",
|
|
11
|
+
"--color-bg-act-subtle": "#FFEDD4",
|
|
12
|
+
"--color-bg-disabled": "#E4E4E7",
|
|
13
|
+
"--color-bg-info-subtle": "#F6FAFE",
|
|
14
|
+
"--color-bg-success-subtle": "#F6FEF9",
|
|
15
|
+
"--color-bg-error-subtle": "#FFFBFA",
|
|
16
|
+
"--color-bg-warning-subtle": "#FFFCF5",
|
|
17
|
+
|
|
18
|
+
"--color-text-primary": "#18181B",
|
|
19
|
+
"--color-text-secondary": "#71717B",
|
|
20
|
+
"--color-text-tertiary": "#9F9FA9",
|
|
21
|
+
"--color-text-inverse": "#FFFFFF",
|
|
22
|
+
"--color-text-link": "#FF6900",
|
|
23
|
+
"--color-text-disabled": "#E4E4E7",
|
|
24
|
+
"--color-text-on-act": "#FFFFFF",
|
|
25
|
+
"--color-text-info-ti": "#3684F3",
|
|
26
|
+
"--color-text-success-ti": "#12B76A",
|
|
27
|
+
"--color-text-error-ti": "#F04438",
|
|
28
|
+
"--color-text-warning-ti": "#E9AF3D",
|
|
29
|
+
|
|
30
|
+
"--color-border-divider-subtle": "#F4F4F5",
|
|
31
|
+
"--color-border-divider-base": "#9F9FA9",
|
|
32
|
+
"--color-border-outline": "#E4E4E7",
|
|
33
|
+
"--color-border-form": "#D4D4D8",
|
|
34
|
+
"--color-border-act-primary": "#FF6900",
|
|
35
|
+
"--color-border-act-subtle": "#FFF7ED",
|
|
36
|
+
"--color-border-disabled": "#E4E4E7",
|
|
37
|
+
"--color-border-info-bor": "#3684F3",
|
|
38
|
+
"--color-border-success-bor": "#12B76A",
|
|
39
|
+
"--color-border-error-bor": "#D92D20",
|
|
40
|
+
"--color-border-warning-bor": "#E9AF3D",
|
|
41
|
+
|
|
42
|
+
"--color-chart-teal-subtle": "#F0FDFA",
|
|
43
|
+
"--color-chart-purple-subtle": "#FAF5FF",
|
|
44
|
+
"--color-chart-blue-subtle": "#EFF6FF",
|
|
45
|
+
"--color-chart-orange-subtle": "#FFF7ED",
|
|
46
|
+
"--color-chart-gray-subtle": "#FAFAFA",
|
|
47
|
+
"--color-chart-teal-solid": "#00BBA7",
|
|
48
|
+
"--color-chart-purple-solid": "#AD46FF",
|
|
49
|
+
"--color-chart-blue-solid": "#2B7FFF",
|
|
50
|
+
"--color-chart-orange-solid": "#FF6900",
|
|
51
|
+
"--color-chart-gray-solid": "#9F9FA9"
|
|
52
|
+
},
|
|
53
|
+
"dark": {
|
|
54
|
+
"--color-bg-canvas": "#09090B",
|
|
55
|
+
"--color-bg-surface": "#18181B",
|
|
56
|
+
"--color-bg-surface-raised": "#27272A",
|
|
57
|
+
"--color-bg-surface-subtle": "#09090B",
|
|
58
|
+
"--color-bg-inverse": "#FFFFFF",
|
|
59
|
+
"--color-bg-act-primary": "#FF6900",
|
|
60
|
+
"--color-bg-act-hover": "#FF8904",
|
|
61
|
+
"--color-bg-act-pressed": "#FFB86A",
|
|
62
|
+
"--color-bg-act-subtle": "#441306",
|
|
63
|
+
"--color-bg-disabled": "#27272A",
|
|
64
|
+
"--color-bg-info-subtle": "#003999",
|
|
65
|
+
"--color-bg-success-subtle": "#054F31",
|
|
66
|
+
"--color-bg-error-subtle": "#7A271A",
|
|
67
|
+
"--color-bg-warning-subtle": "#5F370E",
|
|
68
|
+
|
|
69
|
+
"--color-text-primary": "#FAFAFA",
|
|
70
|
+
"--color-text-secondary": "#9F9FA9",
|
|
71
|
+
"--color-text-tertiary": "#52525C",
|
|
72
|
+
"--color-text-inverse": "#09090B",
|
|
73
|
+
"--color-text-link": "#FF6900",
|
|
74
|
+
"--color-text-disabled": "#3F3F46",
|
|
75
|
+
"--color-text-on-act": "#FFFFFF",
|
|
76
|
+
"--color-text-info-ti": "#0169CD",
|
|
77
|
+
"--color-text-success-ti": "#12B76A",
|
|
78
|
+
"--color-text-error-ti": "#D92D20",
|
|
79
|
+
"--color-text-warning-ti": "#CA8828",
|
|
80
|
+
|
|
81
|
+
"--color-border-divider-subtle": "#27272A",
|
|
82
|
+
"--color-border-divider-base": "#71717B",
|
|
83
|
+
"--color-border-outline": "#27272A",
|
|
84
|
+
"--color-border-form": "#3F3F46",
|
|
85
|
+
"--color-border-act-primary": "#FF6900",
|
|
86
|
+
"--color-border-act-subtle": "#441306",
|
|
87
|
+
"--color-border-disabled": "#3F3F46",
|
|
88
|
+
"--color-border-info-bor": "#3684F3",
|
|
89
|
+
"--color-border-success-bor": "#12B76A",
|
|
90
|
+
"--color-border-error-bor": "#D92D20",
|
|
91
|
+
"--color-border-warning-bor": "#CA8828",
|
|
92
|
+
|
|
93
|
+
"--color-chart-teal-subtle": "#022F2E",
|
|
94
|
+
"--color-chart-purple-subtle": "#3C0366",
|
|
95
|
+
"--color-chart-blue-subtle": "#162456",
|
|
96
|
+
"--color-chart-orange-subtle": "#441306",
|
|
97
|
+
"--color-chart-gray-subtle": "#3F3F47",
|
|
98
|
+
"--color-chart-teal-solid": "#00BBA7",
|
|
99
|
+
"--color-chart-purple-solid": "#AD46FF",
|
|
100
|
+
"--color-chart-blue-solid": "#2B7FFF",
|
|
101
|
+
"--color-chart-orange-solid": "#FF6900",
|
|
102
|
+
"--color-chart-gray-solid": "#D4D4D8"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type ThemeTokens = Record<string, string>;
|
|
2
|
+
/**
|
|
3
|
+
* Menerapkan theme tokens ke CSS variables pada :root.
|
|
4
|
+
* CSS variables ini akan otomatis tersedia untuk @theme block di app.css
|
|
5
|
+
* yang menggunakan var(--variable-name).
|
|
6
|
+
*
|
|
7
|
+
* @param tokens - Object berisi CSS variable names dan values (contoh: { "--bg-canvas": "#F4F4F5" })
|
|
8
|
+
*/
|
|
9
|
+
export declare function applyTheme(tokens: ThemeTokens): void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Menerapkan theme tokens ke CSS variables pada :root.
|
|
3
|
+
* CSS variables ini akan otomatis tersedia untuk @theme block di app.css
|
|
4
|
+
* yang menggunakan var(--variable-name).
|
|
5
|
+
*
|
|
6
|
+
* @param tokens - Object berisi CSS variable names dan values (contoh: { "--bg-canvas": "#F4F4F5" })
|
|
7
|
+
*/
|
|
8
|
+
export function applyTheme(tokens) {
|
|
9
|
+
const root = document.documentElement;
|
|
10
|
+
Object.entries(tokens).forEach(([token, value]) => {
|
|
11
|
+
root.style.setProperty(token, value);
|
|
12
|
+
});
|
|
13
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mertani-web-toolkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.38",
|
|
4
4
|
"homepage": "https://storybook.mertani.com/",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite dev",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"test:e2e": "playwright test",
|
|
16
16
|
"test": "npm run test:e2e",
|
|
17
17
|
"storybook": "storybook dev -p 6006",
|
|
18
|
-
"build-storybook": "storybook build"
|
|
18
|
+
"build-storybook": "storybook build",
|
|
19
|
+
"publish": "npm run build && npm pack && npm publish"
|
|
19
20
|
},
|
|
20
21
|
"files": [
|
|
21
22
|
"dist",
|