@streamscloud/kit 0.67.0 → 0.68.1
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/dist/ui/drag-and-drop/cmp.drop-zone.svelte +3 -0
- package/dist/ui/file-uploader/file-type-colors.d.ts +5 -1
- package/dist/ui/file-uploader/file-type-colors.js +30 -1
- package/dist/ui/grid-card/cmp.grid-card-media.svelte +4 -3
- package/dist/ui/grid-card/cmp.grid-card-media.svelte.d.ts +2 -0
- package/dist/ui/grid-card/cmp.grid-card.svelte +1 -2
- package/dist/ui/grid-card/video-player.svelte +4 -1
- package/dist/ui/player/carousel/cmp.carousel.svelte +17 -5
- package/dist/ui/tile-card/cmp.tile-card.svelte +193 -0
- package/dist/ui/tile-card/cmp.tile-card.svelte.d.ts +43 -0
- package/dist/ui/tile-card/index.d.ts +2 -0
- package/dist/ui/tile-card/index.js +1 -0
- package/dist/ui/tile-card/tile-card-localization.d.ts +4 -0
- package/dist/ui/tile-card/tile-card-localization.js +19 -0
- package/dist/ui/tile-card/types.d.ts +8 -0
- package/dist/ui/tile-card/types.js +1 -0
- package/package.json +5 -1
|
@@ -91,6 +91,9 @@ isn't in `acceptedEvents` — useful for visible zones where silent inertia coul
|
|
|
91
91
|
height: 100%;
|
|
92
92
|
position: relative;
|
|
93
93
|
}
|
|
94
|
+
.drop-zone--active, .drop-zone--drag-reject {
|
|
95
|
+
isolation: isolate;
|
|
96
|
+
}
|
|
94
97
|
.drop-zone--active::before {
|
|
95
98
|
content: "";
|
|
96
99
|
position: absolute;
|
|
@@ -5,4 +5,8 @@ export declare const fileTypeColors: Record<string, string>;
|
|
|
5
5
|
* entry of `fileTypeColors` counts regardless of length (`.sketch`).
|
|
6
6
|
*/
|
|
7
7
|
export declare const fileTypeFromName: (name: string) => string;
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Stamp color of a file type (an extension, e.g. from `fileTypeFromName`). `fallback` is returned for a type missing from
|
|
10
|
+
* `fileTypeColors` — pass your own color, or `''` to detect an unknown type.
|
|
11
|
+
*/
|
|
12
|
+
export declare const fileTypeColor: (type: string, fallback?: string) => string;
|
|
@@ -6,24 +6,49 @@ export const fileTypeColors = {
|
|
|
6
6
|
pdf: '#e15554',
|
|
7
7
|
doc: '#144ab0',
|
|
8
8
|
docx: '#144ab0',
|
|
9
|
+
odt: '#144ab0',
|
|
10
|
+
rtf: '#144ab0',
|
|
9
11
|
xls: '#10b981',
|
|
10
12
|
xlsx: '#10b981',
|
|
13
|
+
ods: '#10b981',
|
|
11
14
|
csv: '#10b981',
|
|
12
15
|
ppt: '#e0772e',
|
|
13
16
|
pptx: '#e0772e',
|
|
17
|
+
odp: '#e0772e',
|
|
14
18
|
zip: '#f5a623',
|
|
15
19
|
rar: '#f5a623',
|
|
20
|
+
'7z': '#f5a623',
|
|
21
|
+
tar: '#f5a623',
|
|
22
|
+
gz: '#f5a623',
|
|
23
|
+
tgz: '#f5a623',
|
|
24
|
+
bz2: '#f5a623',
|
|
16
25
|
fig: '#1abc9c',
|
|
17
26
|
sketch: '#fdb022',
|
|
18
27
|
png: '#8b5cf6',
|
|
19
28
|
jpg: '#8b5cf6',
|
|
20
29
|
jpeg: '#8b5cf6',
|
|
21
30
|
gif: '#8b5cf6',
|
|
31
|
+
webp: '#8b5cf6',
|
|
32
|
+
avif: '#8b5cf6',
|
|
33
|
+
heic: '#8b5cf6',
|
|
34
|
+
heif: '#8b5cf6',
|
|
35
|
+
bmp: '#8b5cf6',
|
|
36
|
+
tif: '#8b5cf6',
|
|
37
|
+
tiff: '#8b5cf6',
|
|
22
38
|
svg: '#7c3aed',
|
|
23
39
|
mp4: '#ec4899',
|
|
24
40
|
mov: '#ec4899',
|
|
41
|
+
webm: '#ec4899',
|
|
42
|
+
mkv: '#ec4899',
|
|
43
|
+
avi: '#ec4899',
|
|
44
|
+
m4v: '#ec4899',
|
|
25
45
|
mp3: '#f97066',
|
|
26
46
|
wav: '#f97066',
|
|
47
|
+
ogg: '#f97066',
|
|
48
|
+
m4a: '#f97066',
|
|
49
|
+
flac: '#f97066',
|
|
50
|
+
aac: '#f97066',
|
|
51
|
+
opus: '#f97066',
|
|
27
52
|
txt: '#6b7280',
|
|
28
53
|
md: '#6b7280',
|
|
29
54
|
json: '#7c3aed',
|
|
@@ -45,4 +70,8 @@ export const fileTypeFromName = (name) => {
|
|
|
45
70
|
const tail = name.slice(dot + 1).toLowerCase();
|
|
46
71
|
return fileTypeColors[tail] !== undefined || EXTENSION_SHAPE.test(tail) ? tail : '';
|
|
47
72
|
};
|
|
48
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Stamp color of a file type (an extension, e.g. from `fileTypeFromName`). `fallback` is returned for a type missing from
|
|
75
|
+
* `fileTypeColors` — pass your own color, or `''` to detect an unknown type.
|
|
76
|
+
*/
|
|
77
|
+
export const fileTypeColor = (type, fallback = '#6b7280') => fileTypeColors[type.toLowerCase()] ?? fallback;
|
|
@@ -8,7 +8,7 @@ import { ProportionalContainer } from '../proportional-container';
|
|
|
8
8
|
import { SeekBar } from '../seek-bar';
|
|
9
9
|
import VideoPlayer from './video-player.svelte';
|
|
10
10
|
import IconImageOff from '@fluentui/svg-icons/icons/image_off_20_regular.svg?raw';
|
|
11
|
-
const { items, aspectRatio = 'vertical', objectFit = 'contain', showSeekBar = true, duration } = $props();
|
|
11
|
+
const { items, aspectRatio = 'vertical', objectFit = 'contain', showSeekBar = true, duration, decoding = 'auto' } = $props();
|
|
12
12
|
let currentIndex = $state(0);
|
|
13
13
|
let currentTime = $state(0);
|
|
14
14
|
let mediaDuration = $state(NaN);
|
|
@@ -63,7 +63,7 @@ const onCarouselIndexChanged = (index) => {
|
|
|
63
63
|
active={true}
|
|
64
64
|
on={{ timeUpdate: onPlayerTimeUpdate, durationChange: onPlayerDurationChange, activate: onPlayerActivate }} />
|
|
65
65
|
{:else}
|
|
66
|
-
<Image src={items[0].url} showStubOnError={true} decoding=
|
|
66
|
+
<Image src={items[0].url} showStubOnError={true} decoding={decoding} />
|
|
67
67
|
{/if}
|
|
68
68
|
</div>
|
|
69
69
|
{:else}
|
|
@@ -78,7 +78,7 @@ const onCarouselIndexChanged = (index) => {
|
|
|
78
78
|
active={item === items[currentIndex]}
|
|
79
79
|
on={{ timeUpdate: onPlayerTimeUpdate, durationChange: onPlayerDurationChange, activate: onPlayerActivate }} />
|
|
80
80
|
{:else}
|
|
81
|
-
<Image src={item.url} showStubOnError={true} decoding=
|
|
81
|
+
<Image src={item.url} showStubOnError={true} decoding={decoding} />
|
|
82
82
|
{/if}
|
|
83
83
|
</div>
|
|
84
84
|
{/snippet}
|
|
@@ -123,6 +123,7 @@ The play glyph of a playable item is a `PlayIndicator` — size and shadow are t
|
|
|
123
123
|
|
|
124
124
|
<style>.grid-card-media {
|
|
125
125
|
--_grid-card-media--padding: var(--sc-kit--grid-card--media--padding, 0);
|
|
126
|
+
isolation: isolate;
|
|
126
127
|
width: 100%;
|
|
127
128
|
margin-bottom: 0.5rem;
|
|
128
129
|
}
|
|
@@ -15,6 +15,8 @@ type Props = {
|
|
|
15
15
|
showSeekBar?: boolean;
|
|
16
16
|
/** Duration in seconds; rendered as a badge overlay at the bottom-right of the media. `≤ 0` / `undefined` renders nothing. */
|
|
17
17
|
duration?: number;
|
|
18
|
+
/** Forwarded to each `Image`. Pass `'sync'` when the card sits in a `DragSource`, or the first drag ghost comes out without the picture. @default 'auto' */
|
|
19
|
+
decoding?: 'sync' | 'async' | 'auto';
|
|
18
20
|
};
|
|
19
21
|
/**
|
|
20
22
|
* Media slot for `GridCard`. Renders an empty placeholder, a single `Image` / `VideoPlayer`, or
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const { selected, children, on } = $props();
|
|
3
3
|
const interactive = $derived(!!on?.activate);
|
|
4
4
|
const onCardKeydown = (e) => {
|
|
5
|
-
if (!on?.activate) {
|
|
5
|
+
if (!on?.activate || e.target !== e.currentTarget) {
|
|
6
6
|
return;
|
|
7
7
|
}
|
|
8
8
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
@@ -87,5 +87,4 @@ Inner interactive elements stop event propagation so they don't double-fire acti
|
|
|
87
87
|
position: absolute;
|
|
88
88
|
top: 0.75rem;
|
|
89
89
|
left: 0.75rem;
|
|
90
|
-
z-index: 10;
|
|
91
90
|
}</style>
|
|
@@ -124,7 +124,10 @@ export const seekTo = (time) => {
|
|
|
124
124
|
onplay={() => setPlaying(true)}
|
|
125
125
|
onpause={() => setPlaying(false)}
|
|
126
126
|
onended={() => setPlaying(false)}
|
|
127
|
-
onclick={
|
|
127
|
+
onclick={(e) => {
|
|
128
|
+
e.stopPropagation();
|
|
129
|
+
togglePlay();
|
|
130
|
+
}}>
|
|
128
131
|
<track kind="captions" />
|
|
129
132
|
</video>
|
|
130
133
|
|
|
@@ -199,6 +199,18 @@ const loadPrevious = Utils.throttle(() => {
|
|
|
199
199
|
const loadNext = Utils.throttle(() => {
|
|
200
200
|
setIndex(selectedIndex + 1);
|
|
201
201
|
}, animationDuration);
|
|
202
|
+
const onPreviousClick = (e) => {
|
|
203
|
+
e.stopPropagation();
|
|
204
|
+
loadPrevious();
|
|
205
|
+
};
|
|
206
|
+
const onNextClick = (e) => {
|
|
207
|
+
e.stopPropagation();
|
|
208
|
+
loadNext();
|
|
209
|
+
};
|
|
210
|
+
const onDotClick = (e, index) => {
|
|
211
|
+
e.stopPropagation();
|
|
212
|
+
setIndex(index);
|
|
213
|
+
};
|
|
202
214
|
$effect(() => {
|
|
203
215
|
if (previousItems && items !== previousItems) {
|
|
204
216
|
setIndex(0);
|
|
@@ -243,24 +255,24 @@ const slidesTranslateX = $derived(swipeTransition || (items.length > 1 ? -slider
|
|
|
243
255
|
{#if items.length > 1}
|
|
244
256
|
{#if mode === 'arrows-with-counts'}
|
|
245
257
|
<div class="carousel__arrows-with-counts">
|
|
246
|
-
<button type="button" class="carousel__counts-navigation-button" onclick={
|
|
258
|
+
<button type="button" class="carousel__counts-navigation-button" onclick={onPreviousClick} aria-label="none">
|
|
247
259
|
<Icon src={IconChevronLeft} />
|
|
248
260
|
</button>
|
|
249
261
|
<div class="carousel__counts">{localization.nOfM(selectedIndex + 1, items.length)}</div>
|
|
250
|
-
<button type="button" class="carousel__counts-navigation-button" onclick={
|
|
262
|
+
<button type="button" class="carousel__counts-navigation-button" onclick={onNextClick} aria-label="none">
|
|
251
263
|
<Icon src={IconChevronRight} />
|
|
252
264
|
</button>
|
|
253
265
|
</div>
|
|
254
266
|
{:else}
|
|
255
|
-
<button type="button" class="carousel__navigation-button carousel__navigation-button--prev" onclick={
|
|
267
|
+
<button type="button" class="carousel__navigation-button carousel__navigation-button--prev" onclick={onPreviousClick} aria-label="none">
|
|
256
268
|
<Icon src={IconChevronLeft} />
|
|
257
269
|
</button>
|
|
258
|
-
<button type="button" class="carousel__navigation-button carousel__navigation-button--next" onclick={
|
|
270
|
+
<button type="button" class="carousel__navigation-button carousel__navigation-button--next" onclick={onNextClick} aria-label="none">
|
|
259
271
|
<Icon src={IconChevronRight} />
|
|
260
272
|
</button>
|
|
261
273
|
<div class="carousel__dots">
|
|
262
274
|
{#each itemIndices as index (index)}
|
|
263
|
-
<button type="button" class="carousel__dot" onclick={() =>
|
|
275
|
+
<button type="button" class="carousel__dot" onclick={(e) => onDotClick(e, index)}>
|
|
264
276
|
{#if dot}
|
|
265
277
|
{@render dot({ active: index === selectedIndex })}
|
|
266
278
|
{:else}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
<script lang="ts">import { Checkbox } from '../checkbox';
|
|
2
|
+
import { ToolbarIconButton } from '../page-toolbar';
|
|
3
|
+
import { PlayIndicator } from '../play-indicator';
|
|
4
|
+
import { Popover } from '../popover';
|
|
5
|
+
import { TileCardLocalization } from './tile-card-localization';
|
|
6
|
+
import IconMore from '@fluentui/svg-icons/icons/more_vertical_20_regular.svg?raw';
|
|
7
|
+
const { title, meta, preview, decoding = 'auto', menu, menuLabel, selected = false, checkboxVisibility = 'hover', on } = $props();
|
|
8
|
+
const localization = new TileCardLocalization();
|
|
9
|
+
// Safari paints no frame for preload="metadata" until a seek target is given.
|
|
10
|
+
const withFirstFrame = (src) => (src.includes('#') ? src : `${src}#t=0.001`);
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<div class="tile-card" class:tile-card--selected={selected} class:tile-card--checkbox-always={checkboxVisibility === 'always'}>
|
|
14
|
+
{#if on?.activate}
|
|
15
|
+
<button type="button" class="tile-card__hit" aria-label={title} title={title} onclick={() => on?.activate?.()}></button>
|
|
16
|
+
{/if}
|
|
17
|
+
{#if preview}
|
|
18
|
+
<div class="tile-card__preview">
|
|
19
|
+
{#if typeof preview === 'function'}
|
|
20
|
+
{@render preview()}
|
|
21
|
+
{:else if 'image' in preview}
|
|
22
|
+
<img class="tile-card__picture" src={preview.image} alt="" decoding={decoding} />
|
|
23
|
+
{:else}
|
|
24
|
+
<video class="tile-card__picture" src={withFirstFrame(preview.video)} poster={preview.poster} preload="metadata" muted playsinline></video>
|
|
25
|
+
<span class="tile-card__play"><PlayIndicator /></span>
|
|
26
|
+
{/if}
|
|
27
|
+
</div>
|
|
28
|
+
{/if}
|
|
29
|
+
<div class="tile-card__footer">
|
|
30
|
+
<div class="tile-card__text">
|
|
31
|
+
<span class="tile-card__title" title={title}>{title}</span>
|
|
32
|
+
{#if meta}
|
|
33
|
+
<span class="tile-card__meta">{meta}</span>
|
|
34
|
+
{/if}
|
|
35
|
+
</div>
|
|
36
|
+
{#if menu}
|
|
37
|
+
<div class="tile-card__menu">
|
|
38
|
+
<Popover position="bottom-end" fixedPosition aria-label={menuLabel ?? localization.actions}>
|
|
39
|
+
{#snippet trigger()}
|
|
40
|
+
<ToolbarIconButton icon={IconMore} presentational />
|
|
41
|
+
{/snippet}
|
|
42
|
+
{@render menu()}
|
|
43
|
+
</Popover>
|
|
44
|
+
</div>
|
|
45
|
+
{/if}
|
|
46
|
+
</div>
|
|
47
|
+
{#if on?.select}
|
|
48
|
+
<div class="tile-card__checkbox">
|
|
49
|
+
<Checkbox checked={selected} variant="on-accent" title={localization.select} on={{ change: (value) => on?.select?.(value) }} />
|
|
50
|
+
</div>
|
|
51
|
+
{/if}
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<!--
|
|
55
|
+
@component
|
|
56
|
+
File-browser style tile: a full-bleed preview on top and a footer with title, optional meta line and a kebab menu. Selection
|
|
57
|
+
checkbox via `on.select`, whole-card activation via `on.activate`. For a drop-target highlight wrap it in `DropZone`.
|
|
58
|
+
|
|
59
|
+
### CSS Custom Properties
|
|
60
|
+
| Property | Description | Default |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `--sc-kit--tile-card--background` | Card background | `var(--sc-kit--color--bg--panel)` |
|
|
63
|
+
| `--sc-kit--tile-card--border-color` | Border color | `var(--sc-kit--color--border)` |
|
|
64
|
+
| `--sc-kit--tile-card--border-color--hover` | Border color on hover | `var(--sc-kit--color--border--strong)` |
|
|
65
|
+
| `--sc-kit--tile-card--border-color--selected` | Border color when selected | `var(--sc-kit--color--accent)` |
|
|
66
|
+
| `--sc-kit--tile-card--border-radius` | Border radius | `var(--sc-kit--radius--md)` |
|
|
67
|
+
| `--sc-kit--tile-card--preview--background` | Preview area background | `var(--sc-kit--color--bg--field-alt)` |
|
|
68
|
+
| `--sc-kit--tile-card--preview--aspect-ratio` | Preview aspect ratio | `7 / 5` |
|
|
69
|
+
| `--sc-kit--tile-card--footer--min-height` | Footer minimum height | `52px` |
|
|
70
|
+
-->
|
|
71
|
+
|
|
72
|
+
<style>.tile-card {
|
|
73
|
+
--_tile-card--background: var(--sc-kit--tile-card--background, var(--sc-kit--color--bg--panel));
|
|
74
|
+
--_tile-card--border-color: var(--sc-kit--tile-card--border-color, var(--sc-kit--color--border));
|
|
75
|
+
--_tile-card--border-color-hover: var(--sc-kit--tile-card--border-color--hover, var(--sc-kit--color--border--strong));
|
|
76
|
+
--_tile-card--border-color-selected: var(--sc-kit--tile-card--border-color--selected, var(--sc-kit--color--accent));
|
|
77
|
+
--_tile-card--border-radius: var(--sc-kit--tile-card--border-radius, var(--sc-kit--radius--md));
|
|
78
|
+
--_tile-card--preview-background: var(--sc-kit--tile-card--preview--background, var(--sc-kit--color--bg--field-alt));
|
|
79
|
+
--_tile-card--preview-aspect-ratio: var(--sc-kit--tile-card--preview--aspect-ratio, 7 / 5);
|
|
80
|
+
--_tile-card--footer-min-height: var(--sc-kit--tile-card--footer--min-height, 3.25rem);
|
|
81
|
+
--_tile-card--checkbox-opacity: 0;
|
|
82
|
+
position: relative;
|
|
83
|
+
display: flex;
|
|
84
|
+
flex-direction: column;
|
|
85
|
+
inline-size: 100%;
|
|
86
|
+
overflow: hidden;
|
|
87
|
+
color: var(--sc-kit--color--text--primary);
|
|
88
|
+
background: var(--_tile-card--background);
|
|
89
|
+
border: 1px solid var(--_tile-card--border-color);
|
|
90
|
+
border-radius: var(--_tile-card--border-radius);
|
|
91
|
+
transition: border-color var(--sc-kit--duration--base) var(--sc-kit--ease--default);
|
|
92
|
+
}
|
|
93
|
+
.tile-card:hover {
|
|
94
|
+
--_tile-card--border-color: var(--_tile-card--border-color-hover);
|
|
95
|
+
}
|
|
96
|
+
.tile-card:hover, .tile-card:focus-within {
|
|
97
|
+
--_tile-card--checkbox-opacity: 1;
|
|
98
|
+
}
|
|
99
|
+
.tile-card:has(> .tile-card__hit:focus-visible) {
|
|
100
|
+
outline: 2px solid var(--sc-kit--color--border--focus);
|
|
101
|
+
outline-offset: 2px;
|
|
102
|
+
}
|
|
103
|
+
.tile-card--selected {
|
|
104
|
+
--_tile-card--checkbox-opacity: 1;
|
|
105
|
+
}
|
|
106
|
+
.tile-card--selected, .tile-card--selected:hover {
|
|
107
|
+
--_tile-card--border-color: var(--_tile-card--border-color-selected);
|
|
108
|
+
}
|
|
109
|
+
.tile-card--checkbox-always {
|
|
110
|
+
--_tile-card--checkbox-opacity: 1;
|
|
111
|
+
}
|
|
112
|
+
@media (hover: none) {
|
|
113
|
+
.tile-card {
|
|
114
|
+
--_tile-card--checkbox-opacity: 1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
.tile-card__hit {
|
|
118
|
+
position: absolute;
|
|
119
|
+
inset: 0;
|
|
120
|
+
padding: 0;
|
|
121
|
+
background: none;
|
|
122
|
+
border: 0;
|
|
123
|
+
outline: none;
|
|
124
|
+
cursor: pointer;
|
|
125
|
+
}
|
|
126
|
+
.tile-card__preview {
|
|
127
|
+
position: relative;
|
|
128
|
+
isolation: isolate;
|
|
129
|
+
container-type: inline-size;
|
|
130
|
+
display: flex;
|
|
131
|
+
align-items: center;
|
|
132
|
+
justify-content: center;
|
|
133
|
+
aspect-ratio: var(--_tile-card--preview-aspect-ratio);
|
|
134
|
+
overflow: hidden;
|
|
135
|
+
background: var(--_tile-card--preview-background);
|
|
136
|
+
pointer-events: none;
|
|
137
|
+
}
|
|
138
|
+
.tile-card__picture {
|
|
139
|
+
display: block;
|
|
140
|
+
max-inline-size: 100%;
|
|
141
|
+
max-block-size: 100%;
|
|
142
|
+
object-fit: contain;
|
|
143
|
+
}
|
|
144
|
+
.tile-card__play {
|
|
145
|
+
position: absolute;
|
|
146
|
+
inset: 0;
|
|
147
|
+
display: flex;
|
|
148
|
+
align-items: center;
|
|
149
|
+
justify-content: center;
|
|
150
|
+
}
|
|
151
|
+
.tile-card__footer {
|
|
152
|
+
display: grid;
|
|
153
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
154
|
+
align-items: center;
|
|
155
|
+
column-gap: var(--sc-kit--space--2);
|
|
156
|
+
min-block-size: var(--_tile-card--footer-min-height);
|
|
157
|
+
padding: var(--sc-kit--space--2) var(--sc-kit--space--3) var(--sc-kit--space--2) var(--sc-kit--space--4);
|
|
158
|
+
}
|
|
159
|
+
.tile-card__text {
|
|
160
|
+
display: flex;
|
|
161
|
+
flex-direction: column;
|
|
162
|
+
gap: 0.125rem;
|
|
163
|
+
min-inline-size: 0;
|
|
164
|
+
}
|
|
165
|
+
.tile-card__title {
|
|
166
|
+
font-size: var(--sc-kit--font-size--md);
|
|
167
|
+
font-weight: var(--sc-kit--font-weight--semibold);
|
|
168
|
+
line-height: var(--sc-kit--leading--tight);
|
|
169
|
+
text-overflow: ellipsis;
|
|
170
|
+
max-width: 100%;
|
|
171
|
+
white-space: nowrap;
|
|
172
|
+
overflow: hidden;
|
|
173
|
+
}
|
|
174
|
+
.tile-card__meta {
|
|
175
|
+
font-size: var(--sc-kit--font-size--xs);
|
|
176
|
+
line-height: var(--sc-kit--leading--tight);
|
|
177
|
+
color: var(--sc-kit--color--text--muted);
|
|
178
|
+
text-overflow: ellipsis;
|
|
179
|
+
max-width: 100%;
|
|
180
|
+
white-space: nowrap;
|
|
181
|
+
overflow: hidden;
|
|
182
|
+
}
|
|
183
|
+
.tile-card__menu {
|
|
184
|
+
position: relative;
|
|
185
|
+
display: flex;
|
|
186
|
+
}
|
|
187
|
+
.tile-card__checkbox {
|
|
188
|
+
position: absolute;
|
|
189
|
+
inset-block-start: var(--sc-kit--space--3);
|
|
190
|
+
inset-inline-start: var(--sc-kit--space--3);
|
|
191
|
+
opacity: var(--_tile-card--checkbox-opacity);
|
|
192
|
+
transition: opacity var(--sc-kit--duration--base) var(--sc-kit--ease--default);
|
|
193
|
+
}</style>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { TileCardCheckboxVisibility, TileCardPreview } from './types';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
type Props = {
|
|
4
|
+
title: string;
|
|
5
|
+
/** Secondary line under the title. */
|
|
6
|
+
meta?: string;
|
|
7
|
+
/** Top area: `image` / `video` render a drag-safe picture or frame (contain, never upscaled; a video gets a play glyph); a snippet renders custom content. The area is `pointer-events: none` — interactive content belongs in `menu` or `on.activate`. */
|
|
8
|
+
preview?: TileCardPreview;
|
|
9
|
+
/** Decoding of the `image` preview. Pass `'sync'` when the tile sits in a `DragSource`, or the first drag ghost comes out without the picture — the same goes for an `<img>` in a preview snippet. @default 'auto' */
|
|
10
|
+
decoding?: 'sync' | 'async' | 'auto';
|
|
11
|
+
/** `PopoverItem`s of the kebab menu at the end of the footer. */
|
|
12
|
+
menu?: Snippet;
|
|
13
|
+
/** Accessible name of the kebab menu. @default localized "Actions" */
|
|
14
|
+
menuLabel?: string;
|
|
15
|
+
selected?: boolean;
|
|
16
|
+
/** When the checkbox shows: `'hover'` — on hover, focus-within, when selected and on touch screens; `'always'` — permanently. @default 'hover' */
|
|
17
|
+
checkboxVisibility?: TileCardCheckboxVisibility;
|
|
18
|
+
on?: {
|
|
19
|
+
/** Renders the checkbox at the top-left of the preview. */
|
|
20
|
+
select?: (value: boolean) => void;
|
|
21
|
+
/** Fires on a click on the card surface or Enter / Space; the checkbox and the menu don't trigger it. */
|
|
22
|
+
activate?: () => void;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* File-browser style tile: a full-bleed preview on top and a footer with title, optional meta line and a kebab menu. Selection
|
|
27
|
+
* checkbox via `on.select`, whole-card activation via `on.activate`. For a drop-target highlight wrap it in `DropZone`.
|
|
28
|
+
*
|
|
29
|
+
* ### CSS Custom Properties
|
|
30
|
+
* | Property | Description | Default |
|
|
31
|
+
* |---|---|---|
|
|
32
|
+
* | `--sc-kit--tile-card--background` | Card background | `var(--sc-kit--color--bg--panel)` |
|
|
33
|
+
* | `--sc-kit--tile-card--border-color` | Border color | `var(--sc-kit--color--border)` |
|
|
34
|
+
* | `--sc-kit--tile-card--border-color--hover` | Border color on hover | `var(--sc-kit--color--border--strong)` |
|
|
35
|
+
* | `--sc-kit--tile-card--border-color--selected` | Border color when selected | `var(--sc-kit--color--accent)` |
|
|
36
|
+
* | `--sc-kit--tile-card--border-radius` | Border radius | `var(--sc-kit--radius--md)` |
|
|
37
|
+
* | `--sc-kit--tile-card--preview--background` | Preview area background | `var(--sc-kit--color--bg--field-alt)` |
|
|
38
|
+
* | `--sc-kit--tile-card--preview--aspect-ratio` | Preview aspect ratio | `7 / 5` |
|
|
39
|
+
* | `--sc-kit--tile-card--footer--min-height` | Footer minimum height | `52px` |
|
|
40
|
+
*/
|
|
41
|
+
declare const Cmp: import("svelte").Component<Props, {}, "">;
|
|
42
|
+
type Cmp = ReturnType<typeof Cmp>;
|
|
43
|
+
export default Cmp;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as TileCard } from './cmp.tile-card.svelte';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { AppLocale } from '../../core/locale';
|
|
2
|
+
const loc = {
|
|
3
|
+
select: {
|
|
4
|
+
en: 'Select',
|
|
5
|
+
no: 'Velg'
|
|
6
|
+
},
|
|
7
|
+
actions: {
|
|
8
|
+
en: 'Actions',
|
|
9
|
+
no: 'Handlinger'
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
export class TileCardLocalization {
|
|
13
|
+
get select() {
|
|
14
|
+
return loc.select[AppLocale.current];
|
|
15
|
+
}
|
|
16
|
+
get actions() {
|
|
17
|
+
return loc.actions[AppLocale.current];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streamscloud/kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.68.1",
|
|
4
4
|
"author": "StreamsCloud",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -411,6 +411,10 @@
|
|
|
411
411
|
"types": "./dist/ui/textarea/index.d.ts",
|
|
412
412
|
"svelte": "./dist/ui/textarea/index.js"
|
|
413
413
|
},
|
|
414
|
+
"./ui/tile-card": {
|
|
415
|
+
"types": "./dist/ui/tile-card/index.d.ts",
|
|
416
|
+
"svelte": "./dist/ui/tile-card/index.js"
|
|
417
|
+
},
|
|
414
418
|
"./ui/time-ago": {
|
|
415
419
|
"types": "./dist/ui/time-ago/index.d.ts",
|
|
416
420
|
"svelte": "./dist/ui/time-ago/index.js"
|