@open-mercato/ui 0.4.11-develop.2631.481e9df5b0 → 0.5.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/.turbo/turbo-build.log +2 -2
- package/AGENTS.md +28 -4
- package/agentic/standalone-guide.md +97 -0
- package/build.mjs +10 -6
- package/dist/backend/AppShell.js +15 -2
- package/dist/backend/AppShell.js.map +2 -2
- package/dist/backend/DataTable.js +22 -1
- package/dist/backend/DataTable.js.map +2 -2
- package/dist/backend/detail/CustomDataSection.js +1 -5
- package/dist/backend/detail/CustomDataSection.js.map +2 -2
- package/dist/backend/detail/InlineEditors.js +2 -5
- package/dist/backend/detail/InlineEditors.js.map +2 -2
- package/dist/backend/detail/NotesSection.js +2 -6
- package/dist/backend/detail/NotesSection.js.map +2 -2
- package/dist/backend/icons/lucideRegistry.generated.js +93 -3
- package/dist/backend/icons/lucideRegistry.generated.js.map +2 -2
- package/dist/backend/markdown/MarkdownContent.js +47 -4
- package/dist/backend/markdown/MarkdownContent.js.map +2 -2
- package/dist/portal/PortalShell.js +41 -11
- package/dist/portal/PortalShell.js.map +2 -2
- package/dist/portal/hooks/usePortalDashboardWidgets.js +40 -1
- package/dist/portal/hooks/usePortalDashboardWidgets.js.map +2 -2
- package/dist/portal/utils/nav.js +84 -0
- package/dist/portal/utils/nav.js.map +7 -0
- package/package.json +13 -9
- package/src/backend/AppShell.tsx +22 -2
- package/src/backend/DataTable.tsx +28 -5
- package/src/backend/__tests__/AppShell.test.tsx +67 -0
- package/src/backend/__tests__/FormHeader.test.tsx +0 -1
- package/src/backend/detail/CustomDataSection.tsx +1 -10
- package/src/backend/detail/InlineEditors.tsx +3 -15
- package/src/backend/detail/NotesSection.tsx +5 -14
- package/src/backend/icons/lucideRegistry.generated.tsx +93 -3
- package/src/backend/injection/__tests__/resolveInjectedIcon.test.tsx +7 -0
- package/src/backend/markdown/MarkdownContent.tsx +76 -6
- package/src/backend/section-page/types.ts +1 -0
- package/src/portal/PortalShell.tsx +43 -11
- package/src/portal/hooks/__tests__/usePortalDashboardWidgets.test.tsx +117 -0
- package/src/portal/hooks/usePortalDashboardWidgets.ts +55 -1
- package/src/portal/utils/__tests__/nav.test.ts +199 -0
- package/src/portal/utils/nav.ts +150 -0
package/src/backend/AppShell.tsx
CHANGED
|
@@ -54,6 +54,7 @@ export type AppShellProps = {
|
|
|
54
54
|
title: string
|
|
55
55
|
defaultTitle?: string
|
|
56
56
|
icon?: React.ReactNode
|
|
57
|
+
iconName?: string
|
|
57
58
|
iconMarkup?: string
|
|
58
59
|
enabled?: boolean
|
|
59
60
|
hidden?: boolean
|
|
@@ -64,6 +65,7 @@ export type AppShellProps = {
|
|
|
64
65
|
title: string
|
|
65
66
|
defaultTitle?: string
|
|
66
67
|
icon?: React.ReactNode
|
|
68
|
+
iconName?: string
|
|
67
69
|
iconMarkup?: string
|
|
68
70
|
enabled?: boolean
|
|
69
71
|
hidden?: boolean
|
|
@@ -109,6 +111,7 @@ function convertInjectedMenuItemToSidebarItem(item: InjectionMenuItem, title: st
|
|
|
109
111
|
title,
|
|
110
112
|
defaultTitle: title,
|
|
111
113
|
icon: resolveInjectedIcon(item.icon) ?? undefined,
|
|
114
|
+
iconName: item.icon,
|
|
112
115
|
enabled: true,
|
|
113
116
|
hidden: false,
|
|
114
117
|
pageContext: 'main',
|
|
@@ -302,8 +305,17 @@ function SerializedIcon({ markup }: { markup: string }) {
|
|
|
302
305
|
return <span aria-hidden="true" dangerouslySetInnerHTML={{ __html: markup }} />
|
|
303
306
|
}
|
|
304
307
|
|
|
305
|
-
function renderIcon(
|
|
308
|
+
function renderIcon(
|
|
309
|
+
icon: React.ReactNode | undefined,
|
|
310
|
+
iconName: string | undefined,
|
|
311
|
+
iconMarkup: string | undefined,
|
|
312
|
+
fallback: React.ReactNode,
|
|
313
|
+
) {
|
|
306
314
|
if (icon) return icon
|
|
315
|
+
if (iconName) {
|
|
316
|
+
const resolved = resolveInjectedIcon(iconName)
|
|
317
|
+
if (resolved) return resolved
|
|
318
|
+
}
|
|
307
319
|
if (iconMarkup) return <SerializedIcon markup={iconMarkup} />
|
|
308
320
|
return fallback
|
|
309
321
|
}
|
|
@@ -862,6 +874,7 @@ function AppShellBody({ productName, email, groups, rightHeaderSlot, children, s
|
|
|
862
874
|
<span className={`flex items-center justify-center shrink-0 ${compact ? '' : 'text-muted-foreground'}`}>
|
|
863
875
|
{renderIcon(
|
|
864
876
|
item.icon,
|
|
877
|
+
item.iconName,
|
|
865
878
|
item.iconMarkup,
|
|
866
879
|
item.href.includes('/backend/entities/user/') && item.href.endsWith('/records') ? DataTableIcon : DefaultIcon,
|
|
867
880
|
)}
|
|
@@ -1243,7 +1256,12 @@ function AppShellBody({ productName, email, groups, rightHeaderSlot, children, s
|
|
|
1243
1256
|
<span className="absolute left-0 top-1 bottom-1 w-0.5 rounded bg-foreground" />
|
|
1244
1257
|
) : null}
|
|
1245
1258
|
<span className={`flex items-center justify-center shrink-0 ${compact ? '' : 'text-muted-foreground'}`}>
|
|
1246
|
-
{renderIcon(
|
|
1259
|
+
{renderIcon(
|
|
1260
|
+
i.icon,
|
|
1261
|
+
i.iconName,
|
|
1262
|
+
i.iconMarkup,
|
|
1263
|
+
DefaultIcon,
|
|
1264
|
+
)}
|
|
1247
1265
|
</span>
|
|
1248
1266
|
{!compact && <span>{i.title}</span>}
|
|
1249
1267
|
</Link>
|
|
@@ -1270,6 +1288,7 @@ function AppShellBody({ productName, email, groups, rightHeaderSlot, children, s
|
|
|
1270
1288
|
<span className={`flex items-center justify-center shrink-0 ${compact ? '' : 'text-muted-foreground'}`}>
|
|
1271
1289
|
{renderIcon(
|
|
1272
1290
|
c.icon,
|
|
1291
|
+
c.iconName,
|
|
1273
1292
|
c.iconMarkup,
|
|
1274
1293
|
c.href.includes('/backend/entities/user/') && c.href.endsWith('/records') ? DataTableIcon : DefaultIcon,
|
|
1275
1294
|
)}
|
|
@@ -1564,6 +1583,7 @@ AppShell.cloneGroups = function cloneGroups(groups: AppShellProps['groups']): Ap
|
|
|
1564
1583
|
title: item.title,
|
|
1565
1584
|
defaultTitle: item.defaultTitle,
|
|
1566
1585
|
icon: item.icon,
|
|
1586
|
+
iconName: item.iconName,
|
|
1567
1587
|
iconMarkup: item.iconMarkup,
|
|
1568
1588
|
enabled: item.enabled,
|
|
1569
1589
|
hidden: item.hidden,
|
|
@@ -677,11 +677,34 @@ function ExportMenu({ config, sections }: { config: DataTableExportConfig; secti
|
|
|
677
677
|
}
|
|
678
678
|
|
|
679
679
|
function sanitizeDndContextId(value: string): string {
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
680
|
+
const trimmed = value.trim().toLowerCase()
|
|
681
|
+
let normalized = ''
|
|
682
|
+
let previousWasDash = false
|
|
683
|
+
|
|
684
|
+
for (const character of trimmed) {
|
|
685
|
+
const isLowercaseLetter = character >= 'a' && character <= 'z'
|
|
686
|
+
const isDigit = character >= '0' && character <= '9'
|
|
687
|
+
|
|
688
|
+
if (isLowercaseLetter || isDigit || character === '_') {
|
|
689
|
+
normalized += character
|
|
690
|
+
previousWasDash = false
|
|
691
|
+
continue
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
if (!previousWasDash) {
|
|
695
|
+
normalized += '-'
|
|
696
|
+
previousWasDash = true
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
while (normalized.startsWith('-')) {
|
|
701
|
+
normalized = normalized.slice(1)
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
while (normalized.endsWith('-')) {
|
|
705
|
+
normalized = normalized.slice(0, -1)
|
|
706
|
+
}
|
|
707
|
+
|
|
685
708
|
return normalized.length > 0 ? normalized : 'data-table'
|
|
686
709
|
}
|
|
687
710
|
|
|
@@ -383,4 +383,71 @@ describe('AppShell', () => {
|
|
|
383
383
|
;(window as Window & { __omOriginalFetch?: typeof fetch }).__omOriginalFetch = previousOriginalFetch
|
|
384
384
|
}
|
|
385
385
|
})
|
|
386
|
+
|
|
387
|
+
it('renders nav icons from iconName when iconMarkup is missing', async () => {
|
|
388
|
+
const previousFetch = global.fetch
|
|
389
|
+
const previousWindowFetch = window.fetch
|
|
390
|
+
const previousOriginalFetch = (window as Window & { __omOriginalFetch?: typeof fetch }).__omOriginalFetch
|
|
391
|
+
const fetchMock = jest.fn(async (input: RequestInfo | URL) => {
|
|
392
|
+
const url = typeof input === 'string'
|
|
393
|
+
? input
|
|
394
|
+
: input instanceof Request
|
|
395
|
+
? input.url
|
|
396
|
+
: input.toString()
|
|
397
|
+
if (url.includes('/api/auth/admin/nav-icon-fallback')) {
|
|
398
|
+
return new Response(JSON.stringify({
|
|
399
|
+
groups: [
|
|
400
|
+
{
|
|
401
|
+
id: 'checkout',
|
|
402
|
+
name: 'Checkout',
|
|
403
|
+
defaultName: 'Checkout',
|
|
404
|
+
items: [
|
|
405
|
+
{
|
|
406
|
+
href: '/backend/checkout/pay-links',
|
|
407
|
+
title: 'Pay Links',
|
|
408
|
+
defaultTitle: 'Pay Links',
|
|
409
|
+
enabled: true,
|
|
410
|
+
iconName: 'ticket',
|
|
411
|
+
},
|
|
412
|
+
],
|
|
413
|
+
},
|
|
414
|
+
],
|
|
415
|
+
settingsSections: [],
|
|
416
|
+
settingsPathPrefixes: [],
|
|
417
|
+
profileSections: [],
|
|
418
|
+
profilePathPrefixes: ['/backend/profile/'],
|
|
419
|
+
grantedFeatures: ['checkout.view'],
|
|
420
|
+
roles: ['admin'],
|
|
421
|
+
}), { status: 200, headers: { 'content-type': 'application/json' } })
|
|
422
|
+
}
|
|
423
|
+
return new Response(JSON.stringify([]), { status: 200, headers: { 'content-type': 'application/json' } })
|
|
424
|
+
}) as unknown as typeof fetch
|
|
425
|
+
global.fetch = fetchMock
|
|
426
|
+
window.fetch = fetchMock
|
|
427
|
+
;(window as Window & { __omOriginalFetch?: typeof fetch }).__omOriginalFetch = fetchMock
|
|
428
|
+
|
|
429
|
+
try {
|
|
430
|
+
renderWithProviders(
|
|
431
|
+
<AppShell
|
|
432
|
+
email="demo@example.com"
|
|
433
|
+
groups={[]}
|
|
434
|
+
adminNavApi="/api/auth/admin/nav-icon-fallback"
|
|
435
|
+
>
|
|
436
|
+
<div>Hydrated content</div>
|
|
437
|
+
</AppShell>,
|
|
438
|
+
{ dict },
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
await waitFor(() => {
|
|
442
|
+
expect(screen.getByText('Pay Links')).toBeInTheDocument()
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
const link = screen.getByRole('link', { name: 'Pay Links' })
|
|
446
|
+
expect(link.querySelector('svg.lucide-ticket')).toBeTruthy()
|
|
447
|
+
} finally {
|
|
448
|
+
global.fetch = previousFetch
|
|
449
|
+
window.fetch = previousWindowFetch
|
|
450
|
+
;(window as Window & { __omOriginalFetch?: typeof fetch }).__omOriginalFetch = previousOriginalFetch
|
|
451
|
+
}
|
|
452
|
+
})
|
|
386
453
|
})
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import Link from 'next/link'
|
|
5
|
-
import dynamic from 'next/dynamic'
|
|
6
5
|
import type { PluggableList } from 'unified'
|
|
7
6
|
import { Pencil, X } from 'lucide-react'
|
|
8
7
|
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
|
@@ -30,19 +29,11 @@ import {
|
|
|
30
29
|
import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
31
30
|
import { cn } from '@open-mercato/shared/lib/utils'
|
|
32
31
|
import { ComponentReplacementHandles } from '@open-mercato/shared/modules/widgets/component-registry'
|
|
32
|
+
import { MarkdownPreview } from '../markdown'
|
|
33
33
|
import { useRegisteredComponent } from '../injection/useRegisteredComponent'
|
|
34
34
|
|
|
35
|
-
type MarkdownPreviewProps = { children: string; className?: string; remarkPlugins?: PluggableList }
|
|
36
|
-
|
|
37
35
|
const isTestEnv = typeof process !== 'undefined' && process.env.NODE_ENV === 'test'
|
|
38
36
|
|
|
39
|
-
const MarkdownPreview: React.ComponentType<MarkdownPreviewProps> = isTestEnv
|
|
40
|
-
? ({ children, className }) => <div className={className}>{children}</div>
|
|
41
|
-
: (dynamic(() => import('react-markdown').then((mod) => mod.default as React.ComponentType<MarkdownPreviewProps>), {
|
|
42
|
-
ssr: false,
|
|
43
|
-
loading: () => null,
|
|
44
|
-
}) as unknown as React.ComponentType<MarkdownPreviewProps>)
|
|
45
|
-
|
|
46
37
|
let markdownPluginsPromise: Promise<PluggableList> | null = null
|
|
47
38
|
|
|
48
39
|
async function loadMarkdownPlugins(): Promise<PluggableList> {
|
|
@@ -11,6 +11,7 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
|
11
11
|
import { cn } from '@open-mercato/shared/lib/utils'
|
|
12
12
|
import { LoadingMessage } from './LoadingMessage'
|
|
13
13
|
import { mapCrudServerErrorToFormErrors } from '../utils/serverErrors'
|
|
14
|
+
import { MarkdownPreview } from '../markdown'
|
|
14
15
|
|
|
15
16
|
function resolveInlineErrorMessage(err: unknown, fallbackMessage: string): string {
|
|
16
17
|
const { message, fieldErrors } = mapCrudServerErrorToFormErrors(err)
|
|
@@ -390,12 +391,6 @@ type UiMarkdownEditorProps = {
|
|
|
390
391
|
previewOptions?: { remarkPlugins?: unknown[] }
|
|
391
392
|
}
|
|
392
393
|
|
|
393
|
-
type MarkdownPreviewProps = {
|
|
394
|
-
children: string
|
|
395
|
-
className?: string
|
|
396
|
-
remarkPlugins?: PluggableList
|
|
397
|
-
}
|
|
398
|
-
|
|
399
394
|
const isTestEnv = typeof process !== 'undefined' && process.env.NODE_ENV === 'test'
|
|
400
395
|
|
|
401
396
|
function MarkdownEditorFallback() {
|
|
@@ -421,13 +416,6 @@ const MarkdownEditorComponent: React.ComponentType<UiMarkdownEditorProps> = isTe
|
|
|
421
416
|
loading: () => <MarkdownEditorFallback />,
|
|
422
417
|
}) as unknown as React.ComponentType<UiMarkdownEditorProps>)
|
|
423
418
|
|
|
424
|
-
const MarkdownPreviewComponent: React.ComponentType<MarkdownPreviewProps> = isTestEnv
|
|
425
|
-
? ({ children, className }) => <div className={className}>{children}</div>
|
|
426
|
-
: (dynamic(() => import('react-markdown').then((mod) => mod.default as React.ComponentType<MarkdownPreviewProps>), {
|
|
427
|
-
ssr: false,
|
|
428
|
-
loading: () => null,
|
|
429
|
-
}) as unknown as React.ComponentType<MarkdownPreviewProps>)
|
|
430
|
-
|
|
431
419
|
let markdownPluginsPromise: Promise<PluggableList> | null = null
|
|
432
420
|
|
|
433
421
|
async function loadMarkdownPlugins(): Promise<PluggableList> {
|
|
@@ -708,12 +696,12 @@ export function InlineMultilineEditor({
|
|
|
708
696
|
{renderDisplay ? (
|
|
709
697
|
renderDisplay({ value, emptyLabel })
|
|
710
698
|
) : value && value.length ? (
|
|
711
|
-
<
|
|
699
|
+
<MarkdownPreview
|
|
712
700
|
remarkPlugins={markdownPlugins}
|
|
713
701
|
className="prose prose-sm max-w-none text-foreground [&>*]:my-2 [&>*:last-child]:mb-0 [&_pre]:rounded-md [&_pre]:bg-muted [&_pre]:p-3 [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5"
|
|
714
702
|
>
|
|
715
703
|
{value}
|
|
716
|
-
</
|
|
704
|
+
</MarkdownPreview>
|
|
717
705
|
) : (
|
|
718
706
|
<span className="text-muted-foreground">{emptyLabel}</span>
|
|
719
707
|
)}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
-
import dynamic from 'next/dynamic'
|
|
5
4
|
import type { PluggableList } from 'unified'
|
|
6
5
|
import type { AppearanceSelectorLabels } from '@open-mercato/core/modules/dictionaries/components/AppearanceSelector'
|
|
7
6
|
import { AppearanceDialog } from '@open-mercato/core/modules/customers/components/detail/AppearanceDialog'
|
|
@@ -17,9 +16,12 @@ import { TabEmptyState } from './TabEmptyState'
|
|
|
17
16
|
import { useConfirmDialog } from '../confirm-dialog'
|
|
18
17
|
import { formatDateTime } from '@open-mercato/shared/lib/time'
|
|
19
18
|
import { ComponentReplacementHandles } from '@open-mercato/shared/modules/widgets/component-registry'
|
|
19
|
+
import { MarkdownPreview } from '../markdown'
|
|
20
20
|
import { useRegisteredComponent } from '../injection/useRegisteredComponent'
|
|
21
21
|
type Translator = (key: string, fallback?: string, params?: Record<string, string | number>) => string
|
|
22
22
|
|
|
23
|
+
const isTestEnv = typeof process !== 'undefined' && process.env.NODE_ENV === 'test'
|
|
24
|
+
|
|
23
25
|
export type SectionAction = {
|
|
24
26
|
label: React.ReactNode
|
|
25
27
|
onClick: () => void
|
|
@@ -70,17 +72,6 @@ export type NotesDataAdapter<C = unknown> = {
|
|
|
70
72
|
type RenderIconFn = (icon: string, className?: string) => React.ReactNode
|
|
71
73
|
type RenderColorFn = (color: string, className?: string) => React.ReactNode
|
|
72
74
|
|
|
73
|
-
type MarkdownPreviewProps = { children: string; className?: string; remarkPlugins?: PluggableList }
|
|
74
|
-
|
|
75
|
-
const isTestEnv = typeof process !== 'undefined' && process.env.NODE_ENV === 'test'
|
|
76
|
-
|
|
77
|
-
const MarkdownPreviewComponent: React.ComponentType<MarkdownPreviewProps> = isTestEnv
|
|
78
|
-
? ({ children, className }) => <div className={className}>{children}</div>
|
|
79
|
-
: (dynamic(() => import('react-markdown').then((mod) => mod.default as React.ComponentType<MarkdownPreviewProps>), {
|
|
80
|
-
ssr: false,
|
|
81
|
-
loading: () => null,
|
|
82
|
-
}) as unknown as React.ComponentType<MarkdownPreviewProps>)
|
|
83
|
-
|
|
84
75
|
let markdownPluginsPromise: Promise<PluggableList> | null = null
|
|
85
76
|
|
|
86
77
|
async function loadMarkdownPlugins(): Promise<PluggableList> {
|
|
@@ -1193,12 +1184,12 @@ function NotesSectionImpl<C = unknown>({
|
|
|
1193
1184
|
onClick={() => setContentEditor({ id: note.id, value: note.body })}
|
|
1194
1185
|
onKeyDown={(event) => handleContentKeyDown(event, note)}
|
|
1195
1186
|
>
|
|
1196
|
-
<
|
|
1187
|
+
<MarkdownPreview
|
|
1197
1188
|
remarkPlugins={markdownPlugins}
|
|
1198
1189
|
className="break-words text-foreground [&>*]:mb-2 [&>*:last-child]:mb-0 [&_ul]:ml-4 [&_ul]:list-disc [&_ol]:ml-4 [&_ol]:list-decimal [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_pre]:rounded-md [&_pre]:bg-muted [&_pre]:p-3 [&_pre]:text-xs"
|
|
1199
1190
|
>
|
|
1200
1191
|
{note.body}
|
|
1201
|
-
</
|
|
1192
|
+
</MarkdownPreview>
|
|
1202
1193
|
</div>
|
|
1203
1194
|
)}
|
|
1204
1195
|
</div>
|
|
@@ -5,12 +5,20 @@
|
|
|
5
5
|
import type * as React from 'react'
|
|
6
6
|
import type { LucideIcon } from 'lucide-react'
|
|
7
7
|
import {
|
|
8
|
+
Activity,
|
|
8
9
|
AlertCircle,
|
|
10
|
+
AlertOctagon,
|
|
9
11
|
AlertTriangle,
|
|
12
|
+
Archive,
|
|
13
|
+
Award,
|
|
10
14
|
BadgeCheck,
|
|
15
|
+
Ban,
|
|
16
|
+
Banknote,
|
|
11
17
|
BarChart2,
|
|
18
|
+
BarChart3,
|
|
12
19
|
Bell,
|
|
13
20
|
Bolt,
|
|
21
|
+
Bookmark,
|
|
14
22
|
Box,
|
|
15
23
|
Briefcase,
|
|
16
24
|
BriefcaseBusiness,
|
|
@@ -19,11 +27,16 @@ import {
|
|
|
19
27
|
Calendar,
|
|
20
28
|
CalendarCheck,
|
|
21
29
|
CalendarClock,
|
|
30
|
+
CalendarCog,
|
|
31
|
+
CalendarMinus,
|
|
22
32
|
CalendarOff,
|
|
23
33
|
CalendarX,
|
|
24
34
|
Check,
|
|
25
35
|
CheckCircle,
|
|
36
|
+
CheckCircle2,
|
|
26
37
|
CheckSquare,
|
|
38
|
+
Circle,
|
|
39
|
+
ClipboardCheck,
|
|
27
40
|
ClipboardList,
|
|
28
41
|
Clock,
|
|
29
42
|
Clock3,
|
|
@@ -35,68 +48,107 @@ import {
|
|
|
35
48
|
Download,
|
|
36
49
|
ExternalLink,
|
|
37
50
|
FileMinus,
|
|
51
|
+
FilePenLine,
|
|
38
52
|
FileText,
|
|
39
53
|
FilterX,
|
|
54
|
+
Flag,
|
|
40
55
|
FolderTree,
|
|
56
|
+
Gauge,
|
|
41
57
|
GitBranch,
|
|
42
58
|
GitCompareArrows,
|
|
43
59
|
GitPullRequestArrow,
|
|
60
|
+
Globe,
|
|
61
|
+
GraduationCap,
|
|
62
|
+
Hand,
|
|
44
63
|
Handshake,
|
|
45
64
|
Heart,
|
|
65
|
+
Hourglass,
|
|
46
66
|
Inbox,
|
|
47
67
|
Key,
|
|
48
68
|
KeyRound,
|
|
49
69
|
Layers,
|
|
70
|
+
Lightbulb,
|
|
50
71
|
LineChart,
|
|
72
|
+
Link,
|
|
51
73
|
List,
|
|
74
|
+
Loader,
|
|
75
|
+
Loader2,
|
|
52
76
|
Lock,
|
|
53
77
|
Mail,
|
|
54
78
|
MailOpen,
|
|
55
79
|
MapPin,
|
|
80
|
+
Megaphone,
|
|
81
|
+
Notebook,
|
|
56
82
|
Package,
|
|
83
|
+
PackageCheck,
|
|
57
84
|
PackagePlus,
|
|
58
85
|
PackageX,
|
|
86
|
+
PauseCircle,
|
|
59
87
|
Percent,
|
|
88
|
+
Phone,
|
|
89
|
+
PhoneCall,
|
|
60
90
|
PieChart,
|
|
61
91
|
PlusSquare,
|
|
62
92
|
Receipt,
|
|
63
93
|
ReceiptText,
|
|
94
|
+
RefreshCcw,
|
|
64
95
|
Reply,
|
|
96
|
+
RotateCcw,
|
|
65
97
|
Ruler,
|
|
98
|
+
Send,
|
|
66
99
|
Settings,
|
|
67
100
|
Shapes,
|
|
68
101
|
Shield,
|
|
69
102
|
ShieldAlert,
|
|
70
103
|
ShieldCheck,
|
|
104
|
+
ShoppingBag,
|
|
71
105
|
ShoppingCart,
|
|
106
|
+
Shuffle,
|
|
72
107
|
Sliders,
|
|
73
108
|
Smartphone,
|
|
109
|
+
Sparkles,
|
|
110
|
+
Star,
|
|
74
111
|
StickyNote,
|
|
75
112
|
Store,
|
|
76
113
|
Tag,
|
|
114
|
+
Target,
|
|
115
|
+
ThumbsUp,
|
|
77
116
|
Ticket,
|
|
78
117
|
Trash2,
|
|
79
118
|
TrendingUp,
|
|
119
|
+
TriangleAlert,
|
|
80
120
|
Trophy,
|
|
81
121
|
Truck,
|
|
122
|
+
Undo2,
|
|
82
123
|
Unlock,
|
|
83
124
|
User,
|
|
125
|
+
UserCheck,
|
|
84
126
|
UserMinus,
|
|
85
127
|
UserPlus,
|
|
86
128
|
UserRound,
|
|
87
129
|
Users,
|
|
130
|
+
Wallet,
|
|
88
131
|
Webhook,
|
|
132
|
+
Wrench,
|
|
89
133
|
X,
|
|
90
134
|
XCircle,
|
|
91
135
|
} from 'lucide-react'
|
|
92
136
|
|
|
93
137
|
export const LUCIDE_ICON_REGISTRY: Record<string, LucideIcon> = {
|
|
138
|
+
'activity': Activity,
|
|
94
139
|
'alert-circle': AlertCircle,
|
|
140
|
+
'alert-octagon': AlertOctagon,
|
|
95
141
|
'alert-triangle': AlertTriangle,
|
|
142
|
+
'archive': Archive,
|
|
143
|
+
'award': Award,
|
|
96
144
|
'badge-check': BadgeCheck,
|
|
145
|
+
'ban': Ban,
|
|
146
|
+
'banknote': Banknote,
|
|
97
147
|
'bar-chart-2': BarChart2,
|
|
148
|
+
'bar-chart-3': BarChart3,
|
|
98
149
|
'bell': Bell,
|
|
99
150
|
'bolt': Bolt,
|
|
151
|
+
'bookmark': Bookmark,
|
|
100
152
|
'box': Box,
|
|
101
153
|
'briefcase': Briefcase,
|
|
102
154
|
'briefcase-business': BriefcaseBusiness,
|
|
@@ -105,11 +157,16 @@ export const LUCIDE_ICON_REGISTRY: Record<string, LucideIcon> = {
|
|
|
105
157
|
'calendar': Calendar,
|
|
106
158
|
'calendar-check': CalendarCheck,
|
|
107
159
|
'calendar-clock': CalendarClock,
|
|
160
|
+
'calendar-cog': CalendarCog,
|
|
161
|
+
'calendar-minus': CalendarMinus,
|
|
108
162
|
'calendar-off': CalendarOff,
|
|
109
163
|
'calendar-x': CalendarX,
|
|
110
164
|
'check': Check,
|
|
111
165
|
'check-circle': CheckCircle,
|
|
166
|
+
'check-circle-2': CheckCircle2,
|
|
112
167
|
'check-square': CheckSquare,
|
|
168
|
+
'circle': Circle,
|
|
169
|
+
'clipboard-check': ClipboardCheck,
|
|
113
170
|
'clipboard-list': ClipboardList,
|
|
114
171
|
'clock': Clock,
|
|
115
172
|
'clock-3': Clock3,
|
|
@@ -121,57 +178,88 @@ export const LUCIDE_ICON_REGISTRY: Record<string, LucideIcon> = {
|
|
|
121
178
|
'download': Download,
|
|
122
179
|
'external-link': ExternalLink,
|
|
123
180
|
'file-minus': FileMinus,
|
|
181
|
+
'file-pen-line': FilePenLine,
|
|
124
182
|
'file-text': FileText,
|
|
125
183
|
'filter-x': FilterX,
|
|
184
|
+
'flag': Flag,
|
|
126
185
|
'folder-tree': FolderTree,
|
|
186
|
+
'gauge': Gauge,
|
|
127
187
|
'git-branch': GitBranch,
|
|
128
188
|
'git-compare-arrows': GitCompareArrows,
|
|
129
189
|
'git-pull-request-arrow': GitPullRequestArrow,
|
|
190
|
+
'globe': Globe,
|
|
191
|
+
'graduation-cap': GraduationCap,
|
|
192
|
+
'hand': Hand,
|
|
130
193
|
'handshake': Handshake,
|
|
131
194
|
'heart': Heart,
|
|
195
|
+
'hourglass': Hourglass,
|
|
132
196
|
'inbox': Inbox,
|
|
133
197
|
'key': Key,
|
|
134
198
|
'key-round': KeyRound,
|
|
135
199
|
'layers': Layers,
|
|
200
|
+
'lightbulb': Lightbulb,
|
|
136
201
|
'line-chart': LineChart,
|
|
202
|
+
'link': Link,
|
|
137
203
|
'list': List,
|
|
204
|
+
'loader': Loader,
|
|
205
|
+
'loader-2': Loader2,
|
|
138
206
|
'lock': Lock,
|
|
139
207
|
'mail': Mail,
|
|
140
208
|
'mail-open': MailOpen,
|
|
141
209
|
'map-pin': MapPin,
|
|
210
|
+
'megaphone': Megaphone,
|
|
211
|
+
'notebook': Notebook,
|
|
142
212
|
'package': Package,
|
|
213
|
+
'package-check': PackageCheck,
|
|
143
214
|
'package-plus': PackagePlus,
|
|
144
215
|
'package-x': PackageX,
|
|
216
|
+
'pause-circle': PauseCircle,
|
|
145
217
|
'percent': Percent,
|
|
218
|
+
'phone': Phone,
|
|
219
|
+
'phone-call': PhoneCall,
|
|
146
220
|
'pie-chart': PieChart,
|
|
147
221
|
'plus-square': PlusSquare,
|
|
148
222
|
'receipt': Receipt,
|
|
149
223
|
'receipt-text': ReceiptText,
|
|
224
|
+
'refresh-ccw': RefreshCcw,
|
|
150
225
|
'reply': Reply,
|
|
226
|
+
'rotate-ccw': RotateCcw,
|
|
151
227
|
'ruler': Ruler,
|
|
228
|
+
'send': Send,
|
|
152
229
|
'settings': Settings,
|
|
153
230
|
'shapes': Shapes,
|
|
154
231
|
'shield': Shield,
|
|
155
232
|
'shield-alert': ShieldAlert,
|
|
156
233
|
'shield-check': ShieldCheck,
|
|
234
|
+
'shopping-bag': ShoppingBag,
|
|
157
235
|
'shopping-cart': ShoppingCart,
|
|
236
|
+
'shuffle': Shuffle,
|
|
158
237
|
'sliders': Sliders,
|
|
159
238
|
'smartphone': Smartphone,
|
|
239
|
+
'sparkles': Sparkles,
|
|
240
|
+
'star': Star,
|
|
160
241
|
'sticky-note': StickyNote,
|
|
161
242
|
'store': Store,
|
|
162
243
|
'tag': Tag,
|
|
244
|
+
'target': Target,
|
|
245
|
+
'thumbs-up': ThumbsUp,
|
|
163
246
|
'ticket': Ticket,
|
|
164
247
|
'trash-2': Trash2,
|
|
165
248
|
'trending-up': TrendingUp,
|
|
249
|
+
'triangle-alert': TriangleAlert,
|
|
166
250
|
'trophy': Trophy,
|
|
167
251
|
'truck': Truck,
|
|
252
|
+
'undo-2': Undo2,
|
|
168
253
|
'unlock': Unlock,
|
|
169
254
|
'user': User,
|
|
255
|
+
'user-check': UserCheck,
|
|
170
256
|
'user-minus': UserMinus,
|
|
171
257
|
'user-plus': UserPlus,
|
|
172
258
|
'user-round': UserRound,
|
|
173
259
|
'users': Users,
|
|
260
|
+
'wallet': Wallet,
|
|
174
261
|
'webhook': Webhook,
|
|
262
|
+
'wrench': Wrench,
|
|
175
263
|
'x': X,
|
|
176
264
|
'x-circle': XCircle,
|
|
177
265
|
}
|
|
@@ -179,13 +267,15 @@ export const LUCIDE_ICON_REGISTRY: Record<string, LucideIcon> = {
|
|
|
179
267
|
function normalizeKebabIconName(input: string): string {
|
|
180
268
|
const trimmed = input.trim()
|
|
181
269
|
if (!trimmed) return ''
|
|
182
|
-
|
|
183
|
-
|
|
270
|
+
const withoutPrefix = trimmed.startsWith('lucide:') ? trimmed.slice('lucide:'.length) : trimmed
|
|
271
|
+
if (!withoutPrefix) return ''
|
|
272
|
+
if (!withoutPrefix.includes('-') && !withoutPrefix.includes('_') && !withoutPrefix.includes(' ') && /[A-Z]/.test(withoutPrefix)) {
|
|
273
|
+
return withoutPrefix
|
|
184
274
|
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
185
275
|
.replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
|
|
186
276
|
.toLowerCase()
|
|
187
277
|
}
|
|
188
|
-
return
|
|
278
|
+
return withoutPrefix
|
|
189
279
|
.replace(/[_\s]+/g, '-')
|
|
190
280
|
.replace(/-+/g, '-')
|
|
191
281
|
.toLowerCase()
|
|
@@ -36,4 +36,11 @@ describe('resolveInjectedIcon', () => {
|
|
|
36
36
|
it('returns null for an empty string', () => {
|
|
37
37
|
expect(resolveInjectedIcon('')).toBeNull()
|
|
38
38
|
})
|
|
39
|
+
|
|
40
|
+
it('resolves lucide-prefixed icon names', () => {
|
|
41
|
+
const node = resolveInjectedIcon('lucide:bell')
|
|
42
|
+
expect(node).not.toBeNull()
|
|
43
|
+
const { container } = render(<>{node}</>)
|
|
44
|
+
expect(container.querySelector('svg')).toBeTruthy()
|
|
45
|
+
})
|
|
39
46
|
})
|