@bemedev/mind-flow 0.3.2 → 1.1.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/README.md +55 -12
- package/lib/helpers/clamp.d.ts +2 -0
- package/lib/helpers/clamp.d.ts.map +1 -0
- package/lib/helpers/createContext.d.ts +1 -1
- package/lib/helpers/createContext.d.ts.map +1 -1
- package/lib/helpers/index.d.ts +3 -0
- package/lib/helpers/index.d.ts.map +1 -0
- package/lib/index.cjs.js +1 -1
- package/lib/index.d.ts +9 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.es.js +2464 -373
- package/lib/output.css +1 -1
- package/lib/server.cjs.js +1002 -262
- package/lib/server.es.js +968 -272
- package/lib/services/main.machine.d.ts +1564 -667
- package/lib/services/main.machine.d.ts.map +1 -1
- package/lib/services/main.machine.data.d.ts +14 -1
- package/lib/services/main.machine.data.d.ts.map +1 -1
- package/lib/services/main.machine.helpers.d.ts +1 -1
- package/lib/services/main.machine.helpers.d.ts.map +1 -1
- package/lib/services/main.machine.test.d.ts +2 -0
- package/lib/services/main.machine.test.d.ts.map +1 -0
- package/lib/services/main.machine.typings.d.ts +26 -8
- package/lib/services/main.machine.typings.d.ts.map +1 -1
- package/lib/ui/Flow.d.ts +8 -4
- package/lib/ui/Flow.d.ts.map +1 -1
- package/lib/ui/components/Bounds.d.ts.map +1 -1
- package/lib/ui/components/EdgeComponent.d.ts.map +1 -1
- package/lib/ui/components/EdgesBoard.d.ts.map +1 -1
- package/lib/ui/components/EditPanel.d.ts +19 -0
- package/lib/ui/components/EditPanel.d.ts.map +1 -0
- package/lib/ui/components/EditPanel.hooks.d.ts +29 -0
- package/lib/ui/components/EditPanel.hooks.d.ts.map +1 -0
- package/lib/ui/components/EditPanel.types.d.ts +47 -0
- package/lib/ui/components/EditPanel.types.d.ts.map +1 -0
- package/lib/ui/components/FlowChart.context.d.ts +1563 -666
- package/lib/ui/components/FlowChart.context.d.ts.map +1 -1
- package/lib/ui/components/FlowChart.d.ts +8 -47
- package/lib/ui/components/FlowChart.d.ts.map +1 -1
- package/lib/ui/components/FlowChart.types.d.ts +87 -0
- package/lib/ui/components/FlowChart.types.d.ts.map +1 -0
- package/lib/ui/components/NodeComponent.d.ts +10 -12
- package/lib/ui/components/NodeComponent.d.ts.map +1 -1
- package/lib/ui/components/NodesBoard.d.ts +17 -2
- package/lib/ui/components/NodesBoard.d.ts.map +1 -1
- package/lib/ui/components/Panels.d.ts +12 -0
- package/lib/ui/components/Panels.d.ts.map +1 -0
- package/lib/ui/globals/components/atoms/TypingText.d.ts +29 -0
- package/lib/ui/globals/components/atoms/TypingText.d.ts.map +1 -0
- package/lib/ui/globals/components/molecules/FadingDots.d.ts +21 -0
- package/lib/ui/globals/components/molecules/FadingDots.d.ts.map +1 -0
- package/lib/ui/globals/components/organisms/LoadingFallback.d.ts +46 -0
- package/lib/ui/globals/components/organisms/LoadingFallback.d.ts.map +1 -0
- package/lib/ui/globals/constants/index.d.ts +3 -0
- package/lib/ui/globals/constants/index.d.ts.map +1 -0
- package/lib/ui/globals/constants/links.d.ts +4 -0
- package/lib/ui/globals/constants/links.d.ts.map +1 -0
- package/lib/ui/globals/constants/strings.d.ts +5 -0
- package/lib/ui/globals/constants/strings.d.ts.map +1 -0
- package/lib/ui/globals/directives/clickOutside.d.ts +9 -0
- package/lib/ui/globals/directives/clickOutside.d.ts.map +1 -0
- package/lib/ui/globals/directives/index.d.ts +16 -0
- package/lib/ui/globals/directives/index.d.ts.map +1 -0
- package/lib/ui/globals/directives/mouseOut.d.ts +33 -0
- package/lib/ui/globals/directives/mouseOut.d.ts.map +1 -0
- package/lib/ui/globals/directives/mouseOut.test.d.ts +2 -0
- package/lib/ui/globals/directives/mouseOut.test.d.ts.map +1 -0
- package/lib/ui/globals/directives/resize.d.ts +11 -0
- package/lib/ui/globals/directives/resize.d.ts.map +1 -0
- package/lib/ui/globals/helpers/espace.d.ts +17 -0
- package/lib/ui/globals/helpers/espace.d.ts.map +1 -0
- package/lib/ui/globals/signals/createTyping.d.ts +27 -0
- package/lib/ui/globals/signals/createTyping.d.ts.map +1 -0
- package/lib/ui/globals/types.d.ts +54 -0
- package/lib/ui/globals/types.d.ts.map +1 -0
- package/lib/ui/utils.d.ts +11 -0
- package/lib/ui/utils.d.ts.map +1 -0
- package/package.json +2 -3
- package/src/helpers/clamp.ts +3 -0
- package/src/helpers/createContext.ts +20 -4
- package/src/helpers/index.ts +2 -0
- package/src/index.ts +9 -1
- package/src/services/main.machine.data.ts +13 -2
- package/src/services/main.machine.helpers.ts +12 -4
- package/src/services/main.machine.test.ts +205 -0
- package/src/services/main.machine.ts +136 -65
- package/src/services/main.machine.typings.ts +31 -6
- package/src/ui/Flow.tsx +47 -8
- package/src/ui/components/Bounds.tsx +0 -2
- package/src/ui/components/EdgeComponent.tsx +60 -46
- package/src/ui/components/EdgesBoard.tsx +1 -2
- package/src/ui/components/EditPanel.hooks.ts +69 -0
- package/src/ui/components/EditPanel.tsx +89 -0
- package/src/ui/components/EditPanel.types.ts +49 -0
- package/src/ui/components/FlowChart.context.ts +21 -11
- package/src/ui/components/FlowChart.tsx +20 -57
- package/src/ui/components/FlowChart.types.ts +92 -0
- package/src/ui/components/NodeComponent.tsx +81 -58
- package/src/ui/components/NodesBoard.tsx +29 -7
- package/src/ui/components/Panels.tsx +31 -0
- package/src/ui/globals/components/atoms/TypingText.tsx +63 -0
- package/src/ui/globals/components/molecules/FadingDots.tsx +45 -0
- package/src/ui/globals/components/organisms/LoadingFallback.tsx +116 -0
- package/src/ui/globals/constants/index.ts +2 -0
- package/src/ui/globals/constants/links.ts +15 -0
- package/src/ui/globals/constants/strings.ts +5 -0
- package/src/ui/globals/directives/clickOutside.ts +18 -0
- package/src/ui/globals/directives/index.ts +16 -0
- package/src/ui/globals/directives/mouseOut.test.ts +115 -0
- package/src/ui/globals/directives/mouseOut.ts +98 -0
- package/src/ui/globals/directives/resize.ts +33 -0
- package/src/ui/globals/helpers/espace.ts +26 -0
- package/src/ui/globals/signals/createTyping.ts +82 -0
- package/src/ui/globals/types.ts +83 -0
- package/src/ui/utils.ts +14 -0
|
@@ -1,43 +1,44 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-namespace */
|
|
2
1
|
import { toArray } from '@bemedev/app';
|
|
3
2
|
import { createState } from '@bemedev/app-solidjs';
|
|
4
3
|
import { createDraggable } from '@thisbeyond/solid-dnd';
|
|
5
4
|
import { dequal } from 'dequal';
|
|
6
|
-
import { Component, Show } from 'solid-js';
|
|
5
|
+
import { type Component, For, type JSX, Show } from 'solid-js';
|
|
6
|
+
import { Dynamic } from 'solid-js/web';
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
HANDLE_CONTAINER_OFFSET_X,
|
|
10
10
|
HANDLE_MARGIN_TOP,
|
|
11
11
|
HANDLE_SIZE,
|
|
12
12
|
} from '#services/main.machine.data';
|
|
13
|
+
import type { NodeData } from '#services/main.machine.typings';
|
|
13
14
|
|
|
15
|
+
import { resize } from '../globals/directives';
|
|
14
16
|
import { useFlow } from './FlowChart.context';
|
|
15
17
|
|
|
16
|
-
declare module 'solid-js' {
|
|
17
|
-
namespace JSX {
|
|
18
|
-
interface Directives {
|
|
19
|
-
draggable: any;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
18
|
/** Properties for rendering an individual flowchart node component. */
|
|
25
|
-
type
|
|
19
|
+
export type NodeComponentProps<D extends NodeData = NodeData> = {
|
|
26
20
|
/** Unique identifier of the node. */
|
|
27
21
|
id: string;
|
|
22
|
+
/** Custom node component to render inside the node container. */
|
|
23
|
+
children?: Component<D>;
|
|
28
24
|
};
|
|
29
25
|
|
|
30
26
|
/**
|
|
31
27
|
* Interactive flowchart node component supporting dragging, selection, handle
|
|
32
28
|
* connections, and child/sibling creation.
|
|
33
29
|
*
|
|
34
|
-
* @
|
|
30
|
+
* @template | {@linkcode NodeData} `D` - Custom node data dictionary type extending
|
|
31
|
+
* {@linkcode NodeData}.
|
|
32
|
+
*
|
|
33
|
+
* @param props - Node rendering properties of type {@linkcode NodeComponentProps}.
|
|
35
34
|
*
|
|
36
35
|
* @returns The rendered Solid component.
|
|
37
36
|
*
|
|
38
37
|
* @see {@linkcode useFlow}, {@linkcode HANDLE_CONTAINER_OFFSET_X}, {@linkcode HANDLE_MARGIN_TOP}, {@linkcode HANDLE_SIZE}
|
|
39
38
|
*/
|
|
40
|
-
export const NodeComponent
|
|
39
|
+
export const NodeComponent = <D extends NodeData = NodeData>(
|
|
40
|
+
props: NodeComponentProps<D>,
|
|
41
|
+
): JSX.Element => {
|
|
41
42
|
const service = useFlow();
|
|
42
43
|
const newEdge = createState(service, { selector: s => s.context.newEdge });
|
|
43
44
|
const draggable = createDraggable(props.id);
|
|
@@ -46,13 +47,11 @@ export const NodeComponent: Component<Props> = props => {
|
|
|
46
47
|
const node = createState(service, {
|
|
47
48
|
selector: ({ context }) => {
|
|
48
49
|
const list = toArray.typed(context.data?.nodes);
|
|
49
|
-
const item = list.find(n => n.id === props.id)
|
|
50
|
+
const item = list.find(n => n.id === props.id);
|
|
50
51
|
return {
|
|
51
|
-
x: item
|
|
52
|
-
y: item
|
|
53
|
-
|
|
54
|
-
content: item.data.content ?? '',
|
|
55
|
-
input: item.input,
|
|
52
|
+
x: item?.position.x ?? 0,
|
|
53
|
+
y: item?.position.y ?? 0,
|
|
54
|
+
data: (item?.data ?? {}) as D,
|
|
56
55
|
};
|
|
57
56
|
},
|
|
58
57
|
equals: dequal,
|
|
@@ -74,8 +73,8 @@ export const NodeComponent: Component<Props> = props => {
|
|
|
74
73
|
<div
|
|
75
74
|
classList={{
|
|
76
75
|
'flex flex-col absolute cursor-grab bg-white rounded-md shadow-md select-none transition-[border,box-shadow] duration-200 ease-in-out hover:shadow-lg draggable': true,
|
|
77
|
-
'border border-[#e38c29] z-
|
|
78
|
-
'border border-[#e6d4be] z-
|
|
76
|
+
'border border-[#e38c29] z-100': selected(),
|
|
77
|
+
'border border-[#e6d4be] z-1': !selected(),
|
|
79
78
|
}}
|
|
80
79
|
|
|
81
80
|
style={{ top: `${node().y}px`, left: `${node().x}px` }}
|
|
@@ -87,6 +86,10 @@ export const NodeComponent: Component<Props> = props => {
|
|
|
87
86
|
e.stopPropagation();
|
|
88
87
|
service.send({ type: 'SELECT', payload: props.id });
|
|
89
88
|
}}
|
|
89
|
+
onDblClick={e => {
|
|
90
|
+
e.stopPropagation();
|
|
91
|
+
service.send({ type: 'EDIT', payload: props.id });
|
|
92
|
+
}}
|
|
90
93
|
>
|
|
91
94
|
<div
|
|
92
95
|
classList={{
|
|
@@ -161,49 +164,69 @@ export const NodeComponent: Component<Props> = props => {
|
|
|
161
164
|
</svg>
|
|
162
165
|
</div>
|
|
163
166
|
|
|
164
|
-
<
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
167
|
+
<div ref={resize(props.id)}>
|
|
168
|
+
<Show
|
|
169
|
+
when={props.children}
|
|
170
|
+
fallback={
|
|
171
|
+
<div class='p-3 select-none'>
|
|
172
|
+
<Show when={node().data.label} keyed>
|
|
173
|
+
{label => (
|
|
174
|
+
<span class='mb-2 block min-w-max border-b border-[#f0f0f0] pb-2 font-semibold whitespace-nowrap text-red-600 select-none'>
|
|
175
|
+
{String(label)}
|
|
176
|
+
</span>
|
|
177
|
+
)}
|
|
178
|
+
</Show>
|
|
179
|
+
|
|
180
|
+
<Show when={node().data.content} keyed>
|
|
181
|
+
{content => <div class='select-none'>{String(content)}</div>}
|
|
182
|
+
</Show>
|
|
183
|
+
|
|
184
|
+
<Show when={!node().data.label && !node().data.content}>
|
|
185
|
+
<For each={Object.entries(node().data)}>
|
|
186
|
+
{([key, val]) => (
|
|
187
|
+
<div>
|
|
188
|
+
<span class='font-semibold'>{key}: </span>
|
|
189
|
+
<span>{String(val)}</span>
|
|
190
|
+
</div>
|
|
191
|
+
)}
|
|
192
|
+
</For>
|
|
193
|
+
</Show>
|
|
194
|
+
</div>
|
|
195
|
+
}
|
|
196
|
+
>
|
|
197
|
+
<Dynamic component={props.children} {...node().data} />
|
|
198
|
+
</Show>
|
|
199
|
+
</div>
|
|
175
200
|
|
|
176
|
-
<
|
|
201
|
+
<div
|
|
202
|
+
id='inputs'
|
|
203
|
+
class='pointer-events-none absolute top-0 z-10 flex cursor-default flex-col'
|
|
204
|
+
style={{ left: `-${HANDLE_CONTAINER_OFFSET_X}px` }}
|
|
205
|
+
>
|
|
177
206
|
<div
|
|
178
|
-
|
|
179
|
-
class='pointer-events-none absolute top-0 z-10 flex cursor-default flex-col'
|
|
180
|
-
style={{ left: `-${HANDLE_CONTAINER_OFFSET_X}px` }}
|
|
181
|
-
>
|
|
182
|
-
<div
|
|
183
|
-
class='cursor-default rounded-full bg-[#e38b29] shadow-md'
|
|
207
|
+
class='cursor-default rounded-full bg-[#e38b29] shadow-md'
|
|
184
208
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
209
|
+
style={{
|
|
210
|
+
width: `${HANDLE_SIZE}px`,
|
|
211
|
+
height: `${HANDLE_SIZE}px`,
|
|
212
|
+
'margin-top': `${HANDLE_MARGIN_TOP}px`,
|
|
213
|
+
'pointer-events': 'all',
|
|
214
|
+
}}
|
|
191
215
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
216
|
+
onMouseDown={event => {
|
|
217
|
+
event.stopPropagation();
|
|
218
|
+
}}
|
|
195
219
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
220
|
+
onMouseUp={event => {
|
|
221
|
+
event.stopPropagation();
|
|
222
|
+
const from = newEdge()?.from;
|
|
199
223
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
</Show>
|
|
224
|
+
if (from) {
|
|
225
|
+
service.send({ type: 'ADD_EDGE', payload: { from, to: props.id } });
|
|
226
|
+
} else service.send('CLEAR_NEW_EDGE');
|
|
227
|
+
}}
|
|
228
|
+
></div>
|
|
229
|
+
</div>
|
|
207
230
|
<div
|
|
208
231
|
id='outputs'
|
|
209
232
|
class='pointer-events-none absolute top-0 z-10 flex flex-col'
|
|
@@ -7,37 +7,55 @@ import {
|
|
|
7
7
|
} from '@thisbeyond/solid-dnd';
|
|
8
8
|
import { dequal } from 'dequal';
|
|
9
9
|
import {
|
|
10
|
-
Component,
|
|
10
|
+
type Component,
|
|
11
11
|
createEffect,
|
|
12
12
|
createSignal,
|
|
13
13
|
For,
|
|
14
|
+
type JSX,
|
|
14
15
|
on,
|
|
15
16
|
onMount,
|
|
16
17
|
Show,
|
|
17
18
|
} from 'solid-js';
|
|
18
19
|
|
|
19
20
|
import { CANVAS_FACTOR, SCROLL_MULTIPLIER } from '../../services/main.machine.data';
|
|
21
|
+
import type { NodeData } from '../../services/main.machine.typings';
|
|
20
22
|
import { DragBounds } from './Bounds';
|
|
21
23
|
import { EdgesBoard } from './EdgesBoard';
|
|
22
24
|
import { useFlow } from './FlowChart.context';
|
|
25
|
+
import type { FlowPanels } from './FlowChart.types';
|
|
23
26
|
import { NodeComponent } from './NodeComponent';
|
|
27
|
+
import { Panels } from './Panels';
|
|
28
|
+
|
|
29
|
+
/** Properties for the {@linkcode NodesBoard} component. */
|
|
30
|
+
type Props<D extends NodeData = NodeData> = {
|
|
31
|
+
/** Optional custom node component. */
|
|
32
|
+
component?: Component<D>;
|
|
33
|
+
/** Optional custom overlay panels. */
|
|
34
|
+
panels?: FlowPanels;
|
|
35
|
+
};
|
|
24
36
|
|
|
25
37
|
/**
|
|
26
38
|
* Interactive board component containing the drag-drop viewport, zoom controls,
|
|
27
39
|
* panning gestures, and rendered nodes/edges.
|
|
28
40
|
*
|
|
41
|
+
* @template | {@linkcode NodeData} `D` - Custom node data dictionary type extending
|
|
42
|
+
* {@linkcode NodeData}.
|
|
43
|
+
*
|
|
44
|
+
* @param props - Board component properties of type {@linkcode Props}.
|
|
45
|
+
*
|
|
29
46
|
* @returns The rendered Solid component.
|
|
30
47
|
*
|
|
31
48
|
* @see {@linkcode DragBounds}, {@linkcode EdgesBoard}, {@linkcode NodeComponent}, {@linkcode useFlow}, {@linkcode CANVAS_FACTOR}, {@linkcode SCROLL_MULTIPLIER}
|
|
32
49
|
*/
|
|
33
|
-
export const NodesBoard
|
|
50
|
+
export const NodesBoard = <D extends NodeData = NodeData>(
|
|
51
|
+
props: Props<D>,
|
|
52
|
+
): JSX.Element => {
|
|
34
53
|
let containerRef: HTMLDivElement;
|
|
35
54
|
const [isPanning, setIsPanning] = createSignal(false);
|
|
36
55
|
const [transform, setTransform] = createSignal({ x: 0, y: 0 });
|
|
37
56
|
const [ref, setRef] = createSignal<HTMLDivElement | undefined>();
|
|
38
57
|
let percentX = 0;
|
|
39
58
|
let percentY = 0;
|
|
40
|
-
|
|
41
59
|
const service = useFlow();
|
|
42
60
|
|
|
43
61
|
const newEdge = createState(service, {
|
|
@@ -135,7 +153,7 @@ export const NodesBoard: Component = () => {
|
|
|
135
153
|
}
|
|
136
154
|
}}
|
|
137
155
|
|
|
138
|
-
class='relative mx-auto h-
|
|
156
|
+
class='relative mx-auto h-full w-full overflow-hidden'
|
|
139
157
|
>
|
|
140
158
|
<DragDropProvider
|
|
141
159
|
onDragMove={({ draggable: { transform: _transform, node, id } }) => {
|
|
@@ -203,7 +221,7 @@ export const NodesBoard: Component = () => {
|
|
|
203
221
|
return (containerRef = el);
|
|
204
222
|
}}
|
|
205
223
|
onScroll={updateScrollPercentages}
|
|
206
|
-
class='relative h-full w-full overflow-auto
|
|
224
|
+
class='relative h-full w-full overflow-auto'
|
|
207
225
|
>
|
|
208
226
|
<DragDropSensors />
|
|
209
227
|
<div
|
|
@@ -251,12 +269,16 @@ export const NodesBoard: Component = () => {
|
|
|
251
269
|
>
|
|
252
270
|
<DragBounds />
|
|
253
271
|
<EdgesBoard />
|
|
254
|
-
<For each={nodeIds()}>
|
|
272
|
+
<For each={nodeIds()}>
|
|
273
|
+
{id => <NodeComponent id={id} children={props.component} />}
|
|
274
|
+
</For>
|
|
255
275
|
</div>
|
|
256
276
|
</div>
|
|
257
277
|
</div>
|
|
258
278
|
|
|
259
|
-
{
|
|
279
|
+
<Panels {...props.panels} />
|
|
280
|
+
|
|
281
|
+
{/* Bottom-Right Panel (Main Zoom & Creation Controls) */}
|
|
260
282
|
<div class='absolute right-4 bottom-4 z-50 flex items-center gap-2 rounded-xl border border-gray-200 bg-white/90 p-2 shadow-lg backdrop-blur-md'>
|
|
261
283
|
<button
|
|
262
284
|
type='button'
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Show, type Component } from 'solid-js';
|
|
2
|
+
|
|
3
|
+
import type { FlowPanels } from './FlowChart.types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Overlay container rendering custom panel slots positioned around the flowchart
|
|
7
|
+
* canvas.
|
|
8
|
+
*
|
|
9
|
+
* @param props - Flow panels layout properties of type {@linkcode FlowPanels}.
|
|
10
|
+
*
|
|
11
|
+
* @returns Rendered overlay panels JSX elements.
|
|
12
|
+
*/
|
|
13
|
+
export const Panels: Component<FlowPanels> = props => {
|
|
14
|
+
return (
|
|
15
|
+
<>
|
|
16
|
+
<Show when={props.topLeft} keyed>
|
|
17
|
+
{Panel => <div class='absolute top-4 left-4 z-50'>{<Panel />}</div>}
|
|
18
|
+
</Show>
|
|
19
|
+
|
|
20
|
+
{/* Top-Right Panel */}
|
|
21
|
+
<Show when={props.topRight} keyed>
|
|
22
|
+
{Panel => <div class='absolute top-4 right-4 z-50'>{<Panel />}</div>}
|
|
23
|
+
</Show>
|
|
24
|
+
|
|
25
|
+
{/* Bottom-Left Panel */}
|
|
26
|
+
<Show when={props.bottomLeft} keyed>
|
|
27
|
+
{Panel => <div class='absolute bottom-4 left-4 z-50'>{<Panel />}</div>}
|
|
28
|
+
</Show>
|
|
29
|
+
</>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createEffect,
|
|
3
|
+
createMemo,
|
|
4
|
+
on,
|
|
5
|
+
type Accessor,
|
|
6
|
+
type Component,
|
|
7
|
+
} from 'solid-js';
|
|
8
|
+
|
|
9
|
+
import { VISIBLE_ESPACE } from '../../constants';
|
|
10
|
+
import { createTyping } from '../../signals/createTyping';
|
|
11
|
+
|
|
12
|
+
/** Base configuration properties for {@linkcode TypingText}. */
|
|
13
|
+
type BaseProps = {
|
|
14
|
+
/** The text string to animate typing out. */
|
|
15
|
+
children: string;
|
|
16
|
+
/** Optional container CSS class name. */
|
|
17
|
+
class?: string;
|
|
18
|
+
/** Keystroke interval delay in milliseconds. Defaults to 62. */
|
|
19
|
+
interval?: number;
|
|
20
|
+
/** Optional boolean accessor to disable typing and display text immediately. */
|
|
21
|
+
disabled?: Accessor<boolean>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Props for the {@linkcode TypingText} component with optional rewind configuration. */
|
|
25
|
+
type Props = BaseProps &
|
|
26
|
+
({ rewind: true; rewindDelay: number } | { rewind?: false });
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Renders animated text with a typewriter keystroke animation effect.
|
|
30
|
+
*
|
|
31
|
+
* @param props - Component configuration properties of type {@linkcode Props}.
|
|
32
|
+
*
|
|
33
|
+
* @returns Rendered animated typing text JSX container.
|
|
34
|
+
*/
|
|
35
|
+
export const TypingText: Component<Props> = props => {
|
|
36
|
+
// #region Default values
|
|
37
|
+
const min = props.interval ?? 62;
|
|
38
|
+
const rewind = props.rewind ?? false;
|
|
39
|
+
const rewindDelay = (props as any).rewindDelay ?? 500;
|
|
40
|
+
const content = props.children;
|
|
41
|
+
const disabled = createMemo(
|
|
42
|
+
() => (props.disabled ? props.disabled() : false),
|
|
43
|
+
false,
|
|
44
|
+
);
|
|
45
|
+
// #endregion
|
|
46
|
+
|
|
47
|
+
const { text, type, setText } = createTyping({
|
|
48
|
+
content,
|
|
49
|
+
min,
|
|
50
|
+
rewind,
|
|
51
|
+
rewindDelay,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
createEffect(on(disabled, () => setText(VISIBLE_ESPACE)));
|
|
55
|
+
|
|
56
|
+
createEffect(() => {
|
|
57
|
+
// If disabled, show full text immediately
|
|
58
|
+
if (disabled()) setText(content);
|
|
59
|
+
else type();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return <div class={props.class}>{text()}</div>;
|
|
63
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { splitProps, type Component, JSX } from 'solid-js';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../../utils';
|
|
4
|
+
import type { OmitPropsOf } from '../../types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Loading animation component displaying animated pulsing dots.
|
|
8
|
+
*
|
|
9
|
+
* @param props - Fading dots configuration properties.
|
|
10
|
+
* @param props.count - The number of dots to display.
|
|
11
|
+
* @param props.duration - Animation duration in milliseconds. Defaults to 500.
|
|
12
|
+
* @param props.innerProps - Additional properties and CSS styles applied to dot
|
|
13
|
+
* elements.
|
|
14
|
+
*
|
|
15
|
+
* @returns Rendered animated dots container JSX element.
|
|
16
|
+
*/
|
|
17
|
+
export const FadingDots: Component<{
|
|
18
|
+
count: number;
|
|
19
|
+
duration?: number;
|
|
20
|
+
innerProps?: OmitPropsOf<'span', 'children' | 'style'> & {
|
|
21
|
+
style?: JSX.CSSProperties;
|
|
22
|
+
};
|
|
23
|
+
}> = ({ count: length, innerProps = {}, duration = 500 }) => {
|
|
24
|
+
const [local, rest] = splitProps(innerProps, ['class', 'style']);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div class='mt-4 flex items-center justify-center space-x-4'>
|
|
28
|
+
{Array.from({ length }, (_, i) => (
|
|
29
|
+
<span
|
|
30
|
+
class={cn(
|
|
31
|
+
'rounded-full animate-out direction-alternate repeat-infinite fade-out-5 size-4 bg-gray-600',
|
|
32
|
+
local.class,
|
|
33
|
+
)}
|
|
34
|
+
style={{
|
|
35
|
+
...local.style,
|
|
36
|
+
'animation-duration': `${duration}ms`,
|
|
37
|
+
'animation-delay': `${duration / i}ms`,
|
|
38
|
+
}}
|
|
39
|
+
aria-hidden='true'
|
|
40
|
+
{...rest}
|
|
41
|
+
/>
|
|
42
|
+
))}
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { Component } from 'solid-js';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../../utils';
|
|
4
|
+
import { TypingText } from '../atoms/TypingText';
|
|
5
|
+
import { FadingDots } from '../molecules/FadingDots';
|
|
6
|
+
|
|
7
|
+
/** Properties for the {@linkcode LoadingFallback} component. */
|
|
8
|
+
interface LoadingFallbackProps {
|
|
9
|
+
/**
|
|
10
|
+
* Text message to display alongside the loading animation.
|
|
11
|
+
*
|
|
12
|
+
* @default 'Chargement en cours...'
|
|
13
|
+
*/
|
|
14
|
+
message?: string;
|
|
15
|
+
/** Additional CSS class names. */
|
|
16
|
+
class?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Spinner dimension sizing.
|
|
19
|
+
*
|
|
20
|
+
* @default 'lg'
|
|
21
|
+
*/
|
|
22
|
+
size?: 'sm' | 'md' | 'lg' | 'xl';
|
|
23
|
+
/** Keystroke typing interval in milliseconds. Defaults to 80. */
|
|
24
|
+
interval?: number;
|
|
25
|
+
/** Whether the typing text animation rewinds upon completion. Defaults to true. */
|
|
26
|
+
rewind?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Delay in milliseconds before rewinding or restarting typing animation. Defaults
|
|
29
|
+
* to 2000.
|
|
30
|
+
*/
|
|
31
|
+
rewindDelay?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Fallback loading screen component displaying an animated spinner and typewriter
|
|
36
|
+
* text.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```tsx
|
|
40
|
+
* <Show when={data()} fallback={<LoadingFallback />}>
|
|
41
|
+
* <DataComponent data={data()!} />
|
|
42
|
+
* </Show>;
|
|
43
|
+
* ```;
|
|
44
|
+
*
|
|
45
|
+
* @param props - Loading fallback properties of type
|
|
46
|
+
* {@linkcode LoadingFallbackProps}.
|
|
47
|
+
*
|
|
48
|
+
* @returns Rendered full-screen loading fallback JSX element.
|
|
49
|
+
*/
|
|
50
|
+
export const LoadingFallback: Component<LoadingFallbackProps> = props => {
|
|
51
|
+
const message = props.message ?? 'Chargement en cours...';
|
|
52
|
+
|
|
53
|
+
const spinnerSizes = {
|
|
54
|
+
sm: 'w-8 h-8',
|
|
55
|
+
md: 'w-12 h-12',
|
|
56
|
+
lg: 'w-16 h-16',
|
|
57
|
+
xl: 'w-24 h-24',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const size = props.size ?? 'lg';
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div
|
|
64
|
+
class={cn(
|
|
65
|
+
'flex flex-col items-center justify-center gap-6',
|
|
66
|
+
'h-screen w-screen overflow-hidden p-4',
|
|
67
|
+
props.class,
|
|
68
|
+
)}
|
|
69
|
+
role='status'
|
|
70
|
+
aria-live='polite'
|
|
71
|
+
aria-label='Chargement en cours'
|
|
72
|
+
>
|
|
73
|
+
{/* Spinner animé */}
|
|
74
|
+
<div class='relative'>
|
|
75
|
+
{/* Cercle extérieur avec rotation */}
|
|
76
|
+
<div
|
|
77
|
+
class={cn(
|
|
78
|
+
spinnerSizes[size],
|
|
79
|
+
'rounded-full border-4 border-[#4B9CAD]/20',
|
|
80
|
+
'animate-spin',
|
|
81
|
+
'border-t-[#4B9CAD]',
|
|
82
|
+
)}
|
|
83
|
+
aria-hidden='true'
|
|
84
|
+
/>
|
|
85
|
+
|
|
86
|
+
{/* Cercle intérieur pour effet de profondeur */}
|
|
87
|
+
<div
|
|
88
|
+
class={cn(
|
|
89
|
+
'absolute inset-2',
|
|
90
|
+
'rounded-full border-4 border-transparent',
|
|
91
|
+
'border-b-[#4B9CAD]/40',
|
|
92
|
+
'animate-spin',
|
|
93
|
+
'animation-duration-[1.5s]',
|
|
94
|
+
'direction-[reverse]',
|
|
95
|
+
)}
|
|
96
|
+
aria-hidden='true'
|
|
97
|
+
/>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
{/* Texte avec effet typing */}
|
|
101
|
+
<div class='text-center' aria-label={message}>
|
|
102
|
+
<TypingText
|
|
103
|
+
class='text-lg font-medium text-gray-700 md:text-xl dark:text-gray-300'
|
|
104
|
+
interval={props.interval ?? 80}
|
|
105
|
+
rewind={props.rewind ?? true}
|
|
106
|
+
rewindDelay={props.rewindDelay ?? 2000}
|
|
107
|
+
>
|
|
108
|
+
{message}
|
|
109
|
+
</TypingText>
|
|
110
|
+
|
|
111
|
+
{/* Points d'animation pour indiquer le chargement */}
|
|
112
|
+
<FadingDots count={5} innerProps={{ class: 'bg-[#3d8091]' }} />
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RootLink } from '../types';
|
|
2
|
+
|
|
3
|
+
/** Preconnect and stylesheet link descriptors for Google Fonts. */
|
|
4
|
+
export const GOOGLE_FONTS_LINKS: RootLink[] = [
|
|
5
|
+
{ rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: 'anonymous' },
|
|
6
|
+
{
|
|
7
|
+
rel: 'preconnect',
|
|
8
|
+
href: 'https://fonts.googleapis.com',
|
|
9
|
+
crossorigin: 'anonymous',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
rel: 'preconnect',
|
|
13
|
+
href: 'https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700;800&family=Open+Sans:wght@300;400;500;600;700;800&family=Poppins:wght@300;400;500;600;700;800&family=Roboto:wght@300;400;500;700;900&family=Lora:ital,wght@0,400;0,500;0,600;0,700;1,400&display=swap',
|
|
14
|
+
},
|
|
15
|
+
];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { onCleanup } from 'solid-js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Solid custom directive that triggers a callback when a click occurs outside the
|
|
5
|
+
* bound element.
|
|
6
|
+
*
|
|
7
|
+
* @param el - The bound DOM element.
|
|
8
|
+
* @param accessor - Accessor returning the callback function to execute.
|
|
9
|
+
*/
|
|
10
|
+
export const clickOutside = (el: any, accessor: any) => {
|
|
11
|
+
const onClick = (e: any) => {
|
|
12
|
+
if (!el.contains(e.target)) {
|
|
13
|
+
accessor()?.();
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
document.body.addEventListener('click', onClick);
|
|
17
|
+
onCleanup(() => document.body.removeEventListener('click', onClick));
|
|
18
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MouseOutParam } from './mouseOut';
|
|
2
|
+
|
|
3
|
+
export * from './clickOutside';
|
|
4
|
+
export * from './mouseOut';
|
|
5
|
+
export * from './resize';
|
|
6
|
+
|
|
7
|
+
declare module 'solid-js' {
|
|
8
|
+
// oxlint-disable-next-line typescript/no-namespace
|
|
9
|
+
namespace JSX {
|
|
10
|
+
interface Directives {
|
|
11
|
+
clickOutside: () => void;
|
|
12
|
+
mouseOut: MouseOutParam;
|
|
13
|
+
draggable: { skipTransform?: boolean };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|