@carbon/motion 11.48.0 → 11.49.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/docs/surfaces.md +442 -0
- package/es/index.js +152 -8
- package/index.scss +133 -0
- package/lib/index.d.ts +2 -18
- package/lib/index.js +155 -7
- package/lib/surfaces.d.ts +165 -0
- package/lib/tokens.d.ts +28 -0
- package/package.json +5 -5
- package/scss/generated/_surfaces.scss +103 -0
- package/src/index.ts +2 -66
- package/src/surfaces.ts +116 -0
- package/src/tokens.ts +147 -0
- package/umd/index.js +155 -7
package/docs/surfaces.md
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
# Motion surfaces architecture spike
|
|
2
|
+
|
|
3
|
+
This spike covers named motion surfaces shared across Carbon:
|
|
4
|
+
|
|
5
|
+
- `disclosure`: accordion / table-row expand — reveal in place
|
|
6
|
+
- `contextual`: icon > tooltip / popover — reveal with opacity and scale
|
|
7
|
+
- `expand`: card / tile > side-panel / tearsheet — shared-element morph
|
|
8
|
+
- `invoke`: button > modal / menu / popover — shared-element morph from the
|
|
9
|
+
trigger
|
|
10
|
+
|
|
11
|
+
## Goal
|
|
12
|
+
|
|
13
|
+
The goal of this spike is to test a simple architecture where:
|
|
14
|
+
|
|
15
|
+
- motion surface definitions live in `@carbon/motion`
|
|
16
|
+
- Sass values are generated from the same source definition
|
|
17
|
+
- React owns lifecycle and presence logic
|
|
18
|
+
- Motion runs reveal and shared-element animations
|
|
19
|
+
- Carbon components keep ownership of layout, focus, dialog behavior, and
|
|
20
|
+
semantics
|
|
21
|
+
|
|
22
|
+
## Files created
|
|
23
|
+
|
|
24
|
+
### `packages/motion/src/surfaces.ts`
|
|
25
|
+
|
|
26
|
+
This file defines the shared motion surface data.
|
|
27
|
+
|
|
28
|
+
What we added:
|
|
29
|
+
|
|
30
|
+
- the `disclosure`, `contextual`, `expand`, and `invoke` surfaces
|
|
31
|
+
- reveal vs shared-element surface kinds
|
|
32
|
+
- optional enter / exit keyframes on shared-element surfaces (used by `expand`
|
|
33
|
+
for a CSS-replicable opacity / scale layer)
|
|
34
|
+
- optional `origin: 'trigger'` on shared-element surfaces (used by `invoke`)
|
|
35
|
+
- `getMotionSurface()`
|
|
36
|
+
- `MotionSurfaceName`
|
|
37
|
+
|
|
38
|
+
Why:
|
|
39
|
+
|
|
40
|
+
- to keep the motion intent in one shared place
|
|
41
|
+
- to avoid putting React or Motion-specific values in the base definition
|
|
42
|
+
- so Sass, React, and future engines can read the same named intents
|
|
43
|
+
|
|
44
|
+
What it uses:
|
|
45
|
+
|
|
46
|
+
- `DurationName`
|
|
47
|
+
- `EasingName`
|
|
48
|
+
- `EasingMode`
|
|
49
|
+
|
|
50
|
+
### `packages/motion/tasks/build.mjs`
|
|
51
|
+
|
|
52
|
+
This file generates Sass from the TypeScript surface definition.
|
|
53
|
+
|
|
54
|
+
What we added:
|
|
55
|
+
|
|
56
|
+
- a build script that reads the exported `surfaces`
|
|
57
|
+
- conversion from JavaScript values to Sass values
|
|
58
|
+
- output generation for `scss/generated/_surfaces.scss`
|
|
59
|
+
|
|
60
|
+
Why:
|
|
61
|
+
|
|
62
|
+
- to keep TypeScript and Sass in sync
|
|
63
|
+
- to follow the request that root definitions live in `@carbon/motion`
|
|
64
|
+
|
|
65
|
+
What it uses:
|
|
66
|
+
|
|
67
|
+
- `@carbon/scss-generator`
|
|
68
|
+
- Node `fs/promises`
|
|
69
|
+
- Node `path`
|
|
70
|
+
|
|
71
|
+
### `packages/motion/scss/generated/_surfaces.scss`
|
|
72
|
+
|
|
73
|
+
This is a generated file.
|
|
74
|
+
|
|
75
|
+
What it contains:
|
|
76
|
+
|
|
77
|
+
- the Sass map for `$surfaces`
|
|
78
|
+
|
|
79
|
+
Why:
|
|
80
|
+
|
|
81
|
+
- so Sass can read the same surface data used by JavaScript
|
|
82
|
+
|
|
83
|
+
### `packages/react/src/internal/motion/useMotionSurface.ts`
|
|
84
|
+
|
|
85
|
+
This file is the internal React helper that resolves a named surface into
|
|
86
|
+
Motion-ready values.
|
|
87
|
+
|
|
88
|
+
What we added:
|
|
89
|
+
|
|
90
|
+
- token resolution from `@carbon/motion` (duration and easing names into seconds
|
|
91
|
+
and cubic-bezier tuples)
|
|
92
|
+
- reveal surfaces resolved to `initial` / `animate` / `exit` targets
|
|
93
|
+
- shared-element surfaces resolved to enter / exit transitions
|
|
94
|
+
- optional `animate` / `exit` keyframes on shared-element surfaces when the
|
|
95
|
+
definition includes them (`expand`)
|
|
96
|
+
- reduced-motion gating through `useMotionEnabled()`
|
|
97
|
+
|
|
98
|
+
Why:
|
|
99
|
+
|
|
100
|
+
- to keep Motion-specific numeric forms out of `@carbon/motion`
|
|
101
|
+
- to create a reusable React helper for every surface name
|
|
102
|
+
|
|
103
|
+
What it uses:
|
|
104
|
+
|
|
105
|
+
- `getMotionSurface()`
|
|
106
|
+
- `resolveDuration()`
|
|
107
|
+
- `resolveEasing()`
|
|
108
|
+
- `useMotionEnabled()`
|
|
109
|
+
- types from `motion/react`
|
|
110
|
+
|
|
111
|
+
### `packages/react/src/internal/motion/useMotionEnabled.ts`
|
|
112
|
+
|
|
113
|
+
This file is the accessibility gate for the surface API.
|
|
114
|
+
|
|
115
|
+
What we added:
|
|
116
|
+
|
|
117
|
+
- a hook that returns whether surface motion should run
|
|
118
|
+
- a bail-out when the user prefers reduced motion
|
|
119
|
+
|
|
120
|
+
Why:
|
|
121
|
+
|
|
122
|
+
- surfaces never animate when users request reduced motion
|
|
123
|
+
- components fall back to default rendering (and their baseline CSS transitions)
|
|
124
|
+
|
|
125
|
+
What it uses:
|
|
126
|
+
|
|
127
|
+
- `useReducedMotion` from `motion/react`
|
|
128
|
+
|
|
129
|
+
### `packages/react/src/internal/motion/MotionSurface.tsx`
|
|
130
|
+
|
|
131
|
+
This file is the React surface entrypoint.
|
|
132
|
+
|
|
133
|
+
What we added:
|
|
134
|
+
|
|
135
|
+
- `MotionSurface` for reveal and shared-element destinations
|
|
136
|
+
- `MotionSurfaceOrigin` for shared-element sources, paired by `surfaceId`
|
|
137
|
+
- `layoutId` morphs for shared-element surfaces via Motion React
|
|
138
|
+
- optional opacity / scale keyframes on top of the morph when the surface
|
|
139
|
+
defines enter / exit (`expand`)
|
|
140
|
+
- `AnimatePresence` for enter / exit lifecycle
|
|
141
|
+
- a hold on enclosing Carbon presence (for example `ModalPresence`) until
|
|
142
|
+
Motion's exit finishes
|
|
143
|
+
- immediate mount / unmount with no Motion when reduced motion is on
|
|
144
|
+
|
|
145
|
+
Why:
|
|
146
|
+
|
|
147
|
+
- to apply a named surface without rewriting Carbon components as Motion
|
|
148
|
+
components
|
|
149
|
+
- to keep presence and reduced-motion behavior in one place
|
|
150
|
+
|
|
151
|
+
What it uses:
|
|
152
|
+
|
|
153
|
+
- `useMotionSurface()`
|
|
154
|
+
- `AnimatePresence` and `motion` from `motion/react`
|
|
155
|
+
- `PresenceHoldContext`
|
|
156
|
+
|
|
157
|
+
### `packages/react/src/internal/motion/__tests__/useMotionSurface-test.js`
|
|
158
|
+
|
|
159
|
+
This file tests the React surface resolver.
|
|
160
|
+
|
|
161
|
+
What we added:
|
|
162
|
+
|
|
163
|
+
- shared-element resolution tests for `expand` and `invoke`
|
|
164
|
+
- reveal resolution tests for `contextual`
|
|
165
|
+
- reduced-motion / `enabled` gating test
|
|
166
|
+
|
|
167
|
+
### `packages/react/src/internal/motion/__tests__/MotionSurface-test.js`
|
|
168
|
+
|
|
169
|
+
This file tests the React surface components.
|
|
170
|
+
|
|
171
|
+
What we added:
|
|
172
|
+
|
|
173
|
+
- open rendering test
|
|
174
|
+
- reveal enter / exit behavior
|
|
175
|
+
- shared-element pairing with `MotionSurfaceOrigin`
|
|
176
|
+
- reduced-motion mount / unmount behavior
|
|
177
|
+
- `onExitComplete` behavior
|
|
178
|
+
|
|
179
|
+
### `packages/react/src/internal/motion/__tests__/MotionSurfacePresence-test.js`
|
|
180
|
+
|
|
181
|
+
This file tests presence hold integration.
|
|
182
|
+
|
|
183
|
+
What we added:
|
|
184
|
+
|
|
185
|
+
- hold-until-exit behavior when a Carbon presence context is present
|
|
186
|
+
- reopen-during-exit behavior
|
|
187
|
+
|
|
188
|
+
### `packages/react/src/internal/motion/__tests__/useMotionEnabled-test.js`
|
|
189
|
+
|
|
190
|
+
This file tests the reduced-motion gate.
|
|
191
|
+
|
|
192
|
+
### `packages/react/src/components/Motion/Expand.stories.js`
|
|
193
|
+
|
|
194
|
+
This file is the Storybook proof of concept for the `expand` surface.
|
|
195
|
+
|
|
196
|
+
What we added:
|
|
197
|
+
|
|
198
|
+
- a grid of Carbon `ClickableTile` origins
|
|
199
|
+
- each tile wrapped in `MotionSurfaceOrigin`
|
|
200
|
+
- a demo dialog destination driven by `MotionSurface`
|
|
201
|
+
- tile-to-dialog morph and morph-back on close
|
|
202
|
+
|
|
203
|
+
Why:
|
|
204
|
+
|
|
205
|
+
- to test a real tile-to-dialog morph with Carbon components
|
|
206
|
+
- to keep the demo focused on `MotionSurface` rather than full Modal integration
|
|
207
|
+
|
|
208
|
+
What it uses:
|
|
209
|
+
|
|
210
|
+
- `ClickableTile`
|
|
211
|
+
- `AspectRatio`
|
|
212
|
+
- `Grid` and `Column`
|
|
213
|
+
- `MotionSurfaceOrigin`
|
|
214
|
+
- `DemoDialog`
|
|
215
|
+
|
|
216
|
+
### `packages/react/src/components/Motion/Invoke.stories.js`
|
|
217
|
+
|
|
218
|
+
This file is the Storybook proof of concept for the `invoke` surface.
|
|
219
|
+
|
|
220
|
+
What we added:
|
|
221
|
+
|
|
222
|
+
- `ButtonToDialog`: button trigger morphs into the dialog
|
|
223
|
+
- `TileToDialog`: tile trigger morphs into the dialog
|
|
224
|
+
- each trigger wrapped in `MotionSurfaceOrigin` with `surface="invoke"`
|
|
225
|
+
|
|
226
|
+
Why:
|
|
227
|
+
|
|
228
|
+
- to show the invoke shared-element intent (morph from the trigger)
|
|
229
|
+
- to compare button and tile origins with the same surface
|
|
230
|
+
|
|
231
|
+
What it uses:
|
|
232
|
+
|
|
233
|
+
- `Button`
|
|
234
|
+
- `ClickableTile`
|
|
235
|
+
- `MotionSurfaceOrigin`
|
|
236
|
+
- `DemoDialog`
|
|
237
|
+
|
|
238
|
+
### `packages/react/src/components/Motion/DemoDialog.js`
|
|
239
|
+
|
|
240
|
+
This file is story-only dialog chrome for the surface demos.
|
|
241
|
+
|
|
242
|
+
What we added:
|
|
243
|
+
|
|
244
|
+
- modal overlay classes for the backdrop fade
|
|
245
|
+
- a `MotionSurface` container so Motion owns the morph
|
|
246
|
+
- basic dialog semantics and a close control
|
|
247
|
+
|
|
248
|
+
Why:
|
|
249
|
+
|
|
250
|
+
- threading a surface through the real `Modal` is the next integration step
|
|
251
|
+
- the demo stays minimal so the story reads as a `MotionSurface` example
|
|
252
|
+
|
|
253
|
+
What it uses:
|
|
254
|
+
|
|
255
|
+
- `MotionSurface`
|
|
256
|
+
- Carbon modal class names via `usePrefix()`
|
|
257
|
+
- `Button`
|
|
258
|
+
|
|
259
|
+
### `packages/react/src/components/Motion/surfaces.stories.scss`
|
|
260
|
+
|
|
261
|
+
This file styles the Motion surface stories.
|
|
262
|
+
|
|
263
|
+
What we added:
|
|
264
|
+
|
|
265
|
+
- layout helpers for the demo grid and origins
|
|
266
|
+
- neutralization of stock modal container transitions so CSS does not fight
|
|
267
|
+
Motion
|
|
268
|
+
- hover emphasis for the expand tile story
|
|
269
|
+
- a larger destination size for the expand dialog
|
|
270
|
+
|
|
271
|
+
Why:
|
|
272
|
+
|
|
273
|
+
- to keep demo layout and chrome styling out of the surface definitions
|
|
274
|
+
- to avoid competing CSS animations during the morph
|
|
275
|
+
|
|
276
|
+
What it uses:
|
|
277
|
+
|
|
278
|
+
- `@carbon/styles/scss/motion`
|
|
279
|
+
- `@carbon/styles/scss/spacing`
|
|
280
|
+
- `@carbon/styles/scss/utilities/box-shadow`
|
|
281
|
+
|
|
282
|
+
## Files modified
|
|
283
|
+
|
|
284
|
+
### `packages/motion/src/tokens.ts`
|
|
285
|
+
|
|
286
|
+
What changed:
|
|
287
|
+
|
|
288
|
+
- added named duration support
|
|
289
|
+
- added easing resolution helpers
|
|
290
|
+
- kept the existing token source as the single source of truth
|
|
291
|
+
|
|
292
|
+
Why:
|
|
293
|
+
|
|
294
|
+
- JavaScript adapters need numeric timing and easing values
|
|
295
|
+
|
|
296
|
+
### `packages/motion/index.scss`
|
|
297
|
+
|
|
298
|
+
What changed:
|
|
299
|
+
|
|
300
|
+
- added generated `$surfaces`
|
|
301
|
+
- added `surface()` Sass function
|
|
302
|
+
- added `surface()` Sass mixin for reveal surfaces (`@starting-style` entrance,
|
|
303
|
+
guarded by `prefers-reduced-motion: no-preference`)
|
|
304
|
+
- shared-element surfaces are rejected by the mixin (no CSS-only form)
|
|
305
|
+
|
|
306
|
+
Why:
|
|
307
|
+
|
|
308
|
+
- Sass needs access to the same surface data
|
|
309
|
+
- reveal surfaces can run in plain CSS; shared-element morphs need a JavaScript
|
|
310
|
+
engine
|
|
311
|
+
|
|
312
|
+
### `packages/motion/__tests__/motion-test.js`
|
|
313
|
+
|
|
314
|
+
What changed:
|
|
315
|
+
|
|
316
|
+
- added tests for expand, invoke, disclosure, and contextual surfaces
|
|
317
|
+
- added Sass parity tests for expand and disclosure
|
|
318
|
+
- added error handling for unknown surfaces and for applying the shared-element
|
|
319
|
+
mixin in CSS
|
|
320
|
+
|
|
321
|
+
### `packages/motion/__tests__/__snapshots__/motion-test.js.snap`
|
|
322
|
+
|
|
323
|
+
What changed:
|
|
324
|
+
|
|
325
|
+
- snapshot updated because the public motion API changed
|
|
326
|
+
|
|
327
|
+
### `packages/motion/docs/surfaces.md`
|
|
328
|
+
|
|
329
|
+
What changed:
|
|
330
|
+
|
|
331
|
+
- rewritten to match the current surface catalog and React adapter
|
|
332
|
+
- documented reveal vs shared-element kinds, story demos, gaps, and limits
|
|
333
|
+
|
|
334
|
+
### `packages/motion/package.json`
|
|
335
|
+
|
|
336
|
+
What changed:
|
|
337
|
+
|
|
338
|
+
- added the surface Sass generation step to the build
|
|
339
|
+
- added `@carbon/scss-generator`
|
|
340
|
+
|
|
341
|
+
### `packages/react/package.json`
|
|
342
|
+
|
|
343
|
+
What changed:
|
|
344
|
+
|
|
345
|
+
- added `motion` as a dependency
|
|
346
|
+
|
|
347
|
+
Why:
|
|
348
|
+
|
|
349
|
+
- the React adapter uses `motion/react` for `layoutId`, `AnimatePresence`, and
|
|
350
|
+
reduced-motion detection
|
|
351
|
+
|
|
352
|
+
### `yarn.lock`
|
|
353
|
+
|
|
354
|
+
What changed:
|
|
355
|
+
|
|
356
|
+
- lockfile updated for dependency changes
|
|
357
|
+
|
|
358
|
+
## Important implementation notes
|
|
359
|
+
|
|
360
|
+
### Surface kinds
|
|
361
|
+
|
|
362
|
+
Surfaces are either:
|
|
363
|
+
|
|
364
|
+
- `reveal`: one element animates between enter / exit styles. Works in CSS (Sass
|
|
365
|
+
mixin) and in Motion React.
|
|
366
|
+
- `shared-element`: one element morphs into another. Needs a JavaScript engine.
|
|
367
|
+
In React this is Motion `layoutId` pairing between `MotionSurfaceOrigin` and
|
|
368
|
+
`MotionSurface`.
|
|
369
|
+
|
|
370
|
+
`expand` and `invoke` are both shared-element. They share the same React morph
|
|
371
|
+
path today. They differ in tokens:
|
|
372
|
+
|
|
373
|
+
- `expand`: productive easing, plus optional enter / exit opacity and scale
|
|
374
|
+
keyframes layered on the morph
|
|
375
|
+
- `invoke`: expressive easing, `origin: 'trigger'` (carried through the
|
|
376
|
+
resolver; the React morph path does not branch on `origin` yet)
|
|
377
|
+
|
|
378
|
+
### Demo dialog ownership
|
|
379
|
+
|
|
380
|
+
We did not rebuild the Carbon Modal for the spike demos.
|
|
381
|
+
|
|
382
|
+
`DemoDialog` reuses modal overlay classes for the backdrop and puts
|
|
383
|
+
`MotionSurface` on the container. The real `Modal` still owns production dialog
|
|
384
|
+
behavior:
|
|
385
|
+
|
|
386
|
+
- dialog semantics
|
|
387
|
+
- overlay behavior
|
|
388
|
+
- focus trap
|
|
389
|
+
- close behavior
|
|
390
|
+
- submit and secondary actions
|
|
391
|
+
|
|
392
|
+
Wiring surfaces through the real `Modal` is the next integration step.
|
|
393
|
+
|
|
394
|
+
### Why the React adapter uses Motion React `layoutId`
|
|
395
|
+
|
|
396
|
+
The current proof of concept uses declarative Motion React (`layoutId`,
|
|
397
|
+
`AnimatePresence`) instead of the earlier imperative `animate()` FLIP adapter.
|
|
398
|
+
|
|
399
|
+
Why:
|
|
400
|
+
|
|
401
|
+
- shared-element pairing maps cleanly to `MotionSurfaceOrigin` + `MotionSurface`
|
|
402
|
+
with a matching `surfaceId`
|
|
403
|
+
- reveal surfaces map cleanly to Motion `initial` / `animate` / `exit`
|
|
404
|
+
- Carbon presence can hold exit until Motion finishes
|
|
405
|
+
|
|
406
|
+
Tradeoff:
|
|
407
|
+
|
|
408
|
+
- layout projection can stretch in-origin content (for example tile text) during
|
|
409
|
+
the morph
|
|
410
|
+
- content crossfade / shell-only morph and axis-sequenced expand choreography
|
|
411
|
+
are still open follow-ups
|
|
412
|
+
|
|
413
|
+
### Reduced motion
|
|
414
|
+
|
|
415
|
+
`prefers-reduced-motion` is intentionally not stored on the surface definition.
|
|
416
|
+
Framework adapters bail before running, and the Sass reveal mixin wraps
|
|
417
|
+
animation in `prefers-reduced-motion: no-preference`.
|
|
418
|
+
|
|
419
|
+
## Current references
|
|
420
|
+
|
|
421
|
+
- Main architecture doc: `packages/motion/docs/surfaces.md`
|
|
422
|
+
- Surface definitions: `packages/motion/src/surfaces.ts`
|
|
423
|
+
- React surface components:
|
|
424
|
+
`packages/react/src/internal/motion/MotionSurface.tsx`
|
|
425
|
+
- Expand story: `packages/react/src/components/Motion/Expand.stories.js`
|
|
426
|
+
- Invoke story: `packages/react/src/components/Motion/Invoke.stories.js`
|
|
427
|
+
- Carbon Modal story: `packages/react/src/components/Modal/Modal.stories.js`
|
|
428
|
+
- Carbon Modal Sass: `packages/styles/scss/components/modal/_modal.scss`
|
|
429
|
+
- Carbon Tile Sass: `packages/styles/scss/components/tile/_tile.scss`
|
|
430
|
+
|
|
431
|
+
## Current limits
|
|
432
|
+
|
|
433
|
+
- Shared-element React path does not yet differentiate `expand` and `invoke`
|
|
434
|
+
beyond easing and optional keyframes / `origin` metadata
|
|
435
|
+
- `origin: 'trigger'` is resolved but not used to change the morph
|
|
436
|
+
- Axis-sequenced expand (width then height) is not implemented
|
|
437
|
+
- Content stretch during `layoutId` morphs is unresolved (shell morph + content
|
|
438
|
+
crossfade is the likely follow-up)
|
|
439
|
+
- The proof of concept currently targets React only
|
|
440
|
+
- Story demos use `DemoDialog`, not the production `Modal` API
|
|
441
|
+
- Reveal surfaces (`disclosure`, `contextual`) are defined and resolved, but do
|
|
442
|
+
not yet have dedicated Storybook demos alongside expand / invoke
|
package/es/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region src/
|
|
1
|
+
//#region src/tokens.ts
|
|
2
2
|
/**
|
|
3
3
|
* Copyright IBM Corp. 2018, 2026
|
|
4
4
|
*
|
|
@@ -31,18 +31,71 @@ const unstable_tokens = [
|
|
|
31
31
|
"durationSlow01",
|
|
32
32
|
"durationSlow02"
|
|
33
33
|
];
|
|
34
|
+
const durations = {
|
|
35
|
+
"fast-01": durationFast01,
|
|
36
|
+
"fast-02": durationFast02,
|
|
37
|
+
"moderate-01": durationModerate01,
|
|
38
|
+
"moderate-02": durationModerate02,
|
|
39
|
+
"slow-01": durationSlow01,
|
|
40
|
+
"slow-02": durationSlow02
|
|
41
|
+
};
|
|
42
|
+
const easingCurves = {
|
|
43
|
+
standard: {
|
|
44
|
+
productive: [
|
|
45
|
+
.2,
|
|
46
|
+
0,
|
|
47
|
+
.38,
|
|
48
|
+
.9
|
|
49
|
+
],
|
|
50
|
+
expressive: [
|
|
51
|
+
.4,
|
|
52
|
+
.14,
|
|
53
|
+
.3,
|
|
54
|
+
1
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
entrance: {
|
|
58
|
+
productive: [
|
|
59
|
+
0,
|
|
60
|
+
0,
|
|
61
|
+
.38,
|
|
62
|
+
.9
|
|
63
|
+
],
|
|
64
|
+
expressive: [
|
|
65
|
+
0,
|
|
66
|
+
0,
|
|
67
|
+
.3,
|
|
68
|
+
1
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
exit: {
|
|
72
|
+
productive: [
|
|
73
|
+
.2,
|
|
74
|
+
0,
|
|
75
|
+
1,
|
|
76
|
+
.9
|
|
77
|
+
],
|
|
78
|
+
expressive: [
|
|
79
|
+
.4,
|
|
80
|
+
.14,
|
|
81
|
+
1,
|
|
82
|
+
1
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const formatEasing = (curve) => `cubic-bezier(${curve.join(", ")})`;
|
|
34
87
|
const easings = {
|
|
35
88
|
standard: {
|
|
36
|
-
productive:
|
|
37
|
-
expressive:
|
|
89
|
+
productive: formatEasing(easingCurves.standard.productive),
|
|
90
|
+
expressive: formatEasing(easingCurves.standard.expressive)
|
|
38
91
|
},
|
|
39
92
|
entrance: {
|
|
40
|
-
productive:
|
|
41
|
-
expressive:
|
|
93
|
+
productive: formatEasing(easingCurves.entrance.productive),
|
|
94
|
+
expressive: formatEasing(easingCurves.entrance.expressive)
|
|
42
95
|
},
|
|
43
96
|
exit: {
|
|
44
|
-
productive:
|
|
45
|
-
expressive:
|
|
97
|
+
productive: formatEasing(easingCurves.exit.productive),
|
|
98
|
+
expressive: formatEasing(easingCurves.exit.expressive)
|
|
46
99
|
}
|
|
47
100
|
};
|
|
48
101
|
const motion = (name, mode) => {
|
|
@@ -51,5 +104,96 @@ const motion = (name, mode) => {
|
|
|
51
104
|
if (!easing[mode]) throw new Error(`Unable to find a mode for the easing \`${name}\` called: \`${mode}\`. Expected one of: ${Object.keys(easing).join(", ")}`);
|
|
52
105
|
return easing[mode];
|
|
53
106
|
};
|
|
107
|
+
const resolveEasing = (name, mode) => {
|
|
108
|
+
if (!easingCurves[name]) throw new Error(`Unable to find easing \`${name}\` in our supported easings. Expected one of: ${Object.keys(easingCurves).join(", ")}`);
|
|
109
|
+
const easing = easingCurves[name];
|
|
110
|
+
if (!easing[mode]) throw new Error(`Unable to find a mode for the easing \`${name}\` called: \`${mode}\`. Expected one of: ${Object.keys(easing).join(", ")}`);
|
|
111
|
+
return easing[mode];
|
|
112
|
+
};
|
|
113
|
+
const resolveDuration = (name) => {
|
|
114
|
+
const duration = durations[name];
|
|
115
|
+
if (!duration) throw new Error(`Unable to find duration \`${name}\` in our supported durations. Expected one of: ${Object.keys(durations).join(", ")}`);
|
|
116
|
+
return duration;
|
|
117
|
+
};
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/surfaces.ts
|
|
120
|
+
/**
|
|
121
|
+
* Named motion intents. These definitions are engine and framework agnostic.
|
|
122
|
+
*
|
|
123
|
+
* `prefers-reduced-motion` is intentionally not represented here: surfaces
|
|
124
|
+
* never animate when the users request reduced motion. Framework adapters
|
|
125
|
+
* bail before running, and the Sass output is wrapped in a
|
|
126
|
+
* `prefers-reduced-motion: no-preference` media query
|
|
127
|
+
*/
|
|
128
|
+
const surfaces = {
|
|
129
|
+
disclosure: {
|
|
130
|
+
kind: "reveal",
|
|
131
|
+
duration: "moderate-01",
|
|
132
|
+
enter: {
|
|
133
|
+
blockSize: "auto",
|
|
134
|
+
opacity: 1
|
|
135
|
+
},
|
|
136
|
+
exit: {
|
|
137
|
+
blockSize: 0,
|
|
138
|
+
opacity: 0
|
|
139
|
+
},
|
|
140
|
+
enterEasing: ["entrance", "productive"],
|
|
141
|
+
exitEasing: ["exit", "productive"]
|
|
142
|
+
},
|
|
143
|
+
contextual: {
|
|
144
|
+
kind: "reveal",
|
|
145
|
+
duration: "fast-02",
|
|
146
|
+
enter: {
|
|
147
|
+
opacity: 1,
|
|
148
|
+
transform: "scale(1)"
|
|
149
|
+
},
|
|
150
|
+
exit: {
|
|
151
|
+
opacity: 0,
|
|
152
|
+
transform: "scale(0.96)"
|
|
153
|
+
},
|
|
154
|
+
enterEasing: ["entrance", "expressive"],
|
|
155
|
+
exitEasing: ["exit", "expressive"]
|
|
156
|
+
},
|
|
157
|
+
stretch: {
|
|
158
|
+
kind: "reveal",
|
|
159
|
+
duration: "slow-01",
|
|
160
|
+
enter: {
|
|
161
|
+
opacity: 1,
|
|
162
|
+
clipPath: "inset(0 0 0 0)"
|
|
163
|
+
},
|
|
164
|
+
exit: {
|
|
165
|
+
opacity: 0,
|
|
166
|
+
clipPath: "inset(50% 0 50% 0)"
|
|
167
|
+
},
|
|
168
|
+
enterEasing: ["entrance", "expressive"],
|
|
169
|
+
exitEasing: ["exit", "expressive"]
|
|
170
|
+
},
|
|
171
|
+
expand: {
|
|
172
|
+
kind: "shared-element",
|
|
173
|
+
duration: "moderate-02",
|
|
174
|
+
enter: {
|
|
175
|
+
opacity: 1,
|
|
176
|
+
transform: "scale(1)"
|
|
177
|
+
},
|
|
178
|
+
exit: {
|
|
179
|
+
opacity: 0,
|
|
180
|
+
transform: "scale(0.96)"
|
|
181
|
+
},
|
|
182
|
+
enterEasing: ["standard", "productive"],
|
|
183
|
+
exitEasing: ["standard", "productive"]
|
|
184
|
+
},
|
|
185
|
+
invoke: {
|
|
186
|
+
kind: "shared-element",
|
|
187
|
+
origin: "trigger",
|
|
188
|
+
duration: "moderate-02",
|
|
189
|
+
enterEasing: ["standard", "expressive"],
|
|
190
|
+
exitEasing: ["standard", "expressive"]
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
const getMotionSurface = (name) => {
|
|
194
|
+
const surface = surfaces[name];
|
|
195
|
+
if (!surface) throw new Error(`Unable to find motion surface \`${name}\`. Expected one of: ` + Object.keys(surfaces).join(", "));
|
|
196
|
+
return surface;
|
|
197
|
+
};
|
|
54
198
|
//#endregion
|
|
55
|
-
export { durationFast01, durationFast02, durationModerate01, durationModerate02, durationSlow01, durationSlow02, easings, fast01, fast02, moderate01, moderate02, motion, slow01, slow02, unstable_tokens };
|
|
199
|
+
export { durationFast01, durationFast02, durationModerate01, durationModerate02, durationSlow01, durationSlow02, easings, fast01, fast02, getMotionSurface, moderate01, moderate02, motion, resolveDuration, resolveEasing, slow01, slow02, surfaces, unstable_tokens };
|