@dxos/brand 0.0.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 (74) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +21 -0
  3. package/assets/backgrounds/bg-dxns.svg +49 -0
  4. package/assets/backgrounds/bg-dxos.svg +43 -0
  5. package/assets/backgrounds/bg-echo.svg +30 -0
  6. package/assets/backgrounds/bg-halo.svg +48 -0
  7. package/assets/backgrounds/bg-kube.svg +78 -0
  8. package/assets/backgrounds/bg-mesh.svg +49 -0
  9. package/assets/fonts/dm-mono/DMMono-Italic.ttf +0 -0
  10. package/assets/fonts/dm-mono/DMMono-Light.ttf +0 -0
  11. package/assets/fonts/dm-mono/DMMono-LightItalic.ttf +0 -0
  12. package/assets/fonts/dm-mono/DMMono-Medium.ttf +0 -0
  13. package/assets/fonts/dm-mono/DMMono-MediumItalic.ttf +0 -0
  14. package/assets/fonts/dm-mono/DMMono-Regular.ttf +0 -0
  15. package/assets/fonts/dm-sans/DMSans-Bold.ttf +0 -0
  16. package/assets/fonts/dm-sans/DMSans-BoldItalic.ttf +0 -0
  17. package/assets/fonts/dm-sans/DMSans-Italic.ttf +0 -0
  18. package/assets/fonts/dm-sans/DMSans-Medium.ttf +0 -0
  19. package/assets/fonts/dm-sans/DMSans-MediumItalic.ttf +0 -0
  20. package/assets/fonts/dm-sans/DMSans-Regular.ttf +0 -0
  21. package/assets/fonts/sharp-sans/SharpSansDispNo1-Light.ttf +0 -0
  22. package/assets/fonts/sharp-sans/SharpSansDispNo1-Medium.ttf +0 -0
  23. package/assets/icons/README.md +3 -0
  24. package/assets/icons/black/icon-dxns.svg +26 -0
  25. package/assets/icons/black/icon-dxos.svg +11 -0
  26. package/assets/icons/black/icon-echo.svg +11 -0
  27. package/assets/icons/black/icon-halo.svg +15 -0
  28. package/assets/icons/black/icon-kube.svg +35 -0
  29. package/assets/icons/black/icon-mesh.svg +12 -0
  30. package/assets/icons/composer-icon-monochrome.svg +14 -0
  31. package/assets/icons/composer-icon.afdesign +0 -0
  32. package/assets/icons/composer-icon.svg +15 -0
  33. package/assets/icons/white/icon-dxns.svg +14 -0
  34. package/assets/icons/white/icon-dxos.svg +10 -0
  35. package/assets/icons/white/icon-echo.svg +11 -0
  36. package/assets/icons/white/icon-halo.svg +15 -0
  37. package/assets/icons/white/icon-kube.svg +17 -0
  38. package/assets/icons/white/icon-mesh.svg +12 -0
  39. package/assets/logotypes/dxos-hero-black.svg +11 -0
  40. package/assets/logotypes/dxos-hero-white.svg +11 -0
  41. package/assets/logotypes/dxos-horizontal-block.svg +12 -0
  42. package/assets/logotypes/dxos-horizontal-white.svg +11 -0
  43. package/assets/logotypes/dxos-logo-black.svg +7 -0
  44. package/assets/logotypes/dxos-logo-white.svg +7 -0
  45. package/assets/logotypes/dxos-text-black.svg +10 -0
  46. package/assets/logotypes/dxos-text-white.svg +10 -0
  47. package/assets/logotypes/dxos-vertical-black.svg +11 -0
  48. package/assets/logotypes/dxos-vertical-white.svg +11 -0
  49. package/assets/rive/dxos.riv +0 -0
  50. package/assets/sounds/ident-1.mp3 +0 -0
  51. package/assets/sounds/ident-2.mp3 +0 -0
  52. package/assets/sounds/ident-3.mp3 +0 -0
  53. package/assets/sounds/ident-4.mp3 +0 -0
  54. package/package.json +49 -0
  55. package/src/Icons.stories.tsx +50 -0
  56. package/src/Logotypes.stories.tsx +60 -0
  57. package/src/components/ComposerLogo/ComposerLogo.stories.tsx +210 -0
  58. package/src/components/ComposerLogo/ComposerLogo.tsx +286 -0
  59. package/src/components/ComposerLogo/index.ts +5 -0
  60. package/src/components/index.ts +5 -0
  61. package/src/components/rive.stories.tsx +98 -0
  62. package/src/icons/Composer.tsx +57 -0
  63. package/src/icons/DXNS.tsx +46 -0
  64. package/src/icons/DXOS.tsx +23 -0
  65. package/src/icons/ECHO.tsx +23 -0
  66. package/src/icons/HALO.tsx +27 -0
  67. package/src/icons/KUBE.tsx +55 -0
  68. package/src/icons/MESH.tsx +24 -0
  69. package/src/icons/index.ts +11 -0
  70. package/src/index.ts +7 -0
  71. package/src/logotypes/DXOSHorizontalType.tsx +41 -0
  72. package/src/logotypes/DXOSType.tsx +38 -0
  73. package/src/logotypes/DXOSVerticalType.tsx +41 -0
  74. package/src/logotypes/index.ts +7 -0
