@elah/editor 0.3.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  The full Elah video editor SDK for React. Combines the core engine, timeline UI, WebGL2 renderer, media library, and export pipeline into a single package.
4
4
 
5
- Ships `EditorProvider`, `Preview` (WebGL2 canvas + interactive transform overlays), `Timeline`, `AssetPanel`, `SourcePanel`, and `ElementsPanel`, and re-exports the entire `@elah/core` and `@elah/timeline` API. Supports video, image, text, **shape**, and **freehand** clips, **multi-track audio**, and MP4 export.
5
+ Ships `EditorProvider`, `Preview` (WebGL2 canvas + interactive transform overlays), `Timeline`, `AssetPanel`, `SourcePanel`, and `ElementsPanel`, and re-exports the public `@elah/core`, `@elah/react`, and `@elah/timeline` API — so most apps only ever import from `@elah/editor`. (Renderer and debug internals are the exception; import those from `@elah/core` directly.) Supports video, image, text, **shape**, and **freehand** clips, **multi-track audio**, and MP4 export.
6
6
 
7
7
  [![npm](https://img.shields.io/npm/v/@elah/editor)](https://www.npmjs.com/package/@elah/editor)
8
8
  [![gzip size](https://img.shields.io/badge/gzip-~63%20KiB%20full%20SDK-brightgreen)](../../BUNDLE_STRATEGY.md)
@@ -16,7 +16,9 @@ Ships `EditorProvider`, `Preview` (WebGL2 canvas + interactive transform overlay
16
16
  npm install @elah/editor
17
17
  ```
18
18
 
19
- Peer dependencies: `react`, `react-dom` >= 18.
19
+ Peer dependencies: `react`, `react-dom` >= 18, `lucide-react` >= 0.400.0 (used by the
20
+ bundled `@elah/timeline` UI for clip icons — install it alongside `react`/`react-dom`
21
+ even though nothing in your own code imports it).
20
22
 
21
23
  **Bundle size:** ~10 KiB gzipped for the editor layer (51 KiB raw); ~63 KiB gzipped for the full SDK graph (`core` + `timeline` + `editor`, 330 KiB raw). `mediabunny` is injected, never bundled — see [BUNDLE_STRATEGY.md](../../BUNDLE_STRATEGY.md).
22
24
 
@@ -84,14 +86,16 @@ function App() {
84
86
  ## Import media
85
87
 
86
88
  ```ts
87
- import { importFiles, importUrl, useMediaLibrary } from '@elah/editor'
89
+ import { importFiles, importUrl, importBlob, useMediaLibrary } from '@elah/editor'
88
90
 
89
- await importFiles(Array.from(fileList)) // local files
90
- await importUrl('https://example.com/clip.mp4') // remote URL (also importBlob for blobs)
91
+ await importFiles(Array.from(fileList)) // local files
92
+ await importUrl('https://example.com/clip.mp4') // remote URL
93
+ await importBlob(recordedBlob, { name: 'take-1.webm' })
91
94
 
92
95
  // Subscribe in React — useMediaLibrary() takes no arguments and returns
93
- // { assets, getAsset, removeAsset, updateAsset } with assets in insertion order.
94
- const { assets } = useMediaLibrary()
96
+ // { assets, getAsset, removeAsset, updateAsset, importFiles, importUrl, importBlob }
97
+ // with assets in insertion order. `useAssets` is an alias for the same hook.
98
+ const { assets, importFiles: addFiles } = useMediaLibrary()
95
99
  ```
96
100
 
97
101
  ### Programmatic insertion (no drag)
@@ -142,16 +146,27 @@ Runs in a web worker. Reuses `resolveTimeline` + the GPU renderer's placement ma
142
146
  ## Package layers
143
147
 
144
148
  ```
145
- @elah/core — engine, playback, resolver, stores, media, export (framework-agnostic)
146
- @elah/timeline — React timeline UI components and hooks
147
- @elah/editor — EditorProvider, Preview, AssetPanel + re-exports everything above
149
+ @elah/core — engine, playback, resolver, vanilla stores, media, export (framework-agnostic)
150
+ @elah/react — React bindings: EditorContext, store hooks, audio hooks
151
+ @elah/timeline — React timeline UI components and hooks (consumes @elah/core + @elah/react)
152
+ @elah/editor — EditorProvider, Preview, AssetPanel + re-exports @elah/core, @elah/react, @elah/timeline
148
153
  @elah/cli — headless split/trim/build/export/serve on top of @elah/core
149
154
  ```
150
155
 
151
156
  Use `@elah/editor` for the full in-browser experience. Use `@elah/core` directly
152
- for headless or custom rendering pipelines. Use `@elah/cli` for automation,
157
+ for headless or custom rendering pipelines, or pair it with `@elah/react` if you
158
+ want the hooks without the timeline UI. Use `@elah/cli` for automation,
153
159
  AI-generation pipelines, and server-side rendering.
154
160
 
161
+ **One active project per page.** `@elah/core`'s stores (`tracksStore`,
162
+ `playbackStore`, `selectionStore`, `transitionsStore`, `mediaLibraryStore`) are
163
+ module-level singletons, and `<EditorProvider>` wires the `TimelineEngine` /
164
+ `PlaybackEngine` it creates into those same shared stores. Mounting a second
165
+ `<EditorProvider>` on the same page (two independent projects at once) will have
166
+ both instances overwrite each other's state — there is currently no per-instance
167
+ scoping. If you need multiple concurrent editors, isolate each in its own
168
+ tab/window/iframe.
169
+
155
170
  ---
156
171
 
157
172
  ## Links
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useRef, useState, } from 'react';
3
3
  import { createPortal } from 'react-dom';
4
- import { importFiles, importUrl, MEDIA_DRAG_MIME, mediaDragKindMime, useMediaLibrary, useMediaLibraryStore, } from '@elah/core';
4
+ import { importFiles, importUrl, MEDIA_DRAG_MIME, mediaDragKindMime, } from '@elah/core';
5
+ import { useMediaLibrary, useMediaLibraryStore } from '@elah/react';
5
6
  import { cn } from '@elah/timeline';
6
7
  import { isActivationKey, useAssetActivation, } from '../activation';
7
8
  const KIND_ICONS = {
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo } from 'react';
3
- import { TimelineEngine, PlaybackEngine, useTracksStore, usePlaybackStore, useTransitionsStore, EditorContext, installTraceGlobal, trace, } from '@elah/core';
3
+ import { TimelineEngine, PlaybackEngine, installTraceGlobal, trace, } from '@elah/core';
4
+ import { useTracksStore, usePlaybackStore, useTransitionsStore, EditorContext, } from '@elah/react';
4
5
  export function EditorProvider({ fps, stage, defaultTrackHeight, maxHistorySize, initialTracks, children, }) {
5
6
  const engine = useMemo(() => new TimelineEngine({
6
7
  fps,
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useLayoutEffect, useMemo, useRef, useState, } from 'react';
3
- import { useTimelineEngine, useSelectionStore, usePlaybackStore, useMediaLibraryStore, resolveDrawRect, transformFromContainRect, computeContainViewport, } from '@elah/core';
3
+ import { resolveDrawRect, transformFromContainRect, computeContainViewport, } from '@elah/core';
4
+ import { useTimelineEngine, useSelectionStore, usePlaybackStore, useMediaLibraryStore, } from '@elah/react';
4
5
  import { useResolvedScene } from '../useResolvedScene';
5
6
  const MIN_BOX_PX = 28;
6
7
  const BOX_PAD = 0;
@@ -2,8 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef, useEffect, useImperativeHandle, useRef, } from 'react';
3
3
  import { GpuRenderer } from '@elah/core';
4
4
  import { resolveTimeline } from '@elah/core';
5
- import { useTimelineEngine, usePlaybackEngine } from '@elah/core';
6
- import { AudioPlaybackController, useMediaLibraryStore, preloadProjectImages, warmImageSrc } from '@elah/core';
5
+ import { useTimelineEngine, usePlaybackEngine, useMediaLibraryStore } from '@elah/react';
6
+ import { AudioPlaybackController, preloadProjectImages, warmImageSrc } from '@elah/core';
7
7
  import { cn } from '@elah/timeline';
8
8
  import { TextOverlay } from './TextOverlay';
9
9
  import { ShapeOverlay } from './ShapeOverlay';
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useLayoutEffect, useMemo, useRef, useState, } from 'react';
3
- import { useTimelineEngine, useSelectionStore, usePlaybackStore, computeContainViewport, } from '@elah/core';
3
+ import { computeContainViewport } from '@elah/core';
4
+ import { useTimelineEngine, useSelectionStore, usePlaybackStore, } from '@elah/react';
4
5
  import { useResolvedScene } from '../useResolvedScene';
5
6
  const DEFAULT_SCALE = 0.5;
6
7
  const DEFAULT_CENTER = 0.5;
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useLayoutEffect, useMemo, useRef, useState } from 'react';
3
- import { computeContainViewport, useTracksStore } from '@elah/core';
3
+ import { computeContainViewport } from '@elah/core';
4
+ import { useTracksStore } from '@elah/react';
4
5
  export function StageBorder() {
5
6
  const rootRef = useRef(null);
6
7
  const [size, setSize] = useState({ width: 0, height: 0 });
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
3
- import { computeTextLayout, computeContainViewport, useTimelineEngine, useSelectionStore, usePlaybackStore, SIDE_MARGIN, } from '@elah/core';
3
+ import { computeTextLayout, computeContainViewport, SIDE_MARGIN, } from '@elah/core';
4
+ import { useTimelineEngine, useSelectionStore, usePlaybackStore, } from '@elah/react';
4
5
  import { useResolvedScene } from '../useResolvedScene';
5
6
  const MIN_FONT_SIZE = 6;
6
7
  const MAX_FONT_SIZE = 4000;
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useRef, useState, } from 'react';
3
3
  import { createPortal } from 'react-dom';
4
- import { importFiles, importUrl, MEDIA_DRAG_MIME, mediaDragKindMime, useMediaLibrary, useMediaLibraryStore, } from '@elah/core';
4
+ import { importFiles, importUrl, MEDIA_DRAG_MIME, mediaDragKindMime, } from '@elah/core';
5
+ import { useMediaLibrary, useMediaLibraryStore } from '@elah/react';
5
6
  import { ELEMENT_DRAG_MIME, cn } from '@elah/timeline';
6
7
  import { isActivationKey, useAssetActivation, } from '../activation';
7
8
  const KIND_ICONS = { video: '▶', audio: '♪', image: '◻' };
@@ -1,4 +1,4 @@
1
- import { type MediaAsset } from '@elah/core';
1
+ import type { MediaAsset } from '@elah/core';
2
2
  import { type ElementKind, type ShapeVariant } from '@elah/timeline';
3
3
  export interface ActivationToast {
4
4
  message: string;
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useContext } from 'react';
2
- import { EditorContext } from '@elah/core';
2
+ import { EditorContext } from '@elah/react';
3
3
  import { insertElement, insertMediaAsset, } from '@elah/timeline';
4
4
  export function isActivationKey(key) {
5
5
  return key === 'Enter' || key === ' ';
@@ -1,5 +1,6 @@
1
1
  import { useMemo, useRef } from 'react';
2
- import { useTimelineEngine, useTracksStore, usePlaybackStore, resolveTimeline, } from '@elah/core';
2
+ import { resolveTimeline } from '@elah/core';
3
+ import { useTimelineEngine, useTracksStore, usePlaybackStore, } from '@elah/react';
3
4
  export function useResolvedScene(frameOverride) {
4
5
  const engine = useTimelineEngine();
5
6
  const playbackFrame = usePlaybackStore((s) => (s.isPlaying ? null : s.currentFrame));
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export { TimelineEngine } from '@elah/core';
3
3
  export { PlaybackEngine } from '@elah/core';
4
4
  export type { PlaybackSnapshot, PlaybackEngineConfig } from '@elah/core';
5
5
  export { resolveTimeline } from '@elah/core';
6
- export type { Scene, ActiveTransition, ActiveVideoClip, ActiveAudioClip, ActiveTextClip, ActiveImageClip, ActiveClipBase, } from '@elah/core';
6
+ export type { Scene, ActiveTransition, ActiveVideoClip, ActiveAudioClip, ActiveTextClip, ActiveImageClip, ActiveShapeClip, ActiveFreehandClip, ActiveClipBase, } from '@elah/core';
7
7
  export type { Renderer } from '@elah/core';
8
8
  export { GpuRenderer } from '@elah/core';
9
9
  export type { RendererOptions } from '@elah/core';
@@ -11,44 +11,62 @@ export { GpuDebugCounters } from '@elah/core';
11
11
  export type { CounterSnapshot } from '@elah/core';
12
12
  export { createDefaultDemuxerFactory, createMediabunnyBackend, isMediabunnyCompatible } from '@elah/core';
13
13
  export type { MediabunnyModule, CreateMediabunnyBackendOpts } from '@elah/core';
14
- export type { DemuxerBackend, DemuxerFactory } from '@elah/core';
14
+ export type { DemuxerBackend, DemuxerFactory, MediabunnyDemuxer } from '@elah/core';
15
15
  export type { VideoFrameProvider, VideoFrameProviderDeps } from '@elah/core';
16
16
  export { createVideoFrameProvider, MockVideoFrameProvider, SyntheticVideoFrameProvider } from '@elah/core';
17
17
  export { AudioPlaybackController } from '@elah/core';
18
18
  export type { AudioPlaybackControllerOptions } from '@elah/core';
19
19
  export { defaultAudioResolver } from '@elah/core';
20
20
  export type { AudioResolver } from '@elah/core';
21
- export { useMediaLibrary, useMediaLibraryStore, MEDIA_DRAG_MIME, mediaDragKindMime, importFiles, importUrl } from '@elah/core';
22
- export type { MediaAsset, MediaKind, DragMediaPayload, ImportFilesOptions, ImportFilesResult, SkippedImport } from '@elah/core';
23
- export { useTracksStore } from '@elah/core';
24
- export { usePlaybackStore } from '@elah/core';
25
- export { useSelectionStore } from '@elah/core';
21
+ export { warmImageSrc, preloadProjectImages } from '@elah/core';
22
+ export type { ImageLoader, LoadedImage } from '@elah/core';
23
+ export { MEDIA_DRAG_MIME, mediaDragKindMime, importFiles, importUrl, importBlob } from '@elah/core';
24
+ export { useMediaLibrary, useAssets, useMediaLibraryStore } from '@elah/react';
25
+ export type { MediaAsset, MediaKind, DragMediaPayload, ImportFilesOptions, ImportFilesResult, ImportUrlOptions, ImportBlobOptions, SkippedImport, MediaLibraryState, MediaLibraryActions, } from '@elah/core';
26
+ export type { UseMediaLibraryApi } from '@elah/react';
27
+ export { useTracksStore } from '@elah/react';
28
+ export { usePlaybackStore } from '@elah/react';
29
+ export { useSelectionStore } from '@elah/react';
30
+ export { useTransitionsStore } from '@elah/react';
31
+ export type { BoundStoreHook } from '@elah/react';
32
+ export { tracksStore, playbackStore, selectionStore, transitionsStore, mediaLibraryStore } from '@elah/core';
33
+ export type { TracksState, TracksActions, PlaybackState, PlaybackActions, SelectionState, SelectionActions, TransitionsState, TransitionsActions, } from '@elah/core';
26
34
  export { createVideoClip } from '@elah/core';
27
35
  export { createAudioClip } from '@elah/core';
28
36
  export { createTextClip } from '@elah/core';
29
37
  export { createImageClip } from '@elah/core';
38
+ export { createShapeClip } from '@elah/core';
39
+ export { createFreehandClip } from '@elah/core';
40
+ export type { CreateClipOptions, CreateShapeClipOptions, CreateFreehandClipOptions, ShapeClipMetadata, FreehandClipMetadata, } from '@elah/core';
30
41
  export { splitClipAtPlayhead } from '@elah/core';
31
42
  export type { SplitAtPlayheadData } from '@elah/core';
32
43
  export type { ActionResult, ActionFailureReason } from '@elah/core';
33
44
  export { framesToTimecode, secondsToFrames, framesToSeconds, getTotalFrames } from '@elah/core';
34
45
  export { generateId } from '@elah/core';
35
46
  export { transformFromCoverRect } from '@elah/core';
47
+ export { snapFrame, buildSnapPoints, resolveOverlapEdgeSnap, clipsOverlap, DEFAULT_OVERLAP_TOLERANCE } from '@elah/core';
48
+ export { serializeProject, deserializeProject } from '@elah/core';
36
49
  export { exportVideo } from '@elah/core';
37
50
  export { lazyExportVideo } from '@elah/core';
38
51
  export type { ExportOptions, ExportProgress, ExportVideoCodec, ExportAudioCodec } from '@elah/core';
39
52
  export { Timeline } from '@elah/timeline';
40
- export type { TimelineProps, TimelineRef } from '@elah/timeline';
53
+ export type { TimelineProps, TimelineRef, TimelineClassNames } from '@elah/timeline';
54
+ export { cn } from '@elah/timeline';
41
55
  export { useTimeline } from '@elah/timeline';
42
56
  export { useTracks } from '@elah/timeline';
43
57
  export { usePlayback } from '@elah/timeline';
44
58
  export { useSelection } from '@elah/timeline';
45
59
  export { useTimelineDrop } from '@elah/timeline';
60
+ export type { TimelineDropState } from '@elah/timeline';
46
61
  export { insertMediaAsset, insertElement } from '@elah/timeline';
47
62
  export { ELEMENT_DRAG_MIME } from '@elah/timeline';
48
63
  export type { DragElementPayload, ElementKind, ShapeVariant, InsertAssetOptions, InsertAssetResult, InsertAssetFailureReason, InsertedKind, } from '@elah/timeline';
49
64
  export { EditorProvider } from './editor/EditorProvider';
50
65
  export type { EditorProviderProps } from './editor/EditorProvider';
51
- export { useEditor, useTimelineEngine, usePlaybackEngine } from '@elah/core';
66
+ export { EditorContext, useEditor, useTimelineEngine, usePlaybackEngine } from '@elah/react';
67
+ export type { EditorContextValue } from '@elah/react';
68
+ export { useAudioMixer, useMasterVolume, useTrackLevels } from '@elah/react';
69
+ export type { AudioMixerApi, MasterVolumeApi, TrackLevel } from '@elah/react';
52
70
  export { useResolvedScene } from './editor/useResolvedScene';
53
71
  export { AssetPanel } from './editor/AssetPanel';
54
72
  export type { AssetPanelProps } from './editor/AssetPanel';
package/dist/index.js CHANGED
@@ -7,21 +7,30 @@ export { createDefaultDemuxerFactory, createMediabunnyBackend, isMediabunnyCompa
7
7
  export { createVideoFrameProvider, MockVideoFrameProvider, SyntheticVideoFrameProvider } from '@elah/core';
8
8
  export { AudioPlaybackController } from '@elah/core';
9
9
  export { defaultAudioResolver } from '@elah/core';
10
- export { useMediaLibrary, useMediaLibraryStore, MEDIA_DRAG_MIME, mediaDragKindMime, importFiles, importUrl } from '@elah/core';
11
- export { useTracksStore } from '@elah/core';
12
- export { usePlaybackStore } from '@elah/core';
13
- export { useSelectionStore } from '@elah/core';
10
+ export { warmImageSrc, preloadProjectImages } from '@elah/core';
11
+ export { MEDIA_DRAG_MIME, mediaDragKindMime, importFiles, importUrl, importBlob } from '@elah/core';
12
+ export { useMediaLibrary, useAssets, useMediaLibraryStore } from '@elah/react';
13
+ export { useTracksStore } from '@elah/react';
14
+ export { usePlaybackStore } from '@elah/react';
15
+ export { useSelectionStore } from '@elah/react';
16
+ export { useTransitionsStore } from '@elah/react';
17
+ export { tracksStore, playbackStore, selectionStore, transitionsStore, mediaLibraryStore } from '@elah/core';
14
18
  export { createVideoClip } from '@elah/core';
15
19
  export { createAudioClip } from '@elah/core';
16
20
  export { createTextClip } from '@elah/core';
17
21
  export { createImageClip } from '@elah/core';
22
+ export { createShapeClip } from '@elah/core';
23
+ export { createFreehandClip } from '@elah/core';
18
24
  export { splitClipAtPlayhead } from '@elah/core';
19
25
  export { framesToTimecode, secondsToFrames, framesToSeconds, getTotalFrames } from '@elah/core';
20
26
  export { generateId } from '@elah/core';
21
27
  export { transformFromCoverRect } from '@elah/core';
28
+ export { snapFrame, buildSnapPoints, resolveOverlapEdgeSnap, clipsOverlap, DEFAULT_OVERLAP_TOLERANCE } from '@elah/core';
29
+ export { serializeProject, deserializeProject } from '@elah/core';
22
30
  export { exportVideo } from '@elah/core';
23
31
  export { lazyExportVideo } from '@elah/core';
24
32
  export { Timeline } from '@elah/timeline';
33
+ export { cn } from '@elah/timeline';
25
34
  export { useTimeline } from '@elah/timeline';
26
35
  export { useTracks } from '@elah/timeline';
27
36
  export { usePlayback } from '@elah/timeline';
@@ -30,7 +39,8 @@ export { useTimelineDrop } from '@elah/timeline';
30
39
  export { insertMediaAsset, insertElement } from '@elah/timeline';
31
40
  export { ELEMENT_DRAG_MIME } from '@elah/timeline';
32
41
  export { EditorProvider } from './editor/EditorProvider';
33
- export { useEditor, useTimelineEngine, usePlaybackEngine } from '@elah/core';
42
+ export { EditorContext, useEditor, useTimelineEngine, usePlaybackEngine } from '@elah/react';
43
+ export { useAudioMixer, useMasterVolume, useTrackLevels } from '@elah/react';
34
44
  export { useResolvedScene } from './editor/useResolvedScene';
35
45
  export { AssetPanel } from './editor/AssetPanel';
36
46
  export { ElementsPanel } from './editor/ElementsPanel';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elah/editor",
3
- "version": "0.3.2",
3
+ "version": "0.4.1",
4
4
  "description": "Full React video editor SDK — timeline UI, WebGL2 preview, media library, drag and drop, and MP4 export",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -55,11 +55,13 @@
55
55
  "license": "Apache-2.0",
56
56
  "peerDependencies": {
57
57
  "react": ">=18.0.0",
58
- "react-dom": ">=18.0.0"
58
+ "react-dom": ">=18.0.0",
59
+ "lucide-react": ">=0.400.0"
59
60
  },
60
61
  "dependencies": {
61
- "@elah/core": "^0.3.0",
62
- "@elah/timeline": "^0.3.0",
62
+ "@elah/core": "^0.4.1",
63
+ "@elah/react": "^0.4.1",
64
+ "@elah/timeline": "^0.4.1",
63
65
  "immer": "^10.1.1",
64
66
  "zustand": "^5.0.3"
65
67
  },
@@ -67,6 +69,7 @@
67
69
  "@types/react": "^18.3.18",
68
70
  "@types/react-dom": "^18.3.5",
69
71
  "autoprefixer": "^10.4.21",
72
+ "lucide-react": "^0.511.0",
70
73
  "postcss": "^8.5.3",
71
74
  "tailwindcss": "^3.4.17",
72
75
  "typescript": "^5.7.3",