@thinkpixellab-public/px-vue 4.1.7 → 4.1.8

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.
@@ -0,0 +1,137 @@
1
+ <!--
2
+ Describe this component...
3
+ -->
4
+ <template>
5
+ <div :class="bem()">
6
+ <transition-group :class="bem('group')" name="px-background-fader__fade" tag="div">
7
+ <div
8
+ v-for="background in backgrounds"
9
+ :key="background.id"
10
+ :class="bem('background')"
11
+ :style="{
12
+ transitionDuration: duration ? duration + 's' : null,
13
+ }"
14
+ >
15
+ <slot name="background" v-bind="background">
16
+ <div
17
+ :class="[background.value, ...backgroundClassArray]"
18
+ :style="backgroundStyle"
19
+ />
20
+ </slot>
21
+ <slot name="default" />
22
+ </div>
23
+ </transition-group>
24
+ </div>
25
+ </template>
26
+
27
+ <script>
28
+ export default {
29
+ name: 'px-background-fader',
30
+ props: {
31
+ /** A background items can be anything. This will be made available to the background slot
32
+ * and the slot can render accordingly. The default slot assumes that the background is a
33
+ * class names and creates a div and sets the class. When the background is set, we will
34
+ * crossfade between the contents of the two slots. */
35
+ background: { default: null },
36
+
37
+ /** An additional class that gets applied to the background element in the default slot. */
38
+ backgroundClass: { default: 'px-background-fader__bg' },
39
+
40
+ /** Value for the style property that gets applied to the background element in th default slot */
41
+ backgroundStyle: { default: null },
42
+
43
+ /**
44
+ * The duration of the fade animation in seconds.
45
+ */
46
+ duration: { type: Number, default: 0 },
47
+ },
48
+ data() {
49
+ const backgroundId = 0;
50
+ const backgrounds = this.background ? [{ id: backgroundId, value: this.background }] : [];
51
+ return {
52
+ backgrounds,
53
+ backgroundId,
54
+ fadeLeaveDelay: this.duration ? this.duration : '',
55
+ };
56
+ },
57
+
58
+ computed: {
59
+ backgroundClassArray() {
60
+ if (Array.isArray(this.backgroundClass)) {
61
+ return this.backgroundClass;
62
+ }
63
+ return [this.backgroundClass];
64
+ },
65
+ fadeLeaveDelayCalc() {
66
+ if (this.fadeLeaveDelay !== null) {
67
+ return this.fadeLeaveDelay + 's';
68
+ }
69
+ return '--fade-leave-delay-def';
70
+ },
71
+ },
72
+ watch: {
73
+ background(nv, ov) {
74
+ if (this.compareItems(nv, ov)) {
75
+ return;
76
+ }
77
+
78
+ this.fadeLeaveDelay == !!ov ? this.duration : 0;
79
+
80
+ if (ov && this.backgrounds.length) {
81
+ this.backgrounds.shift();
82
+ }
83
+
84
+ if (nv) {
85
+ this.backgroundId++;
86
+ this.backgrounds.push({ id: this.backgroundId, value: nv });
87
+ }
88
+ },
89
+ },
90
+ methods: {
91
+ compareItems(a, b) {
92
+ if (a == b) {
93
+ return true;
94
+ }
95
+
96
+ if (typeof a == 'object' && typeof b == 'object') {
97
+ return JSON.stringify(a) == JSON.stringify(b);
98
+ }
99
+
100
+ return false;
101
+ },
102
+ },
103
+ };
104
+ </script>
105
+
106
+ <style lang="scss">
107
+ @use '../styles/px.scss' as *;
108
+
109
+ @include vue-transition(px-background-fader__fade) {
110
+ $dur: 0.5s;
111
+
112
+ @include vt-transition {
113
+ @include transition(opacity, $dur: $dur);
114
+ }
115
+ @include vt-leaving() {
116
+ --fade-leave-delay-def: $dur;
117
+ transition-delay: v-bind(fadeLeaveDelayCalc);
118
+ }
119
+ @include vt-hidden {
120
+ opacity: 0;
121
+ }
122
+ }
123
+
124
+ .px-background-fader {
125
+ width: 100%;
126
+ height: 100%;
127
+ position: relative;
128
+
129
+ &__group {
130
+ display: contents;
131
+ }
132
+
133
+ &__bg {
134
+ @include abs();
135
+ }
136
+ }
137
+ </style>
@@ -1,5 +1,6 @@
1
1
  <template>
2
- <button
2
+ <component
3
+ :is="tag"
3
4
  :class="
4
5
  bem({
5
6
  ...variantsMap,
@@ -10,7 +11,7 @@
10
11
  @click="tryToggleChecked"
11
12
  >
12
13
  <px-icon :class="iconClass" v-bind="calcIconAttrs" />
13
- </button>
14
+ </component>
14
15
  </template>
15
16
 
16
17
  <script>
@@ -67,6 +68,8 @@ export default {
67
68
  toggles: { type: Boolean, default: false },
68
69
 
69
70
  iconClass: { type: String, default: null },
71
+
72
+ tag: { type: String, default: 'button' },
70
73
  },
71
74
  computed: {
72
75
  calcIcon() {
@@ -0,0 +1,399 @@
1
+ <template>
2
+ <div :class="[bem({ fit }), isEmpty ? emptyClass : '', isError ? errorClass : '']">
3
+ <transition :name="transitionName">
4
+ <img
5
+ v-if="imageA"
6
+ :key="'A'"
7
+ :class="
8
+ bem('img', {
9
+ [imageA.state]:
10
+ imageA.state === IMAGE_STATES.LOADING ||
11
+ imageA.state === IMAGE_STATES.UNLOADING
12
+ ? loadingVisible
13
+ : true,
14
+ })
15
+ "
16
+ :src="imageA.src"
17
+ @load="imageLoad(imageA)"
18
+ @error="imageError($event, imageA)"
19
+ />
20
+ </transition>
21
+ <transition :name="transitionName">
22
+ <img
23
+ v-if="imageB"
24
+ :key="'B'"
25
+ :class="
26
+ bem('img', {
27
+ [imageB.state]:
28
+ imageB.state === IMAGE_STATES.LOADING ||
29
+ imageB.state === IMAGE_STATES.UNLOADING
30
+ ? loadingVisible
31
+ : true,
32
+ })
33
+ "
34
+ :src="imageB.src"
35
+ @load="imageLoad(imageB)"
36
+ @error="imageError($event, imageB)"
37
+ />
38
+ </transition>
39
+
40
+ <slot name="placeholder" v-if="isEmpty"></slot>
41
+
42
+ <slot name="error" v-if="isError">
43
+ <PxIcon :class="bem('error-icon')" size="1.5em" :src="AlertTriangleSvgUrl" />
44
+ </slot>
45
+
46
+ <slot name="loading" v-if="loadingVisible">
47
+ <PxSpinner v-if="showLoadingSpinner" :visible="true" />
48
+ </slot>
49
+ </div>
50
+ </template>
51
+
52
+ <script>
53
+ import PxSpinner from './PxSpinner.vue';
54
+ import PxIcon from './PxIcon.vue';
55
+ import AlertTriangleSvgUrl from '../assets/feather/alert-triangle.svg?url';
56
+
57
+ /**
58
+
59
+ An image component that can be used to easily access advanced functionality for displaying images.
60
+
61
+ Current functionality includes:
62
+
63
+ - Monitory image load state and displaying a loading indicator
64
+ - Cusotmization (via css) of a background for the empty state
65
+ - Fade in on load
66
+ - Crossfade between images when source changes
67
+ - Customizable error state
68
+ - Handles background-like layout scenarios with cover and contain natively
69
+
70
+ Future functionality:
71
+
72
+ - Customization of previous (unloaded) image state when source changes (via class)
73
+ - srcset (with img tag) / image-set (with background)
74
+ - Prioritized / depriotized loading (relative to other images)
75
+ - Only load on active state (via active property) or when in viewport
76
+ - Deal with natural block layout (that allows the container to grow with the image data)
77
+
78
+ IMPLEMENTATION NOTE:
79
+
80
+ We ended up with the two image solution after trying a number of different approaches. The
81
+ primary advantage of this approach is that it keeps image data and loading attached to an img
82
+ elmement in the DOM which seems to simplify a lot of corner conditions like cancelling loads if the
83
+ src changes partway through loading, etc. and issues with flickering when loading large image files.
84
+ Mentioning here so that future me can avoid trying to "optimize" this later and making the same
85
+ mistakes.
86
+
87
+ */
88
+
89
+ const IMAGE_STATES = {
90
+ // inactive so it won't be rendered
91
+ INACTIVE: 'inactive',
92
+
93
+ // src has set but image isn't loaded
94
+ LOADING: 'loading',
95
+
96
+ // image has loaded with valid source
97
+ LOADED: 'loaded',
98
+
99
+ // image is unloading (previous image)
100
+ UNLOADING: 'unloading',
101
+ };
102
+
103
+ export default {
104
+ name: 'px-image',
105
+ components: { PxSpinner, PxIcon },
106
+ // mixins: [ Mixin ],
107
+ props: {
108
+ /**
109
+ * The image source.
110
+ */
111
+ src: { type: String, default: null },
112
+
113
+ /**
114
+ * The fit property specifies how the image should be sized to fit its container.
115
+ */
116
+ fit: { type: String, default: 'contain' },
117
+
118
+ /**
119
+ * Class that gets applied to the component if there is no current src (typically the
120
+ * initial state). Default behaviorOverridden is to set the background to a semi-transparent
121
+ * gray.
122
+ */
123
+ emptyClass: { type: [String, Array, Object], default: 'px-image--empty' },
124
+
125
+ /**
126
+ * Class that gets applied to the component if there is an error state. Default
127
+ * behaviorOverridden is to set the background to a semi-transparent gray like the empty
128
+ * state (and show the default error slot but that is controlled by the slot, not this
129
+ * prop).
130
+ */
131
+ errorClass: { type: [String, Array, Object], default: 'px-image--error' },
132
+
133
+ /**
134
+ * When true, we'll restore an "unloading" image when possible if the new image fails to
135
+ * load. In this case, we'll continue to raise an event but we won't show the error state or
136
+ * set the error class.
137
+ */
138
+ retorePreviousOnError: { type: Boolean, default: false },
139
+
140
+ // /**
141
+ // * Class that gets applied to the component when an image is loading. No default.
142
+ // */
143
+ // loadingClass: { type: [String, Array, Object], default: null },
144
+
145
+ // /**
146
+ // * Class that gets applied to all image elements. The default sets the opacity transition.
147
+ // */
148
+ // imageClass: { type: [String, Array, Object], default: 'px-image__image' },
149
+
150
+ // /**
151
+ // * Class that gets applied to all image elements while loading. Default set opacity to 0.
152
+ // */
153
+ // imageLoadingClass: { type: [String, Array, Object], default: 'px-image__image--loading' },
154
+
155
+ // /**
156
+ // * Class that gets applied to all image elements while unloading. Default sets opacity to 0.5.
157
+ // */
158
+ // imageUnloadingClass: {
159
+ // type: [String, Array, Object],
160
+ // default: 'px-image__image--unloading',
161
+ // },
162
+
163
+ /**
164
+ * A delay (in seconds) before showing loading state to avoid flickering when loading is fast.
165
+ */
166
+ loadingVisibleDelay: { type: Number, default: 0.5 },
167
+
168
+ /**
169
+ * Whether to show a loading spinner when loading in the default loading slot.
170
+ */
171
+ showLoadingSpinner: { type: Boolean, default: true },
172
+
173
+ /**
174
+ * Transition that gets applied when the src changes (we will generate multiple img elements
175
+ * in this case and apply this transition between them, leave as null to use the default
176
+ * which will fade.)
177
+ */
178
+ transitionName: { type: String, default: 'px-image__fade' },
179
+
180
+ /**
181
+ * Duration of the transition in seconds.
182
+ */
183
+ transitionDuration: { type: Number, default: 0.5 },
184
+ },
185
+ data() {
186
+ const image = {
187
+ state: IMAGE_STATES.INACTIVE,
188
+ src: null,
189
+ };
190
+
191
+ return {
192
+ // we manage the data for two images of these (one for the loaded image and one for the
193
+ // next image so we can fade on load) but only render as needed
194
+ images: [{ ...image }, { ...image }],
195
+
196
+ // reference to the last loading error if there is one (otherwise null)
197
+ error: null,
198
+
199
+ // whether to show the loading state (baed on loadingVisibleDelay)
200
+ loadingVisible: false,
201
+
202
+ // so we can access from the template
203
+ IMAGE_STATES,
204
+ AlertTriangleSvgUrl,
205
+ };
206
+ },
207
+ computed: {
208
+ imageA() {
209
+ return this.images[0]?.state !== IMAGE_STATES.INACTIVE ? this.images[0] : null;
210
+ },
211
+ imageB() {
212
+ return this.images[1]?.state !== IMAGE_STATES.INACTIVE ? this.images[1] : null;
213
+ },
214
+ isError() {
215
+ return !!this.error;
216
+ },
217
+ isEmpty() {
218
+ return !this.isError && !this.imageA?.src && !this.imageB?.src;
219
+ },
220
+ isLoading() {
221
+ return !this.isError && this.images.some(i => i.state === IMAGE_STATES.LOADING);
222
+ },
223
+ cssTransitionDuration() {
224
+ return this.transitionDuration + 's';
225
+ },
226
+ },
227
+ watch: {
228
+ src: {
229
+ immediate: true,
230
+ handler(nv) {
231
+ // clear previous errors
232
+ this.error = null;
233
+
234
+ // trim src (just in case but also to prevent false positives on empty string)
235
+ nv = typeof nv === 'string' ? nv.trim() : null;
236
+
237
+ if (!nv) {
238
+ // if we have no src, just mark both images as inactive
239
+ this.images.forEach(i => (i.state = IMAGE_STATES.INACTIVE));
240
+ return;
241
+ }
242
+
243
+ // find an an inactive, unloaded or loading image to update (priotize loading so we don't end
244
+ // up with two loading)
245
+
246
+ const nextImage =
247
+ this.images.find(i => i.state === IMAGE_STATES.LOADING) ||
248
+ this.images.find(i => i.state === IMAGE_STATES.INACTIVE);
249
+
250
+ if (nextImage) {
251
+ nextImage.src = nv;
252
+ nextImage.state = IMAGE_STATES.LOADING;
253
+ } else {
254
+ console.warn('No image available to update');
255
+ return;
256
+ }
257
+
258
+ // if we have a loaded image, mark it as unloading
259
+ const prevImage = this.images.find(i => i.state === IMAGE_STATES.LOADED);
260
+
261
+ if (prevImage) {
262
+ prevImage.state = IMAGE_STATES.UNLOADING;
263
+ }
264
+
265
+ //image.state = IMAGE_STATES.LOADING;
266
+ },
267
+ },
268
+ isLoading(nv) {
269
+ clearTimeout(this.loadingTimeout);
270
+
271
+ if (!nv) {
272
+ this.loadingVisible = false;
273
+ return;
274
+ }
275
+
276
+ this.loadingTimeout = setTimeout(() => {
277
+ this.loadingVisible = this.isLoading;
278
+ }, this.loadingVisibleDelay * 1000);
279
+ },
280
+ },
281
+ mounted() {
282
+ this.nextId = 0;
283
+ this.invalidIds = [];
284
+ },
285
+ methods: {
286
+ resetImages() {
287
+ this.images[0].state = IMAGE_STATES.INACTIVE;
288
+ this.images[0].src = null;
289
+ this.images[1].state = IMAGE_STATES.INACTIVE;
290
+ this.images[1].src = null;
291
+ },
292
+ imageLoad(image) {
293
+ this.$emit('load', image.src);
294
+
295
+ // mark the current image as loaded
296
+ image.state = IMAGE_STATES.LOADED;
297
+
298
+ // mark the previous image as inactive so we can hide remove it from the DOM
299
+ const prevImage = this.images.find(i => i.state === IMAGE_STATES.UNLOADING);
300
+ if (prevImage) {
301
+ prevImage.state = IMAGE_STATES.INACTIVE;
302
+ }
303
+
304
+ // we shouldn't have only have loaded or inactive images at this point
305
+ const loadingImage = this.images.find(
306
+ i => !(i.state == IMAGE_STATES.LOADED || i.state == IMAGE_STATES.INACTIVE),
307
+ );
308
+ if (loadingImage) {
309
+ console.warn('Loading image not properly cleared');
310
+ }
311
+ },
312
+ imageError(e, image) {
313
+ const error = { event: e, image };
314
+ this.$emit('error', error);
315
+
316
+ image.state = IMAGE_STATES.INACTIVE;
317
+
318
+ // try to restore a previous image if we have one and retorePreviousOnError is true
319
+ if (this.retorePreviousOnError) {
320
+ const prevImage = this.images.find(i => i.state === IMAGE_STATES.UNLOADING);
321
+ if (prevImage) {
322
+ prevImage.state = IMAGE_STATES.LOADED;
323
+ prevImage.src = image.src;
324
+ return;
325
+ }
326
+ }
327
+
328
+ // otherwise, set the error and reset images
329
+ this.error = error;
330
+ this.resetImages();
331
+ },
332
+ },
333
+ };
334
+ </script>
335
+
336
+ <style lang="scss">
337
+ @use '../styles/px.scss' as *;
338
+
339
+ $ease: $ease-out-cubic;
340
+ $dur: v-bind(cssTransitionDuration);
341
+ $name: px-image__fade;
342
+
343
+ .px-image {
344
+ width: 100%;
345
+ height: 100%;
346
+ position: relative;
347
+
348
+ // so that placeholder and loading are centered
349
+ display: flex;
350
+ align-items: center;
351
+ justify-content: center;
352
+
353
+ // for the error and empty states
354
+ @include transition(background-color);
355
+
356
+ img {
357
+ @include abs-fill();
358
+ object-position: center center;
359
+ }
360
+
361
+ &--fit-cover {
362
+ img {
363
+ object-fit: cover;
364
+ }
365
+ }
366
+
367
+ &--fit-contain {
368
+ img {
369
+ object-fit: contain;
370
+ }
371
+ }
372
+
373
+ &--error,
374
+ &--empty {
375
+ background-color: rgba(#444, 0.1);
376
+ }
377
+
378
+ &__img {
379
+ transition: opacity $dur $ease;
380
+
381
+ // default transition is to fade
382
+
383
+ &.#{$name}-enter,
384
+ &.#{$name}-enter-from,
385
+ &.#{$name}-leave-to {
386
+ opacity: 0;
387
+ }
388
+
389
+ &--loading {
390
+ visibility: hidden;
391
+ opacity: 0 !important;
392
+ }
393
+
394
+ &--unloading {
395
+ opacity: 0.5;
396
+ }
397
+ }
398
+ }
399
+ </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thinkpixellab-public/px-vue",
3
- "version": "4.1.7",
3
+ "version": "4.1.8",
4
4
  "description": "General purpose Vue components and helpers that can be used across projects.",
5
5
  "author": {
6
6
  "name": "Pixel Lab"
@@ -0,0 +1,42 @@
1
+ import PxBackgroundFader from '@/components/PxBackgroundFader.vue';
2
+ import PxButton from '@/components/PxButton.vue';
3
+ import { extractArgTypes } from '@/storybook/sb-utils.js';
4
+
5
+ export default {
6
+ title: 'PxBackgroundFader',
7
+ component: PxBackgroundFader,
8
+
9
+ argTypes: {
10
+ ...extractArgTypes(PxBackgroundFader),
11
+ },
12
+ };
13
+
14
+ export const Basic = {
15
+ render: (args, { argTypes }) => ({
16
+ components: { PxBackgroundFader, PxButton },
17
+
18
+ data() {
19
+ return {
20
+ args,
21
+ currentBackground: 'story-bg-blue',
22
+ };
23
+ },
24
+
25
+ template: `
26
+ <div class="story-aspect-16-9" style="overflow: hidden;">
27
+ <PxBackgroundFader :background="currentBackground"></PxBackgroundFader>
28
+ <PxButton style="position: absolute; top: 1em; left: 1em; z-index: 1" @click="changeBackground">Fade</PxButton>
29
+ </div>
30
+ `,
31
+
32
+ methods: {
33
+ changeBackground() {
34
+ const backgrounds = 'story-bg-orange|story-bg-blue|story-bg-green|story-bg-gold'
35
+ .split('|')
36
+ .filter(c => c !== this.currentBackground);
37
+ this.currentBackground =
38
+ backgrounds[Math.floor(Math.random() * backgrounds.length)];
39
+ },
40
+ },
41
+ }),
42
+ };
@@ -0,0 +1,113 @@
1
+ import PxImage from '@/components/PxImage.vue';
2
+ import PxButton from '@/components/PxButton.vue';
3
+ import { extractArgTypes } from '@/storybook/sb-utils.js';
4
+ import px01jpg from '@/stories/assets/px-01.jpg';
5
+ import px02jpg from '@/stories/assets/px-02.jpg';
6
+ import px03jpg from '@/stories/assets/px-03.jpg';
7
+ import px04jpg from '@/stories/assets/px-04.jpg';
8
+
9
+ const localImages = [px01jpg, px02jpg, px03jpg, px04jpg];
10
+
11
+ const largeImages = [
12
+ 'https://images.unsplash.com/photo-1738969773091-abcf274f7e0a?q=80&w=3000',
13
+ 'https://images.unsplash.com/photo-1738940251292-49709608c8aa?q=80&w=5970',
14
+ 'https://images.unsplash.com/photo-1551692554-d83872f34d55?q=80&w=3226',
15
+ ];
16
+
17
+ const images = localImages;
18
+
19
+ // REFERENCE:
20
+ //
21
+ // https://storybook.js.org/docs/get-started/frameworks/vue3-vite?renderer=vue
22
+ // https://storybook.js.org/docs/vue/essentials/controls#annotation?renderer=vue
23
+ // https://storybook.js.org/docs/vue/api/argtypes#available-properties?renderer=vue
24
+ //
25
+ // See preview.scss for helper styles.
26
+
27
+ export default {
28
+ title: 'PxImage',
29
+ component: PxImage,
30
+
31
+ // decorators: [ ... ],
32
+
33
+ // parameters: {
34
+ // layout: 'fullscreen', // centered, fullscreen, padded (default)
35
+ // },
36
+
37
+ argTypes: {
38
+ ...extractArgTypes(PxImage),
39
+ },
40
+
41
+ args: {
42
+ fit: 'contain',
43
+ position: null,
44
+ cacheBuster: 'cb',
45
+ },
46
+ };
47
+
48
+ export const Basic = {
49
+ render: (args, { argTypes }) => ({
50
+ components: { PxImage, PxButton },
51
+
52
+ data() {
53
+ return {
54
+ args,
55
+ currentSourceIndex: -1,
56
+ };
57
+ },
58
+
59
+ template: `
60
+
61
+ <div class="story-aspect-16-9" style="overflow: hidden;">
62
+ <PxImage :src="currentSrc" v-bind="args"></PxImage>
63
+ </div>
64
+ <br>
65
+ <p style="max-width: 50em">NOTE: when running with cache disabled, cross fades may appear jumpy because the image will be reloaded while it's fading in. To allow easier visualization of loading (without cache disabled), we a cache buster to the urls when testing (so don't run with cache disabled).</p>
66
+ <div style="display: flex; gap: 1em;">
67
+ <PxButton @click="nextSource">Next Source</PxButton>
68
+ <PxButton @click="clearSource">Clear</PxButton>
69
+ <PxButton @click="invalidSource">Error</PxButton>
70
+ </div>
71
+
72
+ `,
73
+
74
+ computed: {
75
+ currentSrc() {
76
+ if (this.currentSourceIndex == -999) {
77
+ return 'https://thisurldoesnotexist.com/faker-mc-fakerson-face.jpg';
78
+ }
79
+
80
+ if (this.currentSourceIndex == -1) {
81
+ return null;
82
+ }
83
+
84
+ const src = images[this.currentSourceIndex];
85
+ const sep = src.includes('?') ? '&' : '?';
86
+ const cacheBuster = `${sep}${this.cacheBuster}=${Date.now()}`;
87
+
88
+ return src + cacheBuster;
89
+ },
90
+ },
91
+ methods: {
92
+ nextSource() {
93
+ const current = Math.max(this.currentSourceIndex, -1);
94
+ this.currentSourceIndex = (current + 1) % images.length;
95
+ },
96
+ clearSource() {
97
+ this.currentSourceIndex = -1;
98
+ },
99
+ invalidSource() {
100
+ this.currentSourceIndex = -999;
101
+ },
102
+ },
103
+ }),
104
+ };
105
+
106
+ // export const CovertBottomRight = {
107
+ // ...Basic,
108
+ // args: { fit: 'cover', position: 'bottom right' },
109
+ // };
110
+
111
+ // export const Advanced = {
112
+ // ...Basic,
113
+ // };
Binary file
Binary file
Binary file
Binary file
@@ -1,7 +1,5 @@
1
-
2
1
  @use '@thinkpixellab-public/px-styles/all' as *;
3
2
 
4
-
5
3
  // Update this to test font inheritance
6
4
  .sbdocs {
7
5
  font-family: sans-serif;
@@ -88,9 +86,7 @@ body {
88
86
  }
89
87
 
90
88
  .story-aspect-16-9 {
91
- background-color: tomato;
92
89
  width: 100%;
93
- border-radius: 8px;
94
90
  aspect-ratio: 16/9;
95
91
  }
96
92
 
@@ -124,6 +120,31 @@ body {
124
120
  padding: 1em;
125
121
  }
126
122
 
123
+ .story-state-blue {
124
+ background-color: dodgerblue;
125
+ color: white;
126
+ width: 3em;
127
+ height: 3em;
128
+ border-radius: 8px;
129
+ padding: 1em;
130
+ }
131
+
132
+ .story-bg-orange {
133
+ background-color: tomato;
134
+ }
135
+
136
+ .story-bg-blue {
137
+ background-color: dodgerblue;
138
+ }
139
+
140
+ .story-bg-green {
141
+ background-color: yellowgreen;
142
+ }
143
+
144
+ .story-bg-gold {
145
+ background-color: gold;
146
+ }
147
+
127
148
  .story-size-xs {
128
149
  width: 2em;
129
150
  height: 2em;