@@ -0,0 +1,210 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import '@fontsource/k2d/100-italic.css';
6
+
7
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
8
+ import React, { useRef, useState } from 'react';
9
+
10
+ import { Button, Icon } from '@dxos/react-ui';
11
+ import { withTheme } from '@dxos/react-ui/testing';
12
+ import { mx } from '@dxos/ui-theme';
13
+
14
+ import { DXOS } from '../../icons';
15
+
16
+ import { type AnimationController, ComposerLogo, ComposerSpinner } from './ComposerLogo';
17
+
18
+ // import ident from '../../../assets/sounds/ident-1.mp3';
19
+
20
+ // https://pixabay.com/sound-effects/search/logo/?pagi=2
21
+
22
+ const meta = {
23
+ title: 'ui/brand/Logo',
24
+ component: ComposerLogo,
25
+ decorators: [withTheme],
26
+ } satisfies Meta<typeof ComposerLogo>;
27
+
28
+ export default meta;
29
+
30
+ type Story = StoryObj<typeof meta>;
31
+
32
+ // TODO(burdon): Get from theme?
33
+ const colors = {
34
+ gray: '#888888',
35
+ purple: '#AA23D3',
36
+ orange: '#CA6346',
37
+ green: '#4DA676',
38
+ blue: '#539ACD',
39
+ };
40
+
41
+ export const Default: Story = {
42
+ render: () => {
43
+ const controller = useRef<AnimationController>(null);
44
+ const [logo, setLogo] = useState(false);
45
+ const handleSpin = async () => {
46
+ // const audio = new Audio(ident);
47
+ // await audio.play();
48
+ setTimeout(() => {
49
+ setLogo(true);
50
+ }, 1_500);
51
+
52
+ controller.current?.spin();
53
+ };
54
+
55
+ return (
56
+ <div className='absolute flex inset-0 items-center justify-center'>
57
+ <div className='absolute left-4 top-4'>
58
+ <Button onClick={handleSpin}>Spin</Button>
59
+ </div>
60
+
61
+ <div>
62
+ <div className='flex justify-center'>
63
+ <ComposerLogo ref={controller} size={256} />
64
+ </div>
65
+
66
+ <div className={mx('transition opacity-0 duration-1000', logo && 'opacity-100')}>
67
+ <div className={mx('text-[100px] text-teal-400 font-[k2d] italic')}>composer</div>
68
+ <div className={mx('flex items-center -mt-[20px] text-neutral-700')}>
69
+ <span className='ml-[210px] mt-[2px] mr-2'>Powered by DXOS</span>
70
+ <div>
71
+ <DXOS className='is-[32px] bs-[32px]' />
72
+ </div>
73
+ </div>
74
+ </div>
75
+ </div>
76
+ </div>
77
+ );
78
+ },
79
+ };
80
+
81
+ // TODO(burdon): Camo.
82
+ export const Colors: Story = {
83
+ render: () => {
84
+ const colors = [
85
+ ['fill-teal-400', 'fill-teal-500', 'fill-teal-600'],
86
+ ['fill-orange-400', 'fill-orange-500', 'fill-orange-600'],
87
+ ['fill-cyan-400', 'fill-cyan-500', 'fill-cyan-600'],
88
+ ['fill-purple-400', 'fill-purple-500', 'fill-purple-600'],
89
+ ['fill-blue-500', 'fill-blue-600', 'fill-blue-700'],
90
+ ['fill-slate-500', 'fill-slate-600', 'fill-slate-700'],
91
+ ['fill-blue-500', 'fill-neutral-100', 'fill-red-500'],
92
+ ['fill-stone-400', 'fill-stone-500', 'fill-stone-600'],
93
+ ['fill-neutral-500', 'fill-neutral-600', 'fill-neutral-700'],
94
+ ];
95
+
96
+ return (
97
+ <div className='absolute inset-0 flex justify-center items-center'>
98
+ <div className='grid grid-cols-3 gap-20 is-[800px]'>
99
+ {colors.map((classNames, i) => (
100
+ <div key={i} className='flex justify-center items-center'>
101
+ <ComposerLogo animate={false} size={160} classNames={classNames} />
102
+ </div>
103
+ ))}
104
+ </div>
105
+ </div>
106
+ );
107
+ },
108
+ };
109
+
110
+ export const Pacman: Story = {
111
+ render: () => {
112
+ return (
113
+ <div className='absolute inset-0 flex flex-col justify-center'>
114
+ <div className='flex flex-col'>
115
+ <div className='flex items-center p-4'>
116
+ <div className='flex ml-8 mr-[100px]'>
117
+ <div>
118
+ <Icon icon='ph--ghost--duotone' classNames='is-[180px] bs-[180px] text-blue-500' />
119
+ </div>
120
+ <div>
121
+ <Icon icon='ph--ghost--duotone' classNames='is-[180px] bs-[180px] text-purple-500' />
122
+ </div>
123
+ <div>
124
+ <Icon icon='ph--ghost--duotone' classNames='is-[180px] bs-[180px] text-red-500' />
125
+ </div>
126
+ </div>
127
+
128
+ <ComposerLogo size={145} classNames={['fill-yellow-200', 'fill-yellow-300', 'fill-yellow-400']} />
129
+
130
+ <div className='flex -ml-10'>
131
+ {Array.from({ length: 6 }).map((_, i) => (
132
+ <div key={i} className='p-4'>
133
+ <Icon icon='ph--square--duotone' classNames='is-6 bs-6 text-yellow-200' />
134
+ </div>
135
+ ))}
136
+ </div>
137
+ </div>
138
+
139
+ <div className='flex justify-center font-mono font-light text-[60px] mt-8 text-neutral-200'>
140
+ <div>Ready Player 1</div>
141
+ </div>
142
+ </div>
143
+ </div>
144
+ );
145
+ },
146
+ };
147
+
148
+ const SpinnerContainer = () => {
149
+ const [spinning, setSpinning] = useState(false);
150
+ return (
151
+ <div>
152
+ <div className='absolute left-4 top-4'>
153
+ {(spinning && <Button onClick={() => setSpinning(false)}>Stop</Button>) || (
154
+ <Button onClick={() => setSpinning(true)}>Start</Button>
155
+ )}
156
+ </div>
157
+ <div className='grid grid-cols-3 gap-20'>
158
+ <>
159
+ <div className='flex justify-center items-center'>
160
+ <ComposerSpinner animate={spinning} gap={1} size={200} color={colors.blue} />
161
+ </div>
162
+ <div className='flex justify-center items-center'>
163
+ <ComposerSpinner animate={spinning} gap={1} size={200} color={colors.green} />
164
+ </div>
165
+ <div className='flex justify-center items-center'>
166
+ <ComposerSpinner animate={spinning} gap={1} size={200} color={colors.orange} />
167
+ </div>
168
+ </>
169
+ <>
170
+ <div className='flex justify-center items-center'>
171
+ <ComposerSpinner animate={spinning} gap={1} size={200} color={colors.blue} />
172
+ </div>
173
+ <div className='flex justify-center items-center'>
174
+ <ComposerSpinner animate={spinning} gap={1} size={100} color={colors.green} />
175
+ </div>
176
+ <div className='flex justify-center items-center'>
177
+ <ComposerSpinner animate={spinning} gap={1} size={40} color={colors.orange} />
178
+ </div>
179
+ </>
180
+ </div>
181
+ </div>
182
+ );
183
+ };
184
+
185
+ export const Spinner: Story = {
186
+ render: () => {
187
+ return (
188
+ <div className='absolute inset-0 flex items-center justify-center'>
189
+ <SpinnerContainer />
190
+ </div>
191
+ );
192
+ },
193
+ };
194
+
195
+ // https://github.com/grafana/grafana/blob/main/packages/grafana-ui/src/components/LoadingBar/LoadingBar.tsx
196
+ export const Linear: Story = {
197
+ render: () => {
198
+ return (
199
+ <div className='absolute flex flex-col inset-0 bg-black'>
200
+ <div
201
+ className={'h-[1px] translateX(-100%) animate-progress-linear'}
202
+ style={{
203
+ background:
204
+ 'linear-gradient(90deg, rgba(110, 159, 255, 0) 0%, #6E9FFF 80.75%, rgba(110, 159, 255, 0) 100%)',
205
+ }}
206
+ />
207
+ </div>
208
+ );
209
+ },
210
+ };
@@ -0,0 +1,286 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import { type IconWeight } from '@phosphor-icons/react';
6
+ import { arc, interpolateString, select } from 'd3';
7
+ import React, {
8
+ type CSSProperties,
9
+ type FC,
10
+ type HTMLAttributes,
11
+ type ReactElement,
12
+ forwardRef,
13
+ useEffect,
14
+ useImperativeHandle,
15
+ useMemo,
16
+ useRef,
17
+ useState,
18
+ } from 'react';
19
+
20
+ import { mx } from '@dxos/ui-theme';
21
+
22
+ const weights = new Map<IconWeight, ReactElement>([
23
+ [
24
+ 'regular',
25
+ <>
26
+ <path d='M202.206,23.705l-25.956,36.48c-14.091,-10.026 -30.956,-15.413 -48.25,-15.413c-45.935,-0 -83.228,37.293 -83.228,83.228c-0,45.935 37.293,83.228 83.228,83.228c17.294,0 34.159,-5.387 48.25,-15.413l25.956,36.48c-21.672,15.42 -47.609,23.705 -74.206,23.705c-70.645,-0 -128,-57.355 -128,-128c0,-70.645 57.355,-128 128,-128c26.597,0 52.534,8.285 74.206,23.705Z' />{' '}
27
+ </>,
28
+ ],
29
+ ]);
30
+
31
+ const Composer = forwardRef<SVGSVGElement, any>((props, ref) => {
32
+ const weight = props.weight || 'regular';
33
+ const size = props.size || 256;
34
+ return (
35
+ <svg
36
+ ref={ref}
37
+ {...props}
38
+ width={size}
39
+ height={size}
40
+ viewBox='0 0 256 256'
41
+ fill='currentColor'
42
+ xmlns='http://www.w3.org/2000/svg'
43
+ >
44
+ {weights.get(weight)}
45
+ </svg>
46
+ );
47
+ });
48
+
49
+ export interface AnimationController {
50
+ spin: () => void;
51
+ }
52
+
53
+ const defaultClassNames = ['[&>path]:fill-teal-400', '[&>path]:fill-teal-500', '[&>path]:fill-teal-600'];
54
+
55
+ type Props = {
56
+ inset: number;
57
+ spin: string;
58
+ className: string;
59
+ style: CSSProperties;
60
+ };
61
+
62
+ const getLayers = (size: number, [a, b, c]: string[]): Props[] => {
63
+ return [
64
+ {
65
+ inset: 0,
66
+ spin: 'animate-[spin_2s_linear_infinite]',
67
+ style: {},
68
+ className: a,
69
+ },
70
+ {
71
+ inset: size / 6,
72
+ spin: 'animate-[spin_2s_linear_infinite]',
73
+ style: {
74
+ animationDirection: 'reverse',
75
+ },
76
+ className: b,
77
+ },
78
+ {
79
+ inset: size / 3.6,
80
+ spin: 'animate-[spin_1s_linear_infinite]',
81
+ style: {},
82
+ className: c,
83
+ },
84
+ ];
85
+ };
86
+
87
+ type ComposerLogoProps = { animate?: boolean; size?: number; classNames?: string[] } & Omit<
88
+ HTMLAttributes<HTMLDivElement>,
89
+ 'className'
90
+ >;
91
+
92
+ export const ComposerLogo = forwardRef<AnimationController, ComposerLogoProps>(
93
+ ({ animate: _animate = false, size = 32, classNames = defaultClassNames, ...props }: ComposerLogoProps, ref) => {
94
+ const layers = useMemo(() => getLayers(size, classNames), [size, classNames]);
95
+ const [animate, setAnimate] = useState(_animate);
96
+ useImperativeHandle(
97
+ ref,
98
+ () => ({
99
+ spin: () => {
100
+ setAnimate(true);
101
+ setTimeout(() => {
102
+ setAnimate(false);
103
+ }, 2_000);
104
+ },
105
+ }),
106
+ [],
107
+ );
108
+
109
+ return (
110
+ <div
111
+ {...props}
112
+ className='flex relative'
113
+ style={{
114
+ width: size,
115
+ height: size,
116
+ }}
117
+ >
118
+ {layers.map(({ inset, spin, style, className }, i) => (
119
+ <div key={i} className='absolute' style={{ inset: `${inset}px` }}>
120
+ <Composer className={mx('is-full bs-full', animate && spin, className)} style={style} />
121
+ </div>
122
+ ))}
123
+ </div>
124
+ );
125
+ },
126
+ );
127
+
128
+ type Slice = {
129
+ startAngle?: number;
130
+ endAngle?: number;
131
+ innerRadius: number;
132
+ outerRadius: number;
133
+ color: string;
134
+ duration: number;
135
+ };
136
+
137
+ const createSlices = ({
138
+ color,
139
+ radius,
140
+ gap = 0,
141
+ t = 1,
142
+ }: {
143
+ color: string;
144
+ radius: number;
145
+ gap?: number;
146
+ t?: number;
147
+ }): Slice[] => {
148
+ const n = radius < 50 ? 3 : radius < 100 ? 4 : 5;
149
+ const parts: Pick<Slice, 'duration'>[] = [
150
+ {
151
+ duration: 1600,
152
+ },
153
+ {
154
+ duration: 1400,
155
+ },
156
+ {
157
+ duration: 1200,
158
+ },
159
+ {
160
+ duration: 1000,
161
+ },
162
+ {
163
+ duration: 800,
164
+ },
165
+ ].slice(0, n);
166
+
167
+ const width = radius / (parts.length + 1);
168
+
169
+ return parts.map(({ duration }, i) => ({
170
+ outerRadius: radius - i * width,
171
+ innerRadius: radius - (i + 1) * width + gap,
172
+ color: color + (0xa0 - i * 0x15).toString(16),
173
+ duration: duration * t,
174
+ }));
175
+ };
176
+
177
+ /**
178
+ * Spinning Composer "C" logo.
179
+ */
180
+ // TODO(burdon): Configure stripes.
181
+ export const ComposerSpinner: FC<{
182
+ animate?: boolean;
183
+ size?: number;
184
+ gap?: number;
185
+ color?: string;
186
+ autoFade?: boolean;
187
+ onClick?: () => void;
188
+ }> = ({ animate, size = 200, gap = 1, color = '#999999', autoFade, onClick }) => {
189
+ const ref = useRef<SVGSVGElement>(null);
190
+ const triggerRef = useRef(() => {});
191
+ const animateRef = useRef(animate);
192
+ useEffect(() => {
193
+ animateRef.current = animate;
194
+ if (animate) {
195
+ triggerRef.current();
196
+ }
197
+ }, [animate]);
198
+
199
+ useEffect(() => {
200
+ const svg = select(ref.current)
201
+ .attr('width', size)
202
+ .attr('height', size)
203
+ .append('g')
204
+ .attr('transform', `translate(${size / 2}, ${size / 2})`);
205
+
206
+ // TODO(burdon): Pass in.
207
+ const arcs = createSlices({ radius: size / 2, gap, color });
208
+
209
+ let count = 0;
210
+ const fadeOut = () => {
211
+ if (--count === 0) {
212
+ svg
213
+ .selectAll('path')
214
+ .transition()
215
+ .delay((_: any, i: any) => i * 200)
216
+ .duration(2_000)
217
+ .attr('opacity', 0);
218
+ }
219
+ };
220
+
221
+ // const createArc = ({
222
+ // innerRadius,
223
+ // outerRadius,
224
+ // startAngle = (1 / 4) * Math.PI,
225
+ // endAngle = -(5 / 4) * Math.PI,
226
+ // }: Slice): ValueFn<SVGPathElement, DefaultArcObject, string | null> =>
227
+ // arc().innerRadius(innerRadius).outerRadius(outerRadius).startAngle(startAngle).endAngle(endAngle);
228
+
229
+ const trigger = arcs.map(
230
+ ({
231
+ startAngle = (1 / 4) * Math.PI,
232
+ endAngle = -(5 / 4) * Math.PI,
233
+ innerRadius,
234
+ outerRadius,
235
+ color,
236
+ duration,
237
+ }) => {
238
+ const arcPath = svg
239
+ .append('path')
240
+ .attr(
241
+ 'd',
242
+ arc().innerRadius(innerRadius).outerRadius(outerRadius).startAngle(startAngle).endAngle(endAngle) as any,
243
+ )
244
+ .attr('fill', color);
245
+ const rotateArc = () => {
246
+ arcPath
247
+ .attr('opacity', 1)
248
+ .transition()
249
+ .duration(duration)
250
+ .attrTween('transform', (() => interpolateString('rotate(0)', 'rotate(360)')) as any)
251
+ .on('end', ((_: any, i: number, nodes: Node[]) => {
252
+ if (animateRef.current) {
253
+ rotateArc();
254
+ } else if (autoFade) {
255
+ fadeOut();
256
+ // d3.select(nodes[i])
257
+ // .transition()
258
+ // .duration(1000)
259
+ // .attrTween('d', () => {
260
+ // const interpolate = d3.interpolate(0, Math.PI);
261
+ // return (t: number) => createArc(arc);
262
+ // });
263
+ }
264
+ }) as any);
265
+ };
266
+
267
+ return rotateArc;
268
+ },
269
+ );
270
+
271
+ triggerRef.current = () => {
272
+ count = trigger.length;
273
+ trigger.forEach((rotate) => rotate());
274
+ };
275
+
276
+ if (animate) {
277
+ triggerRef.current();
278
+ }
279
+
280
+ return () => {
281
+ select(ref.current).selectChildren().remove();
282
+ };
283
+ }, []);
284
+
285
+ return <svg ref={ref} onClick={onClick} />;
286
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './ComposerLogo';
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './ComposerLogo';
@@ -0,0 +1,98 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type Rive, useRive } from '@rive-app/react-canvas';
6
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
7
+ import React, { useEffect } from 'react';
8
+
9
+ import { log } from '@dxos/log';
10
+ import { useAsyncState } from '@dxos/react-ui';
11
+ import { withTheme } from '@dxos/react-ui/testing';
12
+ import { render } from '@dxos/storybook-utils';
13
+ import { mx } from '@dxos/ui-theme';
14
+
15
+ const useFlash = (rive: Rive | null, name: string, delay: number, period: number) => {
16
+ useEffect(() => {
17
+ let t: any;
18
+ if (rive) {
19
+ t = setTimeout(() => {
20
+ t = setInterval(() => {
21
+ rive?.play(name);
22
+ }, period);
23
+ }, delay);
24
+ }
25
+
26
+ return () => clearTimeout(t);
27
+ }, [rive]);
28
+ };
29
+
30
+ const Component = ({ buffer }: { buffer: ArrayBuffer }) => {
31
+ // https://rive.app/community/doc/rive-parameters/docHI9ASztXP
32
+ const { rive, RiveComponent } = useRive({ buffer, autoplay: false });
33
+ useFlash(rive, 'flash-1', 500, 3_000);
34
+ useFlash(rive, 'flash-2', 2_000, 2_000);
35
+
36
+ return (
37
+ <div className='m-8 relative flex grow justify-center'>
38
+ <RiveComponent />
39
+ <div className='z-1 absolute inset-0' style={{ background: 'radial-gradient(transparent, black)' }} />
40
+ </div>
41
+ );
42
+ };
43
+
44
+ const DefaultStory = () => {
45
+ const [buffer] = useAsyncState<ArrayBuffer>(async () => {
46
+ // CORS set via dashboard.
47
+ // TODO(wittjosiah): Fetch to external url fails in headless storybook test.
48
+ const response = await fetch('https://dxos.network/dxos.riv', { mode: 'cors' }).catch((error) => {
49
+ log.catch(error);
50
+ });
51
+ if (response?.ok) {
52
+ return await response.arrayBuffer();
53
+ } else if (response) {
54
+ console.log(response.status);
55
+ }
56
+ });
57
+
58
+ if (!buffer) {
59
+ return null;
60
+ }
61
+
62
+ return (
63
+ <>
64
+ <Component buffer={buffer} />
65
+ <div className='flex absolute left-0 right-0 top-[120px] bs-[320px] align-center'>
66
+ <div
67
+ className='z-1 absolute inset-0 is-[800px] m-auto'
68
+ style={{
69
+ background: 'radial-gradient(ellipse 200% 100% at center, rgba(0, 0, 0, 1), rgba(0, 0, 0, 0) 50%)',
70
+ }}
71
+ />
72
+ <div
73
+ className={mx(
74
+ 'z-2 absolute inset-0 flex items-center is-[720px] m-auto p-2',
75
+ 'text-white text-[60px] leading-tight text-center font-thin',
76
+ )}
77
+ >
78
+ The new standard for building collaborative local-first software.
79
+ </div>
80
+ </div>
81
+ </>
82
+ );
83
+ };
84
+
85
+ const meta = {
86
+ title: 'ui/brand/Rive',
87
+ render: render(DefaultStory),
88
+ decorators: [withTheme],
89
+ parameters: {
90
+ layout: 'fullscreen',
91
+ },
92
+ } satisfies Meta<typeof DefaultStory>;
93
+
94
+ export default meta;
95
+
96
+ type Story = StoryObj<typeof meta>;
97
+
98
+ export const Default: Story = {};
@@ -0,0 +1,57 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { type IconWeight } from '@phosphor-icons/react';
6
+ import React, { type ReactElement, forwardRef } from 'react';
7
+
8
+ const weights = new Map<IconWeight, ReactElement>([
9
+ [
10
+ 'regular',
11
+ <>
12
+ <g transform='matrix(0.969697,0,0,1,-570.182,0)'>
13
+ <rect x='588' y='0' width='264' height='256' style={{ fill: 'none' }} />
14
+ <g transform='matrix(0.917198,0,0,1,-223.93,-876)'>
15
+ <path
16
+ d='M1065.83,1064L1029.14,1064C991.913,1064 961.684,1037.12 961.684,1004C961.684,971.197 991.282,944.542 1028.02,944.008L1028.02,944L1047.85,944L1039.21,968L1029.14,968C1006.79,968 988.669,984.118 988.669,1004C988.669,1023.87 1006.81,1040 1029.14,1040C1029.38,1040 1029.62,1040 1029.85,1040L1029.85,1040L1074.47,1040L1065.83,1064ZM1083.11,1040L1083.11,1040L1083.11,1064L1083.11,1064L1083.11,1040Z'
17
+ style={{ fill: 'rgb(1,122,183)' }}
18
+ />
19
+ </g>
20
+ <path
21
+ d='M761.579,164L720,164C699.51,164 682.875,147.869 682.875,128C682.875,108.452 698.942,92.543 718.969,92.014L718.969,92L729.238,92L721.317,116L720,116C713.165,116 707.625,121.373 707.625,128C707.625,134.623 713.17,140 720,140C720.072,140 720.144,139.999 720.216,139.998L720.216,140L769.5,140L761.579,164Z'
22
+ style={{ fill: 'rgb(6,197,253)' }}
23
+ />
24
+ <path
25
+ d='M745.738,212L720.025,212L720,212C672.202,212 633.389,174.377 633.375,128.024C633.361,81.958 671.591,44.542 718.969,44.006L718.969,44L719.975,44L745.079,44L737.159,68L719.982,68C685.809,68.01 658.115,94.88 658.125,128.017C658.135,161.126 685.858,188 720,188L720.018,188C720.378,188 720.738,187.997 721.098,187.991L721.098,188L753.659,188L745.738,212Z'
26
+ style={{ fill: 'rgb(10,75,105)' }}
27
+ />
28
+ <g transform='matrix(1.03125,0,0,1,588,0)'>
29
+ <path
30
+ d='M128,236C68.393,236 20,187.607 20,128C20,68.353 68.353,20 128,20L160,20L152.319,44L128,44C81.608,44 44,81.608 44,128C44,174.361 81.639,212 128,212C127.756,212.004 127.878,212.004 128,212.003C128.122,212.002 128.244,212 128,212L152.958,212L145.277,236L128,236ZM128,236C128.628,236 127.372,236.011 128,236Z'
31
+ style={{ fill: 'rgb(5,40,61)' }}
32
+ />
33
+ </g>
34
+ </g>
35
+ </>,
36
+ ],
37
+ ]);
38
+
39
+ export const Composer = forwardRef<SVGSVGElement, any>((props, ref) => {
40
+ const weight = props.weight || 'regular';
41
+ const size = props.size || 256;
42
+ return (
43
+ <svg
44
+ ref={ref}
45
+ {...props}
46
+ width={size}
47
+ height={size}
48
+ viewBox='0 0 256 256'
49
+ fill='currentColor'
50
+ xmlns='http://www.w3.org/2000/svg'
51
+ >
52
+ {weights.get(weight)}
53
+ </svg>
54
+ );
55
+ });
56
+
57
+ Composer.displayName = 'Composer';