@shipfox/react-ui 0.5.0 → 0.7.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.
Files changed (77) hide show
  1. package/.storybook/main.ts +12 -5
  2. package/.storybook/preview.tsx +11 -0
  3. package/.turbo/turbo-build.log +16 -3
  4. package/.turbo/turbo-check.log +2 -2
  5. package/.turbo/turbo-type.log +1 -1
  6. package/CHANGELOG.md +14 -0
  7. package/README.md +16 -0
  8. package/dist/build-css-entry.js +5 -0
  9. package/dist/build-css-entry.js.map +1 -0
  10. package/dist/components/code-block/code-block-footer.d.ts.map +1 -1
  11. package/dist/components/code-block/code-block-footer.js +29 -15
  12. package/dist/components/code-block/code-block-footer.js.map +1 -1
  13. package/dist/components/dot-grid/dot-grid.d.ts +18 -0
  14. package/dist/components/dot-grid/dot-grid.d.ts.map +1 -0
  15. package/dist/components/dot-grid/dot-grid.js +295 -0
  16. package/dist/components/dot-grid/dot-grid.js.map +1 -0
  17. package/dist/components/dot-grid/index.d.ts +2 -0
  18. package/dist/components/dot-grid/index.d.ts.map +1 -0
  19. package/dist/components/dot-grid/index.js +3 -0
  20. package/dist/components/dot-grid/index.js.map +1 -0
  21. package/dist/components/dynamic-item/dynamic-item.stories.js +1 -1
  22. package/dist/components/dynamic-item/dynamic-item.stories.js.map +1 -1
  23. package/dist/components/icon/icon.d.ts +1 -0
  24. package/dist/components/icon/icon.d.ts.map +1 -1
  25. package/dist/components/icon/icon.js +3 -2
  26. package/dist/components/icon/icon.js.map +1 -1
  27. package/dist/components/index.d.ts +3 -0
  28. package/dist/components/index.d.ts.map +1 -1
  29. package/dist/components/index.js +3 -0
  30. package/dist/components/index.js.map +1 -1
  31. package/dist/components/modal/index.d.ts +3 -0
  32. package/dist/components/modal/index.d.ts.map +1 -0
  33. package/dist/components/modal/index.js +3 -0
  34. package/dist/components/modal/index.js.map +1 -0
  35. package/dist/components/modal/modal.d.ts +37 -0
  36. package/dist/components/modal/modal.d.ts.map +1 -0
  37. package/dist/components/modal/modal.js +262 -0
  38. package/dist/components/modal/modal.js.map +1 -0
  39. package/dist/components/modal/modal.stories.js +497 -0
  40. package/dist/components/modal/modal.stories.js.map +1 -0
  41. package/dist/components/moving-border/index.d.ts +2 -0
  42. package/dist/components/moving-border/index.d.ts.map +1 -0
  43. package/dist/components/moving-border/index.js +3 -0
  44. package/dist/components/moving-border/index.js.map +1 -0
  45. package/dist/components/typography/text.d.ts.map +1 -1
  46. package/dist/components/typography/text.js +1 -1
  47. package/dist/components/typography/text.js.map +1 -1
  48. package/dist/hooks/index.d.ts +1 -0
  49. package/dist/hooks/index.d.ts.map +1 -1
  50. package/dist/hooks/index.js +1 -0
  51. package/dist/hooks/index.js.map +1 -1
  52. package/dist/hooks/useMediaQuery.d.ts +2 -0
  53. package/dist/hooks/useMediaQuery.d.ts.map +1 -0
  54. package/dist/hooks/useMediaQuery.js +74 -0
  55. package/dist/hooks/useMediaQuery.js.map +1 -0
  56. package/dist/onboarding/sign-in.stories.js +16 -8
  57. package/dist/onboarding/sign-in.stories.js.map +1 -1
  58. package/dist/styles.css +1 -0
  59. package/index.css +1 -1
  60. package/package.json +21 -16
  61. package/src/build-css-entry.ts +3 -0
  62. package/src/components/code-block/code-block-footer.tsx +37 -30
  63. package/src/components/dot-grid/dot-grid.tsx +325 -0
  64. package/src/components/dot-grid/index.ts +1 -0
  65. package/src/components/dynamic-item/dynamic-item.stories.tsx +1 -1
  66. package/src/components/icon/icon.tsx +2 -0
  67. package/src/components/index.ts +3 -0
  68. package/src/components/modal/index.ts +23 -0
  69. package/src/components/modal/modal.stories.tsx +384 -0
  70. package/src/components/modal/modal.tsx +309 -0
  71. package/src/components/moving-border/index.ts +1 -0
  72. package/src/components/typography/text.tsx +9 -1
  73. package/src/hooks/index.ts +1 -0
  74. package/src/hooks/useMediaQuery.ts +87 -0
  75. package/src/onboarding/sign-in.stories.tsx +20 -8
  76. package/tsconfig.build.json +7 -1
  77. package/vite.css.config.ts +30 -0
@@ -0,0 +1,325 @@
1
+ import {gsap} from 'gsap';
2
+ import {InertiaPlugin} from 'gsap/InertiaPlugin';
3
+ import type React from 'react';
4
+ import {useCallback, useEffect, useMemo, useRef} from 'react';
5
+ import {cn} from 'utils';
6
+
7
+ gsap.registerPlugin(InertiaPlugin);
8
+
9
+ const HEX_COLOR_REGEX = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i;
10
+
11
+ const throttle = <T extends (...args: never[]) => void>(func: T, limit: number): T => {
12
+ let lastCall = 0;
13
+ return ((...args: Parameters<T>) => {
14
+ const now = performance.now();
15
+ if (now - lastCall >= limit) {
16
+ lastCall = now;
17
+ func(...args);
18
+ }
19
+ }) as T;
20
+ };
21
+
22
+ interface Dot {
23
+ cx: number;
24
+ cy: number;
25
+ xOffset: number;
26
+ yOffset: number;
27
+ _inertiaApplied: boolean;
28
+ }
29
+
30
+ export interface DotGridProps {
31
+ dotSize?: number;
32
+ gap?: number;
33
+ baseColor?: string;
34
+ activeColor?: string;
35
+ proximity?: number;
36
+ speedTrigger?: number;
37
+ shockRadius?: number;
38
+ shockStrength?: number;
39
+ maxSpeed?: number;
40
+ resistance?: number;
41
+ returnDuration?: number;
42
+ className?: string;
43
+ style?: React.CSSProperties;
44
+ }
45
+
46
+ type RgbColor = {
47
+ r: number;
48
+ g: number;
49
+ b: number;
50
+ };
51
+
52
+ function hexToRgb(hex: string): RgbColor {
53
+ const m = hex.match(HEX_COLOR_REGEX);
54
+ if (!m) return {r: 0, g: 0, b: 0};
55
+ return {
56
+ r: parseInt(m[1], 16),
57
+ g: parseInt(m[2], 16),
58
+ b: parseInt(m[3], 16),
59
+ };
60
+ }
61
+
62
+ export function DotGrid({
63
+ dotSize = 16,
64
+ gap = 32,
65
+ baseColor = '#5227FF',
66
+ activeColor = '#5227FF',
67
+ proximity = 150,
68
+ speedTrigger = 100,
69
+ shockRadius = 250,
70
+ shockStrength = 5,
71
+ maxSpeed = 5000,
72
+ resistance = 750,
73
+ returnDuration = 1.5,
74
+ className = '',
75
+ style,
76
+ }: DotGridProps): React.JSX.Element {
77
+ const wrapperRef = useRef<HTMLDivElement>(null);
78
+ const canvasRef = useRef<HTMLCanvasElement>(null);
79
+ const dotsRef = useRef<Dot[]>([]);
80
+ const pointerRef = useRef({
81
+ x: 0,
82
+ y: 0,
83
+ vx: 0,
84
+ vy: 0,
85
+ speed: 0,
86
+ lastTime: 0,
87
+ lastX: 0,
88
+ lastY: 0,
89
+ });
90
+
91
+ const baseRgb = useMemo(() => hexToRgb(baseColor), [baseColor]);
92
+ const activeRgb = useMemo(() => hexToRgb(activeColor), [activeColor]);
93
+
94
+ const colorGradient = useMemo(() => {
95
+ const gradient: string[] = new Array(256);
96
+ for (let i = 0; i < 256; i++) {
97
+ const normalizedSqDist = i / 255;
98
+ const normalizedDist = Math.sqrt(normalizedSqDist);
99
+ const t = 1 - normalizedDist;
100
+ const r = Math.round(baseRgb.r + (activeRgb.r - baseRgb.r) * t);
101
+ const g = Math.round(baseRgb.g + (activeRgb.g - baseRgb.g) * t);
102
+ const b = Math.round(baseRgb.b + (activeRgb.b - baseRgb.b) * t);
103
+ gradient[i] = `rgb(${r},${g},${b})`;
104
+ }
105
+ return gradient;
106
+ }, [baseRgb, activeRgb]);
107
+
108
+ const circlePath = useMemo(() => {
109
+ if (typeof window === 'undefined' || !window.Path2D) return null;
110
+
111
+ const p = new Path2D();
112
+ p.arc(0, 0, dotSize / 2, 0, Math.PI * 2);
113
+ return p;
114
+ }, [dotSize]);
115
+
116
+ const buildGrid = useCallback(() => {
117
+ const wrap = wrapperRef.current;
118
+ const canvas = canvasRef.current;
119
+ if (!wrap || !canvas) return;
120
+
121
+ const {width, height} = wrap.getBoundingClientRect();
122
+ const dpr = window.devicePixelRatio || 1;
123
+
124
+ canvas.width = width * dpr;
125
+ canvas.height = height * dpr;
126
+ canvas.style.width = `${width}px`;
127
+ canvas.style.height = `${height}px`;
128
+ const ctx = canvas.getContext('2d');
129
+ if (ctx) ctx.scale(dpr, dpr);
130
+
131
+ const cols = Math.floor((width + gap) / (dotSize + gap));
132
+ const rows = Math.floor((height + gap) / (dotSize + gap));
133
+ const cell = dotSize + gap;
134
+
135
+ const gridW = cell * cols - gap;
136
+ const gridH = cell * rows - gap;
137
+
138
+ const extraX = width - gridW;
139
+ const extraY = height - gridH;
140
+
141
+ const startX = extraX / 2 + dotSize / 2;
142
+ const startY = extraY / 2 + dotSize / 2;
143
+
144
+ const dots: Dot[] = [];
145
+ for (let y = 0; y < rows; y++) {
146
+ for (let x = 0; x < cols; x++) {
147
+ const cx = startX + x * cell;
148
+ const cy = startY + y * cell;
149
+ dots.push({cx, cy, xOffset: 0, yOffset: 0, _inertiaApplied: false});
150
+ }
151
+ }
152
+ dotsRef.current = dots;
153
+ }, [dotSize, gap]);
154
+
155
+ useEffect(() => {
156
+ if (!circlePath) return;
157
+
158
+ let rafId: number;
159
+ const proxSq = proximity * proximity;
160
+
161
+ const draw = () => {
162
+ const canvas = canvasRef.current;
163
+ if (!canvas) return;
164
+ const ctx = canvas.getContext('2d');
165
+ if (!ctx) return;
166
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
167
+
168
+ const {x: px, y: py} = pointerRef.current;
169
+
170
+ for (const dot of dotsRef.current) {
171
+ const ox = dot.cx + dot.xOffset;
172
+ const oy = dot.cy + dot.yOffset;
173
+ const dx = dot.cx - px;
174
+ const dy = dot.cy - py;
175
+ const dsq = dx * dx + dy * dy;
176
+
177
+ let fillColor = baseColor;
178
+ if (dsq <= proxSq) {
179
+ const normalizedSqDist = dsq / proxSq;
180
+ const index = Math.min(255, Math.max(0, Math.round(normalizedSqDist * 255)));
181
+ fillColor = colorGradient[index];
182
+ }
183
+
184
+ ctx.save();
185
+ ctx.translate(ox, oy);
186
+ ctx.fillStyle = fillColor;
187
+ ctx.fill(circlePath);
188
+ ctx.restore();
189
+ }
190
+
191
+ rafId = requestAnimationFrame(draw);
192
+ };
193
+
194
+ draw();
195
+ return () => cancelAnimationFrame(rafId);
196
+ }, [proximity, baseColor, colorGradient, circlePath]);
197
+
198
+ useEffect(() => {
199
+ buildGrid();
200
+ let ro: ResizeObserver | null = null;
201
+ if ('ResizeObserver' in window) {
202
+ ro = new ResizeObserver(buildGrid);
203
+ wrapperRef.current && ro.observe(wrapperRef.current);
204
+ } else {
205
+ (window as Window).addEventListener('resize', buildGrid);
206
+ }
207
+ return () => {
208
+ if (ro) ro.disconnect();
209
+ else window.removeEventListener('resize', buildGrid);
210
+ };
211
+ }, [buildGrid]);
212
+
213
+ useEffect(() => {
214
+ const onMove = (e: MouseEvent) => {
215
+ const now = performance.now();
216
+ const pr = pointerRef.current;
217
+ const dt = pr.lastTime ? now - pr.lastTime : 16;
218
+ const dx = e.clientX - pr.lastX;
219
+ const dy = e.clientY - pr.lastY;
220
+ let vx = (dx / dt) * 1000;
221
+ let vy = (dy / dt) * 1000;
222
+ let speed = Math.hypot(vx, vy);
223
+ if (speed > maxSpeed) {
224
+ const scale = maxSpeed / speed;
225
+ vx *= scale;
226
+ vy *= scale;
227
+ speed = maxSpeed;
228
+ }
229
+ pr.lastTime = now;
230
+ pr.lastX = e.clientX;
231
+ pr.lastY = e.clientY;
232
+ pr.vx = vx;
233
+ pr.vy = vy;
234
+ pr.speed = speed;
235
+
236
+ const canvas = canvasRef.current;
237
+ if (!canvas) return;
238
+ const rect = canvas.getBoundingClientRect();
239
+ pr.x = e.clientX - rect.left;
240
+ pr.y = e.clientY - rect.top;
241
+
242
+ for (const dot of dotsRef.current) {
243
+ const dist = Math.hypot(dot.cx - pr.x, dot.cy - pr.y);
244
+ if (speed > speedTrigger && dist < proximity && !dot._inertiaApplied) {
245
+ dot._inertiaApplied = true;
246
+ gsap.killTweensOf(dot);
247
+ const pushX = dot.cx - pr.x + vx * 0.005;
248
+ const pushY = dot.cy - pr.y + vy * 0.005;
249
+ gsap.to(dot, {
250
+ inertia: {xOffset: pushX, yOffset: pushY, resistance},
251
+ onComplete: () => {
252
+ gsap.to(dot, {
253
+ xOffset: 0,
254
+ yOffset: 0,
255
+ duration: returnDuration,
256
+ ease: 'elastic.out(1,0.75)',
257
+ });
258
+ dot._inertiaApplied = false;
259
+ },
260
+ });
261
+ }
262
+ }
263
+ };
264
+
265
+ const onClick = (e: MouseEvent) => {
266
+ const canvas = canvasRef.current;
267
+ if (!canvas) return;
268
+ const rect = canvas.getBoundingClientRect();
269
+ const cx = e.clientX - rect.left;
270
+ const cy = e.clientY - rect.top;
271
+ for (const dot of dotsRef.current) {
272
+ const dist = Math.hypot(dot.cx - cx, dot.cy - cy);
273
+ if (dist < shockRadius && !dot._inertiaApplied) {
274
+ dot._inertiaApplied = true;
275
+ gsap.killTweensOf(dot);
276
+ const falloff = Math.max(0, 1 - dist / shockRadius);
277
+ const pushX = (dot.cx - cx) * shockStrength * falloff;
278
+ const pushY = (dot.cy - cy) * shockStrength * falloff;
279
+ gsap.to(dot, {
280
+ inertia: {xOffset: pushX, yOffset: pushY, resistance},
281
+ onComplete: () => {
282
+ gsap.to(dot, {
283
+ xOffset: 0,
284
+ yOffset: 0,
285
+ duration: returnDuration,
286
+ ease: 'elastic.out(1,0.75)',
287
+ });
288
+ dot._inertiaApplied = false;
289
+ },
290
+ });
291
+ }
292
+ }
293
+ };
294
+
295
+ const throttledMove = throttle(onMove, 50) as (e: MouseEvent) => void;
296
+ const wrapper = wrapperRef.current;
297
+ if (wrapper) {
298
+ wrapper.addEventListener('mousemove', throttledMove, {passive: true});
299
+ wrapper.addEventListener('click', onClick);
300
+ }
301
+ return () => {
302
+ if (wrapper) {
303
+ wrapper.removeEventListener('mousemove', throttledMove);
304
+ wrapper.removeEventListener('click', onClick);
305
+ }
306
+ };
307
+ }, [maxSpeed, speedTrigger, proximity, resistance, returnDuration, shockRadius, shockStrength]);
308
+
309
+ return (
310
+ <section
311
+ className={cn('p-4 flex items-center justify-center h-full w-full relative', className)}
312
+ style={style}
313
+ role="presentation"
314
+ >
315
+ <div ref={wrapperRef} className="w-full h-full relative">
316
+ {/** biome-ignore lint/a11y/noAriaHiddenOnFocusable: <canvas is not focusable> */}
317
+ <canvas
318
+ ref={canvasRef}
319
+ className="absolute inset-0 w-full h-full pointer-events-none"
320
+ aria-hidden="true"
321
+ />
322
+ </div>
323
+ </section>
324
+ );
325
+ }
@@ -0,0 +1 @@
1
+ export * from './dot-grid';
@@ -1,6 +1,7 @@
1
1
  import type {Meta, StoryObj} from '@storybook/react';
2
2
  import {Button} from 'components/button/button';
3
3
  import {ItemTitle} from 'components/item';
4
+ import {MovingBorder} from 'components/moving-border';
4
5
  import {cn} from 'utils/cn';
5
6
  import illustration1 from '../../assets/illustration-1.svg';
6
7
  import illustration2 from '../../assets/illustration-2.svg';
@@ -8,7 +9,6 @@ import illustrationBg from '../../assets/illustration-gradient.svg';
8
9
  import {Avatar} from '../avatar/avatar';
9
10
  import {AvatarGroup, AvatarGroupTooltip} from '../avatar/avatar-group';
10
11
  import {Icon} from '../icon/icon';
11
- import {MovingBorder} from '../moving-border/moving-border';
12
12
  import {DynamicItem} from './dynamic-item';
13
13
 
14
14
  const meta = {
@@ -2,6 +2,7 @@ import {
2
2
  type RemixiconComponentType,
3
3
  RiAddLine,
4
4
  RiArrowRightSLine,
5
+ RiBookOpenFill,
5
6
  RiCheckLine,
6
7
  RiCloseLine,
7
8
  RiFileCopyLine,
@@ -60,6 +61,7 @@ const iconsMap = {
60
61
  copy: RiFileCopyLine,
61
62
  addLine: RiAddLine,
62
63
  chevronRight: RiArrowRightSLine,
64
+ bookOpen: RiBookOpenFill,
63
65
  } as const satisfies Record<string, RemixiconComponentType>;
64
66
 
65
67
  export type IconName = keyof typeof iconsMap;
@@ -4,13 +4,16 @@ export * from './badge';
4
4
  export * from './button';
5
5
  export * from './checkbox';
6
6
  export * from './code-block';
7
+ export * from './dot-grid';
7
8
  export * from './dynamic-item';
8
9
  export * from './icon';
9
10
  export * from './inline-tips';
10
11
  export * from './input';
11
12
  export * from './item';
12
13
  export * from './label';
14
+ export * from './modal';
13
15
  export * from './textarea';
14
16
  export * from './theme';
15
17
  export * from './toast';
18
+ export * from './tooltip';
16
19
  export * from './typography';
@@ -0,0 +1,23 @@
1
+ export type {
2
+ ModalContentProps,
3
+ ModalDescriptionProps,
4
+ ModalHeaderProps,
5
+ ModalOverlayProps,
6
+ ModalTitleProps,
7
+ } from './modal';
8
+ export {
9
+ Modal,
10
+ ModalBody,
11
+ ModalClose,
12
+ ModalContent,
13
+ ModalDescription,
14
+ ModalFooter,
15
+ ModalHeader,
16
+ ModalOverlay,
17
+ ModalPortal,
18
+ ModalTitle,
19
+ ModalTrigger,
20
+ modalContentVariants,
21
+ modalDefaultTransition,
22
+ modalOverlayVariants,
23
+ } from './modal';