mikuru 1.0.34 → 1.0.35
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/CHANGELOG.md +8 -0
- package/README.md +25 -1
- package/components/MikuruAccordion.mikuru +156 -0
- package/components/MikuruCarousel.mikuru +144 -6
- package/components/MikuruCheckbox.mikuru +84 -0
- package/components/MikuruCodeBlock.mikuru +238 -4
- package/components/MikuruCombobox.mikuru +226 -0
- package/components/MikuruFooter.mikuru +121 -0
- package/components/MikuruHeader.mikuru +165 -0
- package/components/MikuruSelect.mikuru +106 -0
- package/components/MikuruSideMenu.mikuru +188 -0
- package/components/MikuruTabs.mikuru +163 -0
- package/components/MikuruTextInput.mikuru +83 -0
- package/components/MikuruTextarea.mikuru +86 -0
- package/dist/cli/create.js +5 -1
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/templates.d.ts +1 -1
- package/dist/cli/templates.js +3 -2
- package/dist/cli/templates.js.map +1 -1
- package/dist/cli.js +1 -1
- package/package.json +81 -1
- package/templates/video-player/_gitignore +3 -0
- package/templates/video-player/index.html +13 -0
- package/templates/video-player/package.json +19 -0
- package/templates/video-player/public/favicon.svg +4 -0
- package/templates/video-player/src/App.mikuru +92 -0
- package/templates/video-player/src/css-env.d.ts +1 -0
- package/templates/video-player/src/main.ts +10 -0
- package/templates/video-player/src/mikuru-env.d.ts +1 -0
- package/templates/video-player/src/style.css +132 -0
- package/templates/video-player/tsconfig.json +11 -0
- package/templates/video-player/vite.config.ts +6 -0
- package/types/components/MikuruAccordion.d.ts +18 -0
- package/types/components/MikuruCarousel.d.ts +2 -0
- package/types/components/MikuruCheckbox.d.ts +13 -0
- package/types/components/MikuruCombobox.d.ts +21 -0
- package/types/components/MikuruFooter.d.ts +19 -0
- package/types/components/MikuruHeader.d.ts +22 -0
- package/types/components/MikuruSelect.d.ts +21 -0
- package/types/components/MikuruSideMenu.d.ts +22 -0
- package/types/components/MikuruTabs.d.ts +18 -0
- package/types/components/MikuruTextInput.d.ts +16 -0
- package/types/components/MikuruTextarea.d.ts +16 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.35 - 2026-05-17
|
|
4
|
+
|
|
5
|
+
- Added package-exported tabs, accordion, form controls, select, combobox, header, footer, and side menu components with typed exports and dogfood coverage.
|
|
6
|
+
- Added a `video-player` create template that imports `MikuruVideoPlayer` from the package and demonstrates quality options, controls, and media events.
|
|
7
|
+
- Improved `MikuruCarousel` with CSS-mask arrow icons, optional thumbnail navigation, hidden thumbnail scrollbars, centered active thumbnails, and a 20-image dogfood gallery case.
|
|
8
|
+
- Updated `MikuruSideMenu` collapse controls to use icon buttons.
|
|
9
|
+
- Added lightweight VS Code-style syntax highlighting to `MikuruCodeBlock` for Mikuru/markup, JavaScript/TypeScript, JSON, and CSS snippets.
|
|
10
|
+
|
|
3
11
|
## 1.0.34 - 2026-05-16
|
|
4
12
|
|
|
5
13
|
- Stabilized package component internals so repeated mounts and parent rerenders no longer recreate equivalent derived arrays, Sets, or style objects unnecessarily.
|
package/README.md
CHANGED
|
@@ -29,12 +29,19 @@ Use the `basic` template when you want a small component composition example:
|
|
|
29
29
|
npx mikuru create my-basic-app -t basic
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
Use the `video-player` template when you want a Vite app that imports the package-provided `MikuruVideoPlayer` component:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npx mikuru create my-video-app -t video-player
|
|
36
|
+
```
|
|
37
|
+
|
|
32
38
|
List available templates:
|
|
33
39
|
|
|
34
40
|
```sh
|
|
35
41
|
npx mikuru --list-templates
|
|
36
42
|
starter - minimal Vite app
|
|
37
43
|
basic - component composition example
|
|
44
|
+
video-player - MikuruVideoPlayer media app
|
|
38
45
|
```
|
|
39
46
|
|
|
40
47
|
Run a dry-run to preview the target, template, and files without writing them:
|
|
@@ -197,6 +204,7 @@ The package also provides the `mikuru` binary:
|
|
|
197
204
|
```sh
|
|
198
205
|
npx mikuru create my-app
|
|
199
206
|
npx mikuru create my-basic-app -t basic
|
|
207
|
+
npx mikuru create my-video-app -t video-player
|
|
200
208
|
npx mikuru --list-templates
|
|
201
209
|
```
|
|
202
210
|
|
|
@@ -274,12 +282,18 @@ The package also includes original Mikuru components:
|
|
|
274
282
|
- `MikuruAudioPlayer.mikuru`: audio playback with configurable control visibility, live mode, seeking, skip controls, volume, and mute.
|
|
275
283
|
- `MikuruImageViewer.mikuru`: image zoom, pan, rotate, reset, and fullscreen controls.
|
|
276
284
|
- `MikuruModal.mikuru`: accessible modal shell with backdrop, Escape close, slots, and close events.
|
|
277
|
-
- `MikuruCarousel.mikuru`: image carousel with arrows, dots, keyboard navigation, and optional
|
|
285
|
+
- `MikuruCarousel.mikuru`: image carousel with arrows, dots, keyboard navigation, optional autoplay, and optional thumbnail navigation.
|
|
278
286
|
- `MikuruToast.mikuru`: fixed notification stack with timed auto-dismiss, dismiss events, and tone variants.
|
|
279
287
|
- `MikuruDropdown.mikuru`: menu button with outside-click close, Escape handling, and select events.
|
|
280
288
|
- `MikuruToolTip.mikuru`: hover/focus tooltip with configurable placement.
|
|
281
289
|
- `MikuruProgress.mikuru`: determinate and indeterminate progress indicator.
|
|
282
290
|
- `MikuruCodeBlock.mikuru`: code display with language label, line numbers, and copy action.
|
|
291
|
+
- `MikuruTabs.mikuru`: accessible tab list with controlled `m-model`, keyboard navigation, and slot/fallback panels.
|
|
292
|
+
- `MikuruAccordion.mikuru`: single or multiple disclosure panels with controlled `m-model` and slot/fallback content.
|
|
293
|
+
- `MikuruTextInput.mikuru`, `MikuruTextarea.mikuru`, and `MikuruCheckbox.mikuru`: form controls that emit `update:modelValue`.
|
|
294
|
+
- `MikuruSelect.mikuru`: labeled select control with normalized string/object options.
|
|
295
|
+
- `MikuruCombobox.mikuru`: searchable single-select combobox with outside-click and Escape close.
|
|
296
|
+
- `MikuruHeader.mikuru`, `MikuruFooter.mikuru`, and `MikuruSideMenu.mikuru`: app shell layout primitives with normalized navigation items and selection events.
|
|
283
297
|
|
|
284
298
|
They can be imported from the package:
|
|
285
299
|
|
|
@@ -295,6 +309,16 @@ import MikuruDropdown from "mikuru/components/MikuruDropdown";
|
|
|
295
309
|
import MikuruToolTip from "mikuru/components/MikuruToolTip";
|
|
296
310
|
import MikuruProgress from "mikuru/components/MikuruProgress";
|
|
297
311
|
import MikuruCodeBlock from "mikuru/components/MikuruCodeBlock";
|
|
312
|
+
import MikuruTabs from "mikuru/components/MikuruTabs";
|
|
313
|
+
import MikuruAccordion from "mikuru/components/MikuruAccordion";
|
|
314
|
+
import MikuruTextInput from "mikuru/components/MikuruTextInput";
|
|
315
|
+
import MikuruTextarea from "mikuru/components/MikuruTextarea";
|
|
316
|
+
import MikuruCheckbox from "mikuru/components/MikuruCheckbox";
|
|
317
|
+
import MikuruSelect from "mikuru/components/MikuruSelect";
|
|
318
|
+
import MikuruCombobox from "mikuru/components/MikuruCombobox";
|
|
319
|
+
import MikuruHeader from "mikuru/components/MikuruHeader";
|
|
320
|
+
import MikuruFooter from "mikuru/components/MikuruFooter";
|
|
321
|
+
import MikuruSideMenu from "mikuru/components/MikuruSideMenu";
|
|
298
322
|
</script>
|
|
299
323
|
```
|
|
300
324
|
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="mikuru-accordion">
|
|
3
|
+
<details
|
|
4
|
+
m-for="item in normalizedItems"
|
|
5
|
+
:key="item.value"
|
|
6
|
+
class="accordion-item"
|
|
7
|
+
:class="{ open: isOpen(item.value) }"
|
|
8
|
+
@toggle="handleToggle(item, $event)"
|
|
9
|
+
>
|
|
10
|
+
<summary
|
|
11
|
+
class="accordion-trigger"
|
|
12
|
+
:aria-disabled="item.disabled ? 'true' : 'false'"
|
|
13
|
+
@click="handleSummaryClick(item, $event)"
|
|
14
|
+
>
|
|
15
|
+
<span>{{ item.label }}</span>
|
|
16
|
+
<span aria-hidden="true">{{ isOpen(item.value) ? "-" : "+" }}</span>
|
|
17
|
+
</summary>
|
|
18
|
+
<div class="accordion-panel">
|
|
19
|
+
<slot :item="item">{{ item.panel }}</slot>
|
|
20
|
+
</div>
|
|
21
|
+
</details>
|
|
22
|
+
</section>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script>
|
|
26
|
+
import { ref, watch } from "mikuru";
|
|
27
|
+
|
|
28
|
+
const {
|
|
29
|
+
items = [],
|
|
30
|
+
modelValue = "",
|
|
31
|
+
multiple = false
|
|
32
|
+
} = defineProps();
|
|
33
|
+
|
|
34
|
+
const emit = defineEmits(["update:modelValue", "change"]);
|
|
35
|
+
const normalizedItems = ref([]);
|
|
36
|
+
const openValues = ref([]);
|
|
37
|
+
let itemsSignature = "";
|
|
38
|
+
|
|
39
|
+
watch(items, syncItems, { immediate: true });
|
|
40
|
+
watch(modelValue, syncOpenValues, { immediate: true });
|
|
41
|
+
|
|
42
|
+
function syncItems() {
|
|
43
|
+
const source = Array.isArray(items.value) ? items.value : [];
|
|
44
|
+
const nextItems = source.map((item, index) => {
|
|
45
|
+
if (typeof item === "string") {
|
|
46
|
+
return { label: item, value: item, panel: "", disabled: false };
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
label: item.label || `Item ${index + 1}`,
|
|
50
|
+
value: item.value ?? item.label ?? index,
|
|
51
|
+
panel: item.panel || "",
|
|
52
|
+
disabled: Boolean(item.disabled)
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
const nextSignature = nextItems
|
|
56
|
+
.map((item) => `${item.value}\u0000${item.label}\u0000${item.panel}\u0000${item.disabled}`)
|
|
57
|
+
.join("\u0001");
|
|
58
|
+
if (nextSignature === itemsSignature) return;
|
|
59
|
+
itemsSignature = nextSignature;
|
|
60
|
+
normalizedItems.value = nextItems;
|
|
61
|
+
syncOpenValues();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function syncOpenValues() {
|
|
65
|
+
const source = multiple.value
|
|
66
|
+
? (Array.isArray(modelValue.value) ? modelValue.value : [])
|
|
67
|
+
: (modelValue.value === "" || modelValue.value == null ? [] : [modelValue.value]);
|
|
68
|
+
const availableValues = normalizedItems.value.map((item) => item.value);
|
|
69
|
+
openValues.value = source.filter((value) => availableValues.some((itemValue) => Object.is(itemValue, value)));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isOpen(value) {
|
|
73
|
+
return openValues.value.some((itemValue) => Object.is(itemValue, value));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function handleSummaryClick(item, event) {
|
|
77
|
+
if (!item.disabled) return;
|
|
78
|
+
event.preventDefault();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function handleToggle(item, event) {
|
|
82
|
+
if (item.disabled) {
|
|
83
|
+
event.target.open = false;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (event.target.open === isOpen(item.value)) return;
|
|
87
|
+
toggleItem(item);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function toggleItem(item) {
|
|
91
|
+
let nextValues;
|
|
92
|
+
if (multiple.value) {
|
|
93
|
+
nextValues = isOpen(item.value)
|
|
94
|
+
? openValues.value.filter((value) => !Object.is(value, item.value))
|
|
95
|
+
: [...openValues.value, item.value];
|
|
96
|
+
} else {
|
|
97
|
+
nextValues = isOpen(item.value) ? [] : [item.value];
|
|
98
|
+
}
|
|
99
|
+
openValues.value = nextValues;
|
|
100
|
+
const payload = multiple.value ? nextValues : nextValues[0] ?? "";
|
|
101
|
+
emit("update:modelValue", payload);
|
|
102
|
+
emit("change", payload);
|
|
103
|
+
}
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<style scoped>
|
|
107
|
+
.mikuru-accordion {
|
|
108
|
+
display: grid;
|
|
109
|
+
overflow: hidden;
|
|
110
|
+
border: 1px solid #e2e8f0;
|
|
111
|
+
border-radius: 8px;
|
|
112
|
+
background: #ffffff;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.accordion-item + .accordion-item {
|
|
116
|
+
border-top: 1px solid #e2e8f0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.accordion-trigger {
|
|
120
|
+
display: flex;
|
|
121
|
+
width: 100%;
|
|
122
|
+
align-items: center;
|
|
123
|
+
justify-content: space-between;
|
|
124
|
+
gap: 12px;
|
|
125
|
+
border: 0;
|
|
126
|
+
padding: 12px 14px;
|
|
127
|
+
color: #0f172a;
|
|
128
|
+
background: #ffffff;
|
|
129
|
+
text-align: left;
|
|
130
|
+
font: inherit;
|
|
131
|
+
cursor: pointer;
|
|
132
|
+
list-style: none;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.accordion-trigger::-webkit-details-marker {
|
|
136
|
+
display: none;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.accordion-trigger:hover,
|
|
140
|
+
.accordion-trigger:focus-visible,
|
|
141
|
+
.accordion-item.open .accordion-trigger {
|
|
142
|
+
background: #f8fafc;
|
|
143
|
+
outline: none;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.accordion-trigger[aria-disabled="true"] {
|
|
147
|
+
color: #94a3b8;
|
|
148
|
+
cursor: not-allowed;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.accordion-panel {
|
|
152
|
+
padding: 0 14px 14px;
|
|
153
|
+
color: #475569;
|
|
154
|
+
line-height: 1.5;
|
|
155
|
+
}
|
|
156
|
+
</style>
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
</div>
|
|
23
23
|
|
|
24
24
|
<button class="carousel-arrow previous" type="button" @click="previous" aria-label="Previous slide">
|
|
25
|
-
|
|
25
|
+
<span class="carousel-arrow-icon icon-previous" aria-hidden="true"></span>
|
|
26
26
|
</button>
|
|
27
27
|
<button class="carousel-arrow next" type="button" @click="next" aria-label="Next slide">
|
|
28
|
-
|
|
28
|
+
<span class="carousel-arrow-icon icon-next" aria-hidden="true"></span>
|
|
29
29
|
</button>
|
|
30
30
|
</div>
|
|
31
31
|
|
|
@@ -42,6 +42,31 @@
|
|
|
42
42
|
></button>
|
|
43
43
|
</div>
|
|
44
44
|
</div>
|
|
45
|
+
|
|
46
|
+
<div m-if="showThumbnails" class="carousel-thumbnail-shell">
|
|
47
|
+
<button class="thumbnail-scroll previous" type="button" @click="scrollThumbnails(-1)" aria-label="Scroll thumbnails left">
|
|
48
|
+
<span class="carousel-arrow-icon icon-previous" aria-hidden="true"></span>
|
|
49
|
+
</button>
|
|
50
|
+
|
|
51
|
+
<div ref="thumbnailTrackEl" class="carousel-thumbnails" role="tablist" aria-label="Carousel thumbnails">
|
|
52
|
+
<button
|
|
53
|
+
m-for="slide in slides"
|
|
54
|
+
:key="slide.id"
|
|
55
|
+
type="button"
|
|
56
|
+
class="carousel-thumbnail"
|
|
57
|
+
:class="{ active: slide.index === activeIndex }"
|
|
58
|
+
:aria-label="slide.label"
|
|
59
|
+
:aria-selected="slide.index === activeIndex"
|
|
60
|
+
@click="goToSlide(slide.index)"
|
|
61
|
+
>
|
|
62
|
+
<img :src="slide.thumbnail" :alt="slide.alt" />
|
|
63
|
+
</button>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<button class="thumbnail-scroll next" type="button" @click="scrollThumbnails(1)" aria-label="Scroll thumbnails right">
|
|
67
|
+
<span class="carousel-arrow-icon icon-next" aria-hidden="true"></span>
|
|
68
|
+
</button>
|
|
69
|
+
</div>
|
|
45
70
|
</section>
|
|
46
71
|
</template>
|
|
47
72
|
|
|
@@ -53,6 +78,7 @@ const {
|
|
|
53
78
|
title = "Mikuru Carousel",
|
|
54
79
|
autoplay = false,
|
|
55
80
|
interval = 5000,
|
|
81
|
+
thumbnails = false,
|
|
56
82
|
emptyTitle = "No slides",
|
|
57
83
|
emptyMessage = "Add images to show the carousel."
|
|
58
84
|
} = defineProps({
|
|
@@ -60,18 +86,21 @@ const {
|
|
|
60
86
|
title: String,
|
|
61
87
|
autoplay: Boolean,
|
|
62
88
|
interval: Number,
|
|
89
|
+
thumbnails: Boolean,
|
|
63
90
|
emptyTitle: String,
|
|
64
91
|
emptyMessage: String
|
|
65
92
|
});
|
|
66
93
|
|
|
67
94
|
const activeIndex = ref(0);
|
|
68
95
|
const slides = ref([]);
|
|
96
|
+
const thumbnailTrackEl = ref(null);
|
|
69
97
|
let slidesSignature = "";
|
|
70
98
|
let timer = null;
|
|
71
99
|
let mounted = false;
|
|
72
100
|
|
|
73
101
|
const slideCount = computed(() => slides.value.length);
|
|
74
102
|
const isEmpty = computed(() => slideCount.value === 0);
|
|
103
|
+
const showThumbnails = computed(() => thumbnails.value && slideCount.value > 0);
|
|
75
104
|
const trackStyle = computed(() => `transform: translateX(-${activeIndex.value * 100}%)`);
|
|
76
105
|
const positionLabel = computed(() => {
|
|
77
106
|
if (slideCount.value === 0) return "0 / 0";
|
|
@@ -87,10 +116,12 @@ function syncSlides() {
|
|
|
87
116
|
const alt = typeof item === "string" ? "" : item.alt || item.title || `Slide ${index + 1}`;
|
|
88
117
|
const slideTitle = typeof item === "string" ? `Slide ${index + 1}` : item.title || `Slide ${index + 1}`;
|
|
89
118
|
const caption = typeof item === "string" ? "" : item.caption || "";
|
|
119
|
+
const thumbnail = typeof item === "string" ? item : item.thumbnail || item.src;
|
|
90
120
|
return {
|
|
91
121
|
id: `${src}-${index}`,
|
|
92
122
|
index,
|
|
93
123
|
src,
|
|
124
|
+
thumbnail,
|
|
94
125
|
alt,
|
|
95
126
|
title: slideTitle,
|
|
96
127
|
caption,
|
|
@@ -98,7 +129,7 @@ function syncSlides() {
|
|
|
98
129
|
};
|
|
99
130
|
});
|
|
100
131
|
const nextSignature = nextSlides
|
|
101
|
-
.map((slide) => `${slide.id}\u0000${slide.alt}\u0000${slide.title}\u0000${slide.caption}`)
|
|
132
|
+
.map((slide) => `${slide.id}\u0000${slide.thumbnail}\u0000${slide.alt}\u0000${slide.title}\u0000${slide.caption}`)
|
|
102
133
|
.join("\u0001");
|
|
103
134
|
if (nextSignature === slidesSignature) return;
|
|
104
135
|
slidesSignature = nextSignature;
|
|
@@ -128,6 +159,7 @@ function clampIndex(index) {
|
|
|
128
159
|
|
|
129
160
|
function goToSlide(index) {
|
|
130
161
|
activeIndex.value = clampIndex(index);
|
|
162
|
+
centerActiveThumbnail();
|
|
131
163
|
restartAutoplay();
|
|
132
164
|
}
|
|
133
165
|
|
|
@@ -139,6 +171,18 @@ function next() {
|
|
|
139
171
|
goToSlide(activeIndex.value + 1);
|
|
140
172
|
}
|
|
141
173
|
|
|
174
|
+
function scrollThumbnails(direction) {
|
|
175
|
+
goToSlide(activeIndex.value + direction);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function centerActiveThumbnail() {
|
|
179
|
+
window.setTimeout(() => {
|
|
180
|
+
const track = thumbnailTrackEl.value;
|
|
181
|
+
const activeThumbnail = track?.querySelector?.(".carousel-thumbnail.active");
|
|
182
|
+
activeThumbnail?.scrollIntoView?.({ behavior: "smooth", block: "nearest", inline: "center" });
|
|
183
|
+
}, 0);
|
|
184
|
+
}
|
|
185
|
+
|
|
142
186
|
function startAutoplay() {
|
|
143
187
|
stopAutoplay();
|
|
144
188
|
if (!autoplay.value || slideCount.value <= 1) return;
|
|
@@ -250,13 +294,29 @@ function handleKeydown(event) {
|
|
|
250
294
|
border-radius: 999px;
|
|
251
295
|
color: #111827;
|
|
252
296
|
background: #ffffff;
|
|
253
|
-
|
|
254
|
-
font-size: 1.8rem;
|
|
255
|
-
line-height: 1;
|
|
297
|
+
padding: 0;
|
|
256
298
|
transform: translateY(-50%);
|
|
257
299
|
cursor: pointer;
|
|
258
300
|
}
|
|
259
301
|
|
|
302
|
+
.carousel-arrow-icon {
|
|
303
|
+
display: block;
|
|
304
|
+
width: 18px;
|
|
305
|
+
height: 18px;
|
|
306
|
+
background: currentColor;
|
|
307
|
+
mask-position: center;
|
|
308
|
+
mask-repeat: no-repeat;
|
|
309
|
+
mask-size: contain;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.icon-previous {
|
|
313
|
+
mask-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M169.4 297.4C156.9 309.9 156.9 330.2 169.4 342.7L361.4 534.7C373.9 547.2 394.2 547.2 406.7 534.7C419.2 522.2 419.2 501.9 406.7 489.4L237.3 320L406.6 150.6C419.1 138.1 419.1 117.8 406.6 105.3C394.1 92.8 373.8 92.8 361.3 105.3L169.3 297.3z"/></svg>');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.icon-next {
|
|
317
|
+
mask-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M471.1 297.4C483.6 309.9 483.6 330.2 471.1 342.7L279.1 534.7C266.6 547.2 246.3 547.2 233.8 534.7C221.3 522.2 221.3 501.9 233.8 489.4L403.2 320L233.9 150.6C221.4 138.1 221.4 117.8 233.9 105.3C246.4 92.8 266.7 92.8 279.2 105.3L471.2 297.3z"/></svg>');
|
|
318
|
+
}
|
|
319
|
+
|
|
260
320
|
.previous {
|
|
261
321
|
left: 12px;
|
|
262
322
|
}
|
|
@@ -293,4 +353,82 @@ function handleKeydown(event) {
|
|
|
293
353
|
width: 24px;
|
|
294
354
|
background: #111827;
|
|
295
355
|
}
|
|
356
|
+
|
|
357
|
+
.carousel-thumbnail-shell {
|
|
358
|
+
position: relative;
|
|
359
|
+
display: grid;
|
|
360
|
+
align-items: center;
|
|
361
|
+
padding: 0 42px;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.carousel-thumbnails {
|
|
365
|
+
display: grid;
|
|
366
|
+
grid-auto-columns: minmax(86px, 1fr);
|
|
367
|
+
grid-auto-flow: column;
|
|
368
|
+
gap: 8px;
|
|
369
|
+
overflow-x: auto;
|
|
370
|
+
scrollbar-width: none;
|
|
371
|
+
padding-bottom: 2px;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.carousel-thumbnails::-webkit-scrollbar {
|
|
375
|
+
display: none;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.carousel-thumbnail {
|
|
379
|
+
display: block;
|
|
380
|
+
aspect-ratio: 1;
|
|
381
|
+
min-width: 86px;
|
|
382
|
+
overflow: hidden;
|
|
383
|
+
border: 2px solid transparent;
|
|
384
|
+
border-radius: 7px;
|
|
385
|
+
padding: 0;
|
|
386
|
+
background: #e2e8f0;
|
|
387
|
+
cursor: pointer;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.carousel-thumbnail:hover,
|
|
391
|
+
.carousel-thumbnail:focus-visible,
|
|
392
|
+
.carousel-thumbnail.active {
|
|
393
|
+
border-color: #111827;
|
|
394
|
+
outline: none;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.carousel-thumbnail img {
|
|
398
|
+
display: block;
|
|
399
|
+
width: 100%;
|
|
400
|
+
height: 100%;
|
|
401
|
+
object-fit: cover;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.thumbnail-scroll {
|
|
405
|
+
position: absolute;
|
|
406
|
+
top: 50%;
|
|
407
|
+
z-index: 1;
|
|
408
|
+
display: grid;
|
|
409
|
+
width: 32px;
|
|
410
|
+
height: 32px;
|
|
411
|
+
place-items: center;
|
|
412
|
+
border: 1px solid #cbd5e1;
|
|
413
|
+
border-radius: 999px;
|
|
414
|
+
color: #111827;
|
|
415
|
+
background: #ffffff;
|
|
416
|
+
box-shadow: 0 8px 24px rgb(15 23 42 / 18%);
|
|
417
|
+
padding: 0;
|
|
418
|
+
transform: translateY(-50%);
|
|
419
|
+
cursor: pointer;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.thumbnail-scroll.previous {
|
|
423
|
+
left: 0;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.thumbnail-scroll.next {
|
|
427
|
+
right: 0;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.thumbnail-scroll .carousel-arrow-icon {
|
|
431
|
+
width: 14px;
|
|
432
|
+
height: 14px;
|
|
433
|
+
}
|
|
296
434
|
</style>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<label class="mikuru-checkbox">
|
|
3
|
+
<input
|
|
4
|
+
type="checkbox"
|
|
5
|
+
:checked="checked"
|
|
6
|
+
:value="value"
|
|
7
|
+
:disabled="disabled"
|
|
8
|
+
@change="updateChecked($event)"
|
|
9
|
+
/>
|
|
10
|
+
<span>
|
|
11
|
+
<span class="checkbox-label">{{ label }}</span>
|
|
12
|
+
<small m-if="description">{{ description }}</small>
|
|
13
|
+
</span>
|
|
14
|
+
</label>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script>
|
|
18
|
+
import { computed } from "mikuru";
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
label = "Checkbox",
|
|
22
|
+
description = "",
|
|
23
|
+
modelValue = false,
|
|
24
|
+
value = "on",
|
|
25
|
+
disabled = false
|
|
26
|
+
} = defineProps();
|
|
27
|
+
|
|
28
|
+
const emit = defineEmits(["update:modelValue", "change"]);
|
|
29
|
+
|
|
30
|
+
const checked = computed(() => {
|
|
31
|
+
if (Array.isArray(modelValue.value)) {
|
|
32
|
+
return modelValue.value.some((item) => Object.is(item, value.value));
|
|
33
|
+
}
|
|
34
|
+
return Boolean(modelValue.value);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
function updateChecked(event) {
|
|
38
|
+
let nextValue;
|
|
39
|
+
if (Array.isArray(modelValue.value)) {
|
|
40
|
+
nextValue = event.target.checked
|
|
41
|
+
? [...modelValue.value, value.value]
|
|
42
|
+
: modelValue.value.filter((item) => !Object.is(item, value.value));
|
|
43
|
+
} else {
|
|
44
|
+
nextValue = event.target.checked;
|
|
45
|
+
}
|
|
46
|
+
emit("update:modelValue", nextValue);
|
|
47
|
+
emit("change", nextValue);
|
|
48
|
+
}
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<style scoped>
|
|
52
|
+
.mikuru-checkbox {
|
|
53
|
+
display: inline-grid;
|
|
54
|
+
grid-template-columns: auto 1fr;
|
|
55
|
+
gap: 9px;
|
|
56
|
+
align-items: start;
|
|
57
|
+
color: #0f172a;
|
|
58
|
+
font: inherit;
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.mikuru-checkbox input {
|
|
63
|
+
width: 18px;
|
|
64
|
+
height: 18px;
|
|
65
|
+
margin: 2px 0 0;
|
|
66
|
+
accent-color: #2563eb;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.mikuru-checkbox input:disabled {
|
|
70
|
+
cursor: not-allowed;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.checkbox-label {
|
|
74
|
+
display: block;
|
|
75
|
+
font-weight: 650;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.mikuru-checkbox small {
|
|
79
|
+
display: block;
|
|
80
|
+
margin-top: 2px;
|
|
81
|
+
color: #64748b;
|
|
82
|
+
line-height: 1.35;
|
|
83
|
+
}
|
|
84
|
+
</style>
|