@gadxr/ui 0.0.0-stage → 0.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/LICENSE +21 -0
- package/dist/Avatar.svelte +136 -0
- package/dist/Avatar.svelte.d.ts +13 -0
- package/dist/Button.svelte +29 -0
- package/dist/Button.svelte.d.ts +4 -0
- package/dist/Button2D.svelte +77 -0
- package/dist/Button2D.svelte.d.ts +4 -0
- package/dist/Button3D.svelte +98 -0
- package/dist/Button3D.svelte.d.ts +4 -0
- package/dist/ButtonGroup.svelte +59 -0
- package/dist/ButtonGroup.svelte.d.ts +17 -0
- package/dist/Checkbox.svelte +209 -0
- package/dist/Checkbox.svelte.d.ts +14 -0
- package/dist/DimensionScope.svelte +7 -0
- package/dist/DimensionScope.svelte.d.ts +9 -0
- package/dist/Input.svelte +293 -0
- package/dist/Input.svelte.d.ts +23 -0
- package/dist/Key3D.svelte +67 -0
- package/dist/Key3D.svelte.d.ts +13 -0
- package/dist/Keyboard.svelte +167 -0
- package/dist/Keyboard.svelte.d.ts +9 -0
- package/dist/Menu.svelte +193 -0
- package/dist/Menu.svelte.d.ts +23 -0
- package/dist/Modal.svelte +103 -0
- package/dist/Modal.svelte.d.ts +16 -0
- package/dist/Panel.svelte +132 -0
- package/dist/Panel.svelte.d.ts +19 -0
- package/dist/Pressable3D.svelte +82 -0
- package/dist/Pressable3D.svelte.d.ts +28 -0
- package/dist/ProgressBar.svelte +153 -0
- package/dist/ProgressBar.svelte.d.ts +16 -0
- package/dist/RadialMenu.svelte +322 -0
- package/dist/RadialMenu.svelte.d.ts +28 -0
- package/dist/Rule3D.svelte +17 -0
- package/dist/Rule3D.svelte.d.ts +9 -0
- package/dist/Scene3D.svelte +24 -0
- package/dist/Scene3D.svelte.d.ts +11 -0
- package/dist/Slider.svelte +209 -0
- package/dist/Slider.svelte.d.ts +17 -0
- package/dist/Space3D.svelte +6 -0
- package/dist/Space3D.svelte.d.ts +7 -0
- package/dist/Spinner.svelte +64 -0
- package/dist/Spinner.svelte.d.ts +10 -0
- package/dist/Stack.svelte +41 -0
- package/dist/Stack.svelte.d.ts +15 -0
- package/dist/StackScope.svelte +7 -0
- package/dist/StackScope.svelte.d.ts +9 -0
- package/dist/Surface.svelte +152 -0
- package/dist/Surface.svelte.d.ts +30 -0
- package/dist/Tab.svelte +40 -0
- package/dist/Tab.svelte.d.ts +10 -0
- package/dist/Tabs.svelte +145 -0
- package/dist/Tabs.svelte.d.ts +13 -0
- package/dist/Tag.svelte +71 -0
- package/dist/Tag.svelte.d.ts +12 -0
- package/dist/Text.svelte +75 -0
- package/dist/Text.svelte.d.ts +4 -0
- package/dist/TextLine3D.svelte +84 -0
- package/dist/TextLine3D.svelte.d.ts +19 -0
- package/dist/ThemeProvider.svelte +7 -0
- package/dist/ThemeProvider.svelte.d.ts +9 -0
- package/dist/Toaster.svelte +104 -0
- package/dist/Toaster.svelte.d.ts +11 -0
- package/dist/Tooltip.svelte +174 -0
- package/dist/Tooltip.svelte.d.ts +14 -0
- package/dist/aframe/frame.d.ts +1 -0
- package/dist/aframe/frame.js +37 -0
- package/dist/aframe/index.d.ts +1 -0
- package/dist/aframe/index.js +19 -0
- package/dist/aframe/measure.d.ts +3 -0
- package/dist/aframe/measure.js +42 -0
- package/dist/aframe/placement.d.ts +88 -0
- package/dist/aframe/placement.js +163 -0
- package/dist/aframe/rounded.d.ts +61 -0
- package/dist/aframe/rounded.js +121 -0
- package/dist/aframe/scene.d.ts +9 -0
- package/dist/aframe/scene.js +101 -0
- package/dist/assets/Geist-OFL.txt +93 -0
- package/dist/assets/Geist-Regular.ttf +0 -0
- package/dist/button-style.d.ts +22 -0
- package/dist/button-style.js +34 -0
- package/dist/component-style.d.ts +79 -0
- package/dist/component-style.js +129 -0
- package/dist/context.d.ts +16 -0
- package/dist/context.js +26 -0
- package/dist/fonts.css +7 -0
- package/dist/fonts.d.ts +2 -0
- package/dist/fonts.js +7 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +40 -0
- package/dist/keyboard-layout.d.ts +28 -0
- package/dist/keyboard-layout.js +77 -0
- package/dist/layout-math.d.ts +20 -0
- package/dist/layout-math.js +22 -0
- package/dist/layout.svelte.d.ts +17 -0
- package/dist/layout.svelte.js +58 -0
- package/dist/radial-math.d.ts +33 -0
- package/dist/radial-math.js +90 -0
- package/dist/tabs-context.d.ts +13 -0
- package/dist/tabs-context.js +6 -0
- package/dist/text-edit.d.ts +46 -0
- package/dist/text-edit.js +133 -0
- package/dist/text-focus.svelte.d.ts +24 -0
- package/dist/text-focus.svelte.js +102 -0
- package/dist/theme.d.ts +335 -0
- package/dist/theme.js +312 -0
- package/dist/toast.svelte.d.ts +24 -0
- package/dist/toast.svelte.js +30 -0
- package/dist/types.d.ts +25 -0
- package/dist/types.js +1 -0
- package/dist/xr.svelte.d.ts +9 -0
- package/dist/xr.svelte.js +24 -0
- package/package.json +44 -3
- package/README.md +0 -3
package/dist/theme.js
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
// Design tokens, Base UI style. Everything is in px; 3D converts with
|
|
2
|
+
// `space3d.metersPerPx`, so one set of numbers drives both HTML and A-Frame.
|
|
3
|
+
// Keep this file erasable TypeScript only (no enums) — tests import it with Node's type stripping.
|
|
4
|
+
const primitives = {
|
|
5
|
+
white: '#FFFFFF',
|
|
6
|
+
black: '#000000',
|
|
7
|
+
gray50: '#F6F6F6',
|
|
8
|
+
gray100: '#EEEEEE',
|
|
9
|
+
gray200: '#E2E2E2',
|
|
10
|
+
gray300: '#CBCBCB',
|
|
11
|
+
gray400: '#AFAFAF',
|
|
12
|
+
gray500: '#757575',
|
|
13
|
+
gray600: '#545454',
|
|
14
|
+
gray700: '#333333',
|
|
15
|
+
gray800: '#1F1F1F',
|
|
16
|
+
gray900: '#141414',
|
|
17
|
+
accent: '#276EF1',
|
|
18
|
+
accent400: '#5B91F5',
|
|
19
|
+
negative: '#E11900',
|
|
20
|
+
positive: '#05944F',
|
|
21
|
+
warning: '#FFC043',
|
|
22
|
+
};
|
|
23
|
+
const typography = {
|
|
24
|
+
fontFamily: "Geist, system-ui, 'Segoe UI', Roboto, sans-serif",
|
|
25
|
+
// Font URL for 3D text (troika loads it directly). Empty = the bundled Geist (fonts.ts).
|
|
26
|
+
font3d: '',
|
|
27
|
+
LabelXSmall: { fontSize: 12, lineHeight: 16, fontWeight: 500 },
|
|
28
|
+
LabelSmall: { fontSize: 14, lineHeight: 16, fontWeight: 500 },
|
|
29
|
+
LabelMedium: { fontSize: 16, lineHeight: 20, fontWeight: 500 },
|
|
30
|
+
LabelLarge: { fontSize: 18, lineHeight: 24, fontWeight: 500 },
|
|
31
|
+
HeadingSmall: { fontSize: 20, lineHeight: 28, fontWeight: 600 },
|
|
32
|
+
HeadingMedium: { fontSize: 24, lineHeight: 32, fontWeight: 600 },
|
|
33
|
+
ParagraphMedium: { fontSize: 16, lineHeight: 24, fontWeight: 400 },
|
|
34
|
+
};
|
|
35
|
+
// Dark by default: the world and page background are black.
|
|
36
|
+
export const defaultTheme = {
|
|
37
|
+
name: 'dark',
|
|
38
|
+
colors: {
|
|
39
|
+
...primitives,
|
|
40
|
+
contentPrimary: primitives.white,
|
|
41
|
+
contentSecondary: primitives.gray300,
|
|
42
|
+
contentTertiary: primitives.gray500,
|
|
43
|
+
backgroundPrimary: primitives.black,
|
|
44
|
+
backgroundSecondary: primitives.gray800,
|
|
45
|
+
button: {
|
|
46
|
+
primary: {
|
|
47
|
+
fill: primitives.white,
|
|
48
|
+
text: primitives.black,
|
|
49
|
+
hover: primitives.gray200,
|
|
50
|
+
active: primitives.gray300,
|
|
51
|
+
selectedFill: primitives.accent,
|
|
52
|
+
selectedText: primitives.white,
|
|
53
|
+
},
|
|
54
|
+
secondary: {
|
|
55
|
+
fill: primitives.gray700,
|
|
56
|
+
text: primitives.white,
|
|
57
|
+
hover: primitives.gray600,
|
|
58
|
+
active: primitives.gray500,
|
|
59
|
+
selectedFill: primitives.accent,
|
|
60
|
+
selectedText: primitives.white,
|
|
61
|
+
},
|
|
62
|
+
tertiary: {
|
|
63
|
+
fill: 'transparent',
|
|
64
|
+
text: primitives.white,
|
|
65
|
+
hover: primitives.gray800,
|
|
66
|
+
active: primitives.gray700,
|
|
67
|
+
selectedFill: primitives.gray700,
|
|
68
|
+
selectedText: primitives.accent400,
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
buttonDisabledFill: primitives.gray800,
|
|
72
|
+
buttonDisabledText: primitives.gray600,
|
|
73
|
+
panel: { fill: primitives.gray900, border: primitives.gray800 },
|
|
74
|
+
checkbox: {
|
|
75
|
+
border: primitives.gray500,
|
|
76
|
+
// 3D has no outline, so an unchecked box is a solid slab.
|
|
77
|
+
fill3d: primitives.gray700,
|
|
78
|
+
fillChecked: primitives.accent,
|
|
79
|
+
mark: primitives.white,
|
|
80
|
+
toggleTrack: primitives.gray600,
|
|
81
|
+
toggleTrackChecked: primitives.accent,
|
|
82
|
+
toggleKnob: primitives.white,
|
|
83
|
+
},
|
|
84
|
+
slider: { track: primitives.gray700, fill: primitives.accent, thumb: primitives.white },
|
|
85
|
+
tag: {
|
|
86
|
+
neutral: { solidFill: primitives.gray600, solidText: primitives.white, lightFill: primitives.gray800, lightText: primitives.gray200 },
|
|
87
|
+
accent: { solidFill: primitives.accent, solidText: primitives.white, lightFill: '#14284F', lightText: primitives.accent400 },
|
|
88
|
+
positive: { solidFill: primitives.positive, solidText: primitives.white, lightFill: '#0B3B24', lightText: '#3AA76D' },
|
|
89
|
+
warning: { solidFill: primitives.warning, solidText: primitives.black, lightFill: '#4A3810', lightText: primitives.warning },
|
|
90
|
+
negative: { solidFill: primitives.negative, solidText: primitives.white, lightFill: '#4A140E', lightText: '#F1998E' },
|
|
91
|
+
},
|
|
92
|
+
spinner: { track: primitives.gray700, active: primitives.accent },
|
|
93
|
+
input: {
|
|
94
|
+
fill: primitives.gray800,
|
|
95
|
+
border: primitives.gray700,
|
|
96
|
+
hover: primitives.gray600,
|
|
97
|
+
focus: primitives.accent,
|
|
98
|
+
error: primitives.negative,
|
|
99
|
+
text: primitives.white,
|
|
100
|
+
placeholder: primitives.gray500,
|
|
101
|
+
caret: primitives.accent400,
|
|
102
|
+
label: primitives.white,
|
|
103
|
+
caption: primitives.gray400,
|
|
104
|
+
errorText: '#F1998E',
|
|
105
|
+
},
|
|
106
|
+
keyboard: {
|
|
107
|
+
fill: primitives.gray900,
|
|
108
|
+
key: primitives.gray700,
|
|
109
|
+
keyHover: primitives.gray500,
|
|
110
|
+
special: primitives.gray800,
|
|
111
|
+
specialHover: primitives.gray600,
|
|
112
|
+
accent: primitives.accent,
|
|
113
|
+
accentHover: primitives.accent400,
|
|
114
|
+
pressed: primitives.white,
|
|
115
|
+
pressedText: primitives.black,
|
|
116
|
+
on: primitives.gray200,
|
|
117
|
+
onText: primitives.black,
|
|
118
|
+
text: primitives.white,
|
|
119
|
+
},
|
|
120
|
+
tabs: { text: primitives.gray400, textActive: primitives.white, hover: primitives.gray800, indicator: primitives.accent, border: primitives.gray800 },
|
|
121
|
+
menu: {
|
|
122
|
+
fill: primitives.gray900,
|
|
123
|
+
border: primitives.gray800,
|
|
124
|
+
hover: primitives.gray800,
|
|
125
|
+
active: primitives.gray700,
|
|
126
|
+
text: primitives.white,
|
|
127
|
+
end: primitives.gray500,
|
|
128
|
+
selected: primitives.accent400,
|
|
129
|
+
divider: primitives.gray800,
|
|
130
|
+
},
|
|
131
|
+
modal: { fill: primitives.gray900, backdrop: primitives.black, backdropOpacity: 0.6 },
|
|
132
|
+
radial: { fill: primitives.gray800, active: primitives.accent, text: primitives.white, disabledText: primitives.gray600, ring: primitives.gray900 },
|
|
133
|
+
toast: {
|
|
134
|
+
info: { fill: '#14284F', text: primitives.white, accent: primitives.accent400 },
|
|
135
|
+
positive: { fill: '#0B3B24', text: primitives.white, accent: '#3AA76D' },
|
|
136
|
+
warning: { fill: '#4A3810', text: primitives.white, accent: primitives.warning },
|
|
137
|
+
negative: { fill: '#4A140E', text: primitives.white, accent: '#F1998E' },
|
|
138
|
+
},
|
|
139
|
+
progressBar: { track: primitives.gray700, fill: primitives.accent },
|
|
140
|
+
avatar: {
|
|
141
|
+
// Picked per name (avatarColor), so a player keeps the same color everywhere.
|
|
142
|
+
palette: [primitives.accent, '#7356BF', primitives.positive, '#FF6937', '#E11900', '#0E8AA8'],
|
|
143
|
+
text: primitives.white,
|
|
144
|
+
// Around the status dot, to cut it out of the avatar (the panel color).
|
|
145
|
+
statusRing: primitives.gray900,
|
|
146
|
+
status: { online: primitives.positive, away: primitives.warning, busy: primitives.negative, offline: primitives.gray500 },
|
|
147
|
+
},
|
|
148
|
+
// Light on the dark UI, so it reads as floating above it.
|
|
149
|
+
tooltip: { fill: primitives.gray200, text: primitives.black },
|
|
150
|
+
},
|
|
151
|
+
sizing: {
|
|
152
|
+
scale0: 2,
|
|
153
|
+
scale100: 4,
|
|
154
|
+
scale200: 6,
|
|
155
|
+
scale300: 8,
|
|
156
|
+
scale400: 10,
|
|
157
|
+
scale500: 12,
|
|
158
|
+
scale550: 14,
|
|
159
|
+
scale600: 16,
|
|
160
|
+
scale700: 20,
|
|
161
|
+
scale800: 24,
|
|
162
|
+
scale900: 32,
|
|
163
|
+
scale1000: 40,
|
|
164
|
+
scale1200: 48,
|
|
165
|
+
scale1400: 56,
|
|
166
|
+
scale1600: 64,
|
|
167
|
+
},
|
|
168
|
+
borders: {
|
|
169
|
+
radius100: 2,
|
|
170
|
+
radius200: 4,
|
|
171
|
+
radius300: 8,
|
|
172
|
+
radius400: 12,
|
|
173
|
+
radius500: 16,
|
|
174
|
+
buttonBorderRadius: 16,
|
|
175
|
+
},
|
|
176
|
+
typography,
|
|
177
|
+
animation: {
|
|
178
|
+
timing100: 100,
|
|
179
|
+
timing200: 200,
|
|
180
|
+
timing300: 300,
|
|
181
|
+
timing400: 400,
|
|
182
|
+
timing500: 500,
|
|
183
|
+
easeOutCurve: 'cubic-bezier(.2, .8, .4, 1)',
|
|
184
|
+
easeInOutCurve: 'cubic-bezier(.8, .2, .6, 1)',
|
|
185
|
+
// A-Frame animation component easing names
|
|
186
|
+
easeOut3d: 'easeOutQuad',
|
|
187
|
+
easeInOut3d: 'easeInOutQuad',
|
|
188
|
+
},
|
|
189
|
+
button: {
|
|
190
|
+
sizes: {
|
|
191
|
+
mini: { height: 28, paddingX: 8, typography: 'LabelXSmall' },
|
|
192
|
+
compact: { height: 36, paddingX: 12, typography: 'LabelSmall' },
|
|
193
|
+
default: { height: 48, paddingX: 16, typography: 'LabelMedium' },
|
|
194
|
+
large: { height: 56, paddingX: 20, typography: 'LabelLarge' },
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
panel: { padding: 24, gap: 16, radius: 24 },
|
|
198
|
+
checkbox: {
|
|
199
|
+
box: 20,
|
|
200
|
+
boxRadius: 4,
|
|
201
|
+
trackWidth: 40,
|
|
202
|
+
trackHeight: 24,
|
|
203
|
+
knob: 18,
|
|
204
|
+
// Between the control and its label.
|
|
205
|
+
gap: 10,
|
|
206
|
+
typography: 'LabelMedium',
|
|
207
|
+
},
|
|
208
|
+
slider: { width: 240, trackHeight: 4, thumb: 20, gap: 8, typography: 'LabelSmall' },
|
|
209
|
+
tag: {
|
|
210
|
+
radius: 8,
|
|
211
|
+
sizes: {
|
|
212
|
+
small: { height: 24, paddingX: 8, typography: 'LabelXSmall' },
|
|
213
|
+
medium: { height: 32, paddingX: 12, typography: 'LabelSmall' },
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
tabs: { height: 40, paddingX: 12, gap: 4, indicator: 2, radius: 8, typography: 'LabelMedium' },
|
|
217
|
+
menu: {
|
|
218
|
+
itemHeight: 40,
|
|
219
|
+
paddingX: 12,
|
|
220
|
+
// Around the list, inside the menu's background.
|
|
221
|
+
padding: 6,
|
|
222
|
+
radius: 12,
|
|
223
|
+
itemRadius: 8,
|
|
224
|
+
minWidth: 200,
|
|
225
|
+
typography: 'LabelMedium',
|
|
226
|
+
endTypography: 'LabelSmall',
|
|
227
|
+
},
|
|
228
|
+
modal: { width: 420 },
|
|
229
|
+
input: {
|
|
230
|
+
width: 280,
|
|
231
|
+
height: 48,
|
|
232
|
+
paddingX: 14,
|
|
233
|
+
radius: 12,
|
|
234
|
+
border: 2,
|
|
235
|
+
// Between the label, the field and the caption.
|
|
236
|
+
gap: 6,
|
|
237
|
+
caretWidth: 2,
|
|
238
|
+
typography: 'ParagraphMedium',
|
|
239
|
+
labelTypography: 'LabelSmall',
|
|
240
|
+
captionTypography: 'LabelXSmall',
|
|
241
|
+
},
|
|
242
|
+
// VR keyboard. `key` is one key unit (a letter key is key × key).
|
|
243
|
+
keyboard: {
|
|
244
|
+
key: 52,
|
|
245
|
+
gap: 6,
|
|
246
|
+
padding: 14,
|
|
247
|
+
radius: 10,
|
|
248
|
+
typography: 'LabelLarge',
|
|
249
|
+
// Word labels (space, Done, 123, Send).
|
|
250
|
+
wordTypography: 'LabelMedium',
|
|
251
|
+
},
|
|
252
|
+
// px. Labels are shrunk to fit small rings (at most half the radius).
|
|
253
|
+
radial: { radius: 110, innerRadius: 34, gap: 3, deadzone: 24, typography: 'LabelLarge' },
|
|
254
|
+
toast: { width: 320, padding: 16 },
|
|
255
|
+
spinner: { sizes: { small: 24, medium: 32, large: 48 }, thickness: 0.125 },
|
|
256
|
+
progressBar: {
|
|
257
|
+
width: 240,
|
|
258
|
+
// Bar thickness.
|
|
259
|
+
sizes: { small: 4, medium: 8, large: 12 },
|
|
260
|
+
// Between the label row and the bar.
|
|
261
|
+
gap: 8,
|
|
262
|
+
typography: 'LabelSmall',
|
|
263
|
+
// Indeterminate (`infinite`): the moving segment's share of the bar, and one pass (ms).
|
|
264
|
+
segment: 0.3,
|
|
265
|
+
duration: 1200,
|
|
266
|
+
},
|
|
267
|
+
avatar: {
|
|
268
|
+
sizes: { small: 24, medium: 40, large: 64 },
|
|
269
|
+
// Initials size and status dot diameter, as a fraction of the avatar.
|
|
270
|
+
initials: 0.4,
|
|
271
|
+
status: 0.3,
|
|
272
|
+
statusRing: 2,
|
|
273
|
+
},
|
|
274
|
+
tooltip: { paddingX: 10, paddingY: 6, radius: 8, gap: 8, maxWidth: 240, delay: 400, typography: 'LabelSmall' },
|
|
275
|
+
space3d: {
|
|
276
|
+
// A default (48px) button comes out 0.15 m tall, the size of the original menu buttons.
|
|
277
|
+
metersPerPx: 0.003125,
|
|
278
|
+
// Text needs to be relatively bigger in-world: 16px label → 0.075 m.
|
|
279
|
+
textScale: 1.5,
|
|
280
|
+
// Slab thickness (meters).
|
|
281
|
+
depth: 0.05,
|
|
282
|
+
hoverScale: 1.04,
|
|
283
|
+
pressDepth: 0.02,
|
|
284
|
+
material: { primary: 'glass', secondary: 'solid', tertiary: 'none' },
|
|
285
|
+
panelOpacity: 0.92,
|
|
286
|
+
// Raised parts (switch knob, slider thumb) sit this far in front of their track.
|
|
287
|
+
raise: 0.012,
|
|
288
|
+
// Label color on glass, which ignores the fill color.
|
|
289
|
+
glassText: primitives.white,
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
function isPlainObject(value) {
|
|
293
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
294
|
+
}
|
|
295
|
+
function merge(base, overrides) {
|
|
296
|
+
if (!isPlainObject(base) || !isPlainObject(overrides))
|
|
297
|
+
return (overrides ?? base);
|
|
298
|
+
const out = { ...base };
|
|
299
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
300
|
+
if (value === undefined)
|
|
301
|
+
continue;
|
|
302
|
+
out[key] = isPlainObject(value) && isPlainObject(out[key]) ? merge(out[key], value) : value;
|
|
303
|
+
}
|
|
304
|
+
return out;
|
|
305
|
+
}
|
|
306
|
+
// Like Base UI's createTheme: start from the default theme and override any token.
|
|
307
|
+
export function createTheme(overrides = {}, base = defaultTheme) {
|
|
308
|
+
return merge(base, overrides);
|
|
309
|
+
}
|
|
310
|
+
export function toMeters(theme, px) {
|
|
311
|
+
return px * theme.space3d.metersPerPx;
|
|
312
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ToastKind } from './theme';
|
|
2
|
+
export type Toast = {
|
|
3
|
+
id: number;
|
|
4
|
+
kind: ToastKind;
|
|
5
|
+
message: string;
|
|
6
|
+
};
|
|
7
|
+
export type ToastOptions = {
|
|
8
|
+
kind?: ToastKind;
|
|
9
|
+
duration?: number;
|
|
10
|
+
};
|
|
11
|
+
export declare const DEFAULT_TOAST_DURATION = 4000;
|
|
12
|
+
declare class Toaster {
|
|
13
|
+
#private;
|
|
14
|
+
items: Toast[];
|
|
15
|
+
show(message: string, { kind, duration }?: ToastOptions): number;
|
|
16
|
+
info: (message: string, options?: Omit<ToastOptions, "kind">) => number;
|
|
17
|
+
positive: (message: string, options?: Omit<ToastOptions, "kind">) => number;
|
|
18
|
+
warning: (message: string, options?: Omit<ToastOptions, "kind">) => number;
|
|
19
|
+
negative: (message: string, options?: Omit<ToastOptions, "kind">) => number;
|
|
20
|
+
dismiss(id: number): void;
|
|
21
|
+
clear(): void;
|
|
22
|
+
}
|
|
23
|
+
export declare const toaster: Toaster;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// ms before a toast hides itself; 0 keeps it until closed.
|
|
2
|
+
export const DEFAULT_TOAST_DURATION = 4000;
|
|
3
|
+
class Toaster {
|
|
4
|
+
items = $state([]);
|
|
5
|
+
#nextId = 0;
|
|
6
|
+
#timers = new Map();
|
|
7
|
+
show(message, { kind = 'info', duration = DEFAULT_TOAST_DURATION } = {}) {
|
|
8
|
+
const id = this.#nextId++;
|
|
9
|
+
this.items = [...this.items, { id, kind, message }];
|
|
10
|
+
if (duration > 0)
|
|
11
|
+
this.#timers.set(id, setTimeout(() => this.dismiss(id), duration));
|
|
12
|
+
return id;
|
|
13
|
+
}
|
|
14
|
+
info = (message, options) => this.show(message, { ...options, kind: 'info' });
|
|
15
|
+
positive = (message, options) => this.show(message, { ...options, kind: 'positive' });
|
|
16
|
+
warning = (message, options) => this.show(message, { ...options, kind: 'warning' });
|
|
17
|
+
negative = (message, options) => this.show(message, { ...options, kind: 'negative' });
|
|
18
|
+
dismiss(id) {
|
|
19
|
+
clearTimeout(this.#timers.get(id));
|
|
20
|
+
this.#timers.delete(id);
|
|
21
|
+
this.items = this.items.filter((toast) => toast.id !== id);
|
|
22
|
+
}
|
|
23
|
+
clear() {
|
|
24
|
+
for (const timer of this.#timers.values())
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
this.#timers.clear();
|
|
27
|
+
this.items = [];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export const toaster = new Toaster();
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { ButtonKind, ButtonShape, ButtonSize, Dimension, TypographyVariant } from './theme';
|
|
3
|
+
export type ButtonProps = {
|
|
4
|
+
label?: string;
|
|
5
|
+
children?: Snippet;
|
|
6
|
+
kind?: ButtonKind;
|
|
7
|
+
size?: ButtonSize;
|
|
8
|
+
shape?: ButtonShape;
|
|
9
|
+
isSelected?: boolean;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
inert?: boolean;
|
|
12
|
+
onclick?: (event: Event) => void;
|
|
13
|
+
dimension?: Dimension;
|
|
14
|
+
[attribute: string]: unknown;
|
|
15
|
+
};
|
|
16
|
+
export type TextProps = {
|
|
17
|
+
text?: string;
|
|
18
|
+
children?: Snippet;
|
|
19
|
+
variant?: TypographyVariant;
|
|
20
|
+
color?: string;
|
|
21
|
+
align?: 'left' | 'center' | 'right';
|
|
22
|
+
maxWidth?: number;
|
|
23
|
+
dimension?: Dimension;
|
|
24
|
+
[attribute: string]: unknown;
|
|
25
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Whether the player is in an immersive (headset) session — the signal Surfaces use
|
|
2
|
+
// to switch from HTML to 3D. `simulated` forces 3D on desktop for testing and demos.
|
|
3
|
+
// import { xr } from '$lib/ui'; xr.simulated = true; if (xr.immersive) …
|
|
4
|
+
class XRState {
|
|
5
|
+
presenting = $state(false);
|
|
6
|
+
simulated = $state(false);
|
|
7
|
+
get immersive() {
|
|
8
|
+
return this.presenting || this.simulated;
|
|
9
|
+
}
|
|
10
|
+
#watched = new WeakSet();
|
|
11
|
+
// Follows a scene's enter-vr / exit-vr. Called by every Surface; each scene is watched once.
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
watch(scene) {
|
|
14
|
+
if (!scene || this.#watched.has(scene))
|
|
15
|
+
return;
|
|
16
|
+
this.#watched.add(scene);
|
|
17
|
+
// enter-vr also fires for non-immersive fullscreen; only a real XR session counts.
|
|
18
|
+
const update = () => (this.presenting = !!scene.xrSession && (scene.is('vr-mode') || scene.is('ar-mode')));
|
|
19
|
+
scene.addEventListener('enter-vr', update);
|
|
20
|
+
scene.addEventListener('exit-vr', update);
|
|
21
|
+
update();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export const xr = new XRState();
|
package/package.json
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gadxr/ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "gadxr design system: Svelte 5 components that render as HTML, or as A-Frame entities in 3D.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/gad-sh/xr-kit.git",
|
|
9
|
+
"directory": "packages/ui"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"svelte": "./dist/index.js",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"sideEffects": [
|
|
26
|
+
"**/*.css"
|
|
27
|
+
],
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"aframe": "^1.8.0",
|
|
30
|
+
"svelte": "^5.0.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@sveltejs/package": "^3.0.1",
|
|
34
|
+
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
|
35
|
+
"@tsconfig/svelte": "^5.0.8",
|
|
36
|
+
"aframe": "^1.8.0",
|
|
37
|
+
"svelte": "^5.57.2",
|
|
38
|
+
"svelte-check": "^4.4.6",
|
|
39
|
+
"typescript": "~6.0.2",
|
|
40
|
+
"vite": "^8.0.9"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "svelte-package -i src -o dist",
|
|
44
|
+
"test": "node --test \"tests/*.test.mjs\"",
|
|
45
|
+
"check": "svelte-check --tsconfig ./tsconfig.json"
|
|
46
|
+
}
|
|
6
47
|
}
|
package/README.md
DELETED