@mdguggenbichler/slugbase-core 0.0.31 → 0.0.32
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/frontend/src/components/FilterChips.tsx +5 -3
- package/frontend/src/components/StatCard.tsx +82 -5
- package/frontend/src/components/bookmarks/BookmarkCard.tsx +317 -210
- package/frontend/src/components/bookmarks/BookmarkTableView.tsx +47 -23
- package/frontend/src/components/collections/CollectionToolbar.tsx +294 -0
- package/frontend/src/components/collections/README.md +44 -0
- package/frontend/src/components/collections/index.ts +2 -0
- package/frontend/src/components/dashboard/DashboardHeader.tsx +16 -0
- package/frontend/src/components/dashboard/MostUsedTagsSection.tsx +49 -0
- package/frontend/src/components/dashboard/PinnedSection.tsx +110 -0
- package/frontend/src/components/dashboard/QuickAccessSection.tsx +120 -0
- package/frontend/src/components/dashboard/README.md +35 -0
- package/frontend/src/components/dashboard/StatsCardsRow.tsx +78 -0
- package/frontend/src/components/dashboard/index.ts +17 -0
- package/frontend/src/locales/de.json +2 -0
- package/frontend/src/locales/en.json +1 -0
- package/frontend/src/locales/es.json +2 -0
- package/frontend/src/locales/fr.json +2 -0
- package/frontend/src/locales/it.json +2 -0
- package/frontend/src/locales/ja.json +2 -0
- package/frontend/src/locales/nl.json +2 -0
- package/frontend/src/locales/pl.json +2 -0
- package/frontend/src/locales/pt.json +2 -0
- package/frontend/src/locales/ru.json +2 -0
- package/frontend/src/locales/zh.json +2 -0
- package/frontend/src/pages/Bookmarks.tsx +97 -214
- package/frontend/src/pages/Dashboard.tsx +99 -216
- package/frontend/src/pages/Folders.tsx +181 -251
- package/frontend/src/pages/Tags.tsx +87 -145
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { X } from 'lucide-react';
|
|
2
|
+
import { Badge } from './ui/badge';
|
|
2
3
|
|
|
3
4
|
export interface FilterChipItem {
|
|
4
5
|
key: string;
|
|
@@ -20,9 +21,10 @@ export function FilterChips({ chips, onRemove, onClearAll, clearAllLabel, clearA
|
|
|
20
21
|
return (
|
|
21
22
|
<div className="flex flex-wrap items-center gap-2">
|
|
22
23
|
{chips.map(({ key, label, ariaLabel }) => (
|
|
23
|
-
<
|
|
24
|
+
<Badge
|
|
24
25
|
key={key}
|
|
25
|
-
|
|
26
|
+
variant="secondary"
|
|
27
|
+
className="inline-flex items-center gap-1.5 pl-2.5 pr-1 py-1 text-sm font-normal"
|
|
26
28
|
>
|
|
27
29
|
<span>{label}</span>
|
|
28
30
|
<button
|
|
@@ -39,7 +41,7 @@ export function FilterChips({ chips, onRemove, onClearAll, clearAllLabel, clearA
|
|
|
39
41
|
>
|
|
40
42
|
<X className="h-3.5 w-3.5" />
|
|
41
43
|
</button>
|
|
42
|
-
</
|
|
44
|
+
</Badge>
|
|
43
45
|
))}
|
|
44
46
|
<button
|
|
45
47
|
type="button"
|
|
@@ -3,6 +3,18 @@ import { LucideIcon } from 'lucide-react';
|
|
|
3
3
|
import { Card, CardContent } from './ui/card';
|
|
4
4
|
import { cn } from '@/lib/utils';
|
|
5
5
|
|
|
6
|
+
/** Optional usage display for plan/limit (e.g. slugbase-cloud). Only rendered when used + limit are both set. */
|
|
7
|
+
export interface StatCardUsageProps {
|
|
8
|
+
used: number;
|
|
9
|
+
limit: number;
|
|
10
|
+
/** e.g. "Bookmarks used" */
|
|
11
|
+
labelOverride?: string;
|
|
12
|
+
showProgress?: boolean;
|
|
13
|
+
progressVariant?: 'normal' | 'warning' | 'danger';
|
|
14
|
+
/** Upgrade CTA; only pass in cloud when plan limits apply */
|
|
15
|
+
cta?: { label: string; onClick: () => void };
|
|
16
|
+
}
|
|
17
|
+
|
|
6
18
|
interface StatCardProps {
|
|
7
19
|
label: string;
|
|
8
20
|
value: string | number;
|
|
@@ -15,21 +27,86 @@ interface StatCardProps {
|
|
|
15
27
|
/** Tailwind classes for the icon color (e.g. text-blue-600 dark:text-blue-400) */
|
|
16
28
|
iconColorClassName?: string;
|
|
17
29
|
className?: string;
|
|
30
|
+
/** Optional secondary line below value (e.g. "+12 this week") */
|
|
31
|
+
secondaryLine?: string;
|
|
32
|
+
/** Optional usage/limit display for cloud plan; only shown when both used and limit are provided */
|
|
33
|
+
used?: number;
|
|
34
|
+
limit?: number;
|
|
35
|
+
labelOverride?: string;
|
|
36
|
+
showProgress?: boolean;
|
|
37
|
+
progressVariant?: 'normal' | 'warning' | 'danger';
|
|
38
|
+
cta?: { label: string; onClick: () => void };
|
|
18
39
|
}
|
|
19
40
|
|
|
20
|
-
export function StatCard({
|
|
41
|
+
export function StatCard({
|
|
42
|
+
label,
|
|
43
|
+
value,
|
|
44
|
+
icon: Icon,
|
|
45
|
+
href,
|
|
46
|
+
dense,
|
|
47
|
+
iconContainerClassName,
|
|
48
|
+
iconColorClassName,
|
|
49
|
+
className,
|
|
50
|
+
secondaryLine,
|
|
51
|
+
used: usedProp,
|
|
52
|
+
limit: limitProp,
|
|
53
|
+
labelOverride,
|
|
54
|
+
showProgress = true,
|
|
55
|
+
progressVariant = 'normal',
|
|
56
|
+
cta,
|
|
57
|
+
}: StatCardProps) {
|
|
58
|
+
const hasUsage = usedProp != null && limitProp != null;
|
|
59
|
+
const used = hasUsage ? usedProp : 0;
|
|
60
|
+
const limit = hasUsage ? limitProp : 0;
|
|
61
|
+
const showProgressBar = hasUsage && showProgress;
|
|
62
|
+
const progressPercent = limit > 0 ? Math.min(100, (used / limit) * 100) : 0;
|
|
63
|
+
|
|
21
64
|
const content = (
|
|
22
65
|
<>
|
|
23
66
|
<div className="flex items-center justify-between">
|
|
24
|
-
<div>
|
|
25
|
-
<p className={cn('
|
|
67
|
+
<div className="min-w-0 flex-1">
|
|
68
|
+
<p className={cn('text-muted-foreground', dense ? 'text-xs' : 'text-sm')}>
|
|
26
69
|
{label}
|
|
27
70
|
</p>
|
|
28
|
-
<p className={cn('font-semibold mt-
|
|
71
|
+
<p className={cn('font-semibold mt-1 text-foreground', dense ? 'text-xl' : 'text-2xl')}>
|
|
29
72
|
{value}
|
|
30
73
|
</p>
|
|
74
|
+
{secondaryLine && (
|
|
75
|
+
<p className="mt-0.5 text-xs text-muted-foreground">{secondaryLine}</p>
|
|
76
|
+
)}
|
|
77
|
+
{hasUsage && (
|
|
78
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
79
|
+
{labelOverride ?? label} {used} / {limit}
|
|
80
|
+
</p>
|
|
81
|
+
)}
|
|
82
|
+
{showProgressBar && (
|
|
83
|
+
<div className="mt-2 w-full overflow-hidden rounded-full bg-primary/20">
|
|
84
|
+
<div
|
|
85
|
+
className={cn(
|
|
86
|
+
'h-2 transition-all',
|
|
87
|
+
progressVariant === 'warning' && 'bg-amber-500',
|
|
88
|
+
progressVariant === 'danger' && 'bg-destructive',
|
|
89
|
+
(progressVariant === 'normal' || !progressVariant) && 'bg-primary'
|
|
90
|
+
)}
|
|
91
|
+
style={{ width: `${progressPercent}%` }}
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
)}
|
|
95
|
+
{cta && (
|
|
96
|
+
<button
|
|
97
|
+
type="button"
|
|
98
|
+
onClick={(e) => {
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
e.stopPropagation();
|
|
101
|
+
cta.onClick();
|
|
102
|
+
}}
|
|
103
|
+
className="mt-2 text-xs font-medium text-primary hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded"
|
|
104
|
+
>
|
|
105
|
+
{cta.label}
|
|
106
|
+
</button>
|
|
107
|
+
)}
|
|
31
108
|
</div>
|
|
32
|
-
<div className={cn('rounded-lg', iconContainerClassName ?? 'bg-muted', dense ? 'p-2' : 'p-3')}>
|
|
109
|
+
<div className={cn('shrink-0 rounded-lg', iconContainerClassName ?? 'bg-muted', dense ? 'p-2' : 'p-3')}>
|
|
33
110
|
<Icon className={cn(iconColorClassName ?? 'text-muted-foreground', dense ? 'h-5 w-5' : 'h-6 w-6')} />
|
|
34
111
|
</div>
|
|
35
112
|
</div>
|