mikuru 1.0.39 → 1.0.40

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,46 @@
1
+ <template>
2
+ <label class="mikuru-field-shell" :data-required="required ? 'true' : 'false'">
3
+ <span class="field-heading">
4
+ <span>{{ label }}</span>
5
+ <span m-if="required" aria-hidden="true">*</span>
6
+ </span>
7
+ <slot></slot>
8
+ <MikuruFormMessage m-if="error" tone="error" :message="error" />
9
+ <MikuruFormMessage m-else-if="help" tone="neutral" :message="help" />
10
+ </label>
11
+ </template>
12
+
13
+ <script>
14
+ import MikuruFormMessage from "./MikuruFormMessage.mikuru";
15
+
16
+ const {
17
+ label = "Field",
18
+ help = "",
19
+ error = "",
20
+ required = false
21
+ } = defineProps({
22
+ label: String,
23
+ help: String,
24
+ error: String,
25
+ required: Boolean
26
+ });
27
+ </script>
28
+
29
+ <style scoped>
30
+ .mikuru-field-shell {
31
+ display: grid;
32
+ gap: 6px;
33
+ color: #111827;
34
+ font: inherit;
35
+ }
36
+
37
+ .field-heading {
38
+ display: inline-flex;
39
+ gap: 4px;
40
+ font-weight: 650;
41
+ }
42
+
43
+ .field-heading span:last-child {
44
+ color: #dc2626;
45
+ }
46
+ </style>
@@ -0,0 +1,80 @@
1
+ <template>
2
+ <section class="mikuru-filter-bar" :aria-label="label">
3
+ <MikuruSearchInput
4
+ :label="searchLabel"
5
+ :modelValue="search"
6
+ :placeholder="searchPlaceholder"
7
+ @input="setSearch"
8
+ @submit="submitSearch"
9
+ @clear="clearSearch"
10
+ />
11
+ <div class="filter-actions">
12
+ <MikuruChip
13
+ m-for="filter in filters"
14
+ :key="filter.value"
15
+ :label="filter.label"
16
+ :tone="filter.value === activeFilter ? 'info' : 'neutral'"
17
+ @click="selectFilter(filter)"
18
+ />
19
+ <slot></slot>
20
+ </div>
21
+ </section>
22
+ </template>
23
+
24
+ <script>
25
+ import MikuruChip from "./MikuruChip.mikuru";
26
+ import MikuruSearchInput from "./MikuruSearchInput.mikuru";
27
+
28
+ const {
29
+ label = "Filter bar",
30
+ search = "",
31
+ searchLabel = "Search",
32
+ searchPlaceholder = "Search...",
33
+ filters = [],
34
+ activeFilter = ""
35
+ } = defineProps({
36
+ label: String,
37
+ search: String,
38
+ searchLabel: String,
39
+ searchPlaceholder: String,
40
+ filters: Array,
41
+ activeFilter: String
42
+ });
43
+
44
+ const emit = defineEmits(["update:search", "search", "filter", "clear"]);
45
+
46
+ function setSearch(value) {
47
+ emit("update:search", value);
48
+ emit("search", value);
49
+ }
50
+
51
+ function submitSearch(value) {
52
+ emit("search", value);
53
+ }
54
+
55
+ function clearSearch() {
56
+ emit("update:search", "");
57
+ emit("clear");
58
+ }
59
+
60
+ function selectFilter(filter) {
61
+ emit("filter", filter.value, filter);
62
+ }
63
+ </script>
64
+
65
+ <style scoped>
66
+ .mikuru-filter-bar {
67
+ display: grid;
68
+ gap: 10px;
69
+ }
70
+
71
+ .filter-actions {
72
+ display: flex;
73
+ flex-wrap: wrap;
74
+ gap: 6px;
75
+ }
76
+
77
+ :deep(.mikuru-chip) {
78
+ cursor: pointer;
79
+ }
80
+ </style>
@@ -0,0 +1,67 @@
1
+ <template>
2
+ <form class="mikuru-form" :aria-label="label" @submit="submitForm" @reset="resetForm">
3
+ <header m-if="title || description">
4
+ <h3 m-if="title">{{ title }}</h3>
5
+ <p m-if="description">{{ description }}</p>
6
+ </header>
7
+ <fieldset :disabled="disabled">
8
+ <slot></slot>
9
+ </fieldset>
10
+ </form>
11
+ </template>
12
+
13
+ <script>
14
+ const {
15
+ label = "Form",
16
+ title = "",
17
+ description = "",
18
+ disabled = false
19
+ } = defineProps({
20
+ label: String,
21
+ title: String,
22
+ description: String,
23
+ disabled: Boolean
24
+ });
25
+
26
+ const emit = defineEmits(["submit", "reset"]);
27
+
28
+ function submitForm(event) {
29
+ event.preventDefault();
30
+ emit("submit", event);
31
+ }
32
+
33
+ function resetForm(event) {
34
+ emit("reset", event);
35
+ }
36
+ </script>
37
+
38
+ <style scoped>
39
+ .mikuru-form {
40
+ display: grid;
41
+ gap: 12px;
42
+ color: #111827;
43
+ font: inherit;
44
+ }
45
+
46
+ header {
47
+ display: grid;
48
+ gap: 4px;
49
+ }
50
+
51
+ h3,
52
+ p {
53
+ margin: 0;
54
+ }
55
+
56
+ p {
57
+ color: #64748b;
58
+ }
59
+
60
+ fieldset {
61
+ display: grid;
62
+ gap: 12px;
63
+ margin: 0;
64
+ border: 0;
65
+ padding: 0;
66
+ }
67
+ </style>
@@ -0,0 +1,33 @@
1
+ <template>
2
+ <p class="mikuru-form-message" :data-tone="tone" role="status">{{ message }}</p>
3
+ </template>
4
+
5
+ <script>
6
+ const {
7
+ message = "",
8
+ tone = "neutral"
9
+ } = defineProps({
10
+ message: String,
11
+ tone: String
12
+ });
13
+ </script>
14
+
15
+ <style scoped>
16
+ .mikuru-form-message {
17
+ margin: 0;
18
+ color: #64748b;
19
+ font-size: 0.88rem;
20
+ }
21
+
22
+ .mikuru-form-message[data-tone="error"] {
23
+ color: #b91c1c;
24
+ }
25
+
26
+ .mikuru-form-message[data-tone="success"] {
27
+ color: #166534;
28
+ }
29
+
30
+ .mikuru-form-message[data-tone="warning"] {
31
+ color: #92400e;
32
+ }
33
+ </style>
@@ -0,0 +1,91 @@
1
+ <template>
2
+ <label class="mikuru-input-otp">
3
+ <span>{{ label }}</span>
4
+ <input
5
+ inputmode="numeric"
6
+ autocomplete="one-time-code"
7
+ :maxlength="length"
8
+ :value="modelValue"
9
+ :placeholder="placeholder"
10
+ :disabled="disabled"
11
+ @input="updateValue($event)"
12
+ />
13
+ <span class="otp-slots" aria-hidden="true">
14
+ <span m-for="slot in slots" :key="slot.index" :data-filled="slot.value ? 'true' : 'false'">{{ slot.value }}</span>
15
+ </span>
16
+ </label>
17
+ </template>
18
+
19
+ <script>
20
+ import { computed } from "mikuru";
21
+
22
+ const {
23
+ label = "One-time code",
24
+ modelValue = "",
25
+ length = 6,
26
+ disabled = false
27
+ } = defineProps({
28
+ label: String,
29
+ modelValue: String,
30
+ length: Number,
31
+ disabled: Boolean
32
+ });
33
+
34
+ const emit = defineEmits(["update:modelValue", "input", "complete"]);
35
+ const placeholder = computed(() => "0".repeat(length.value));
36
+ const slots = computed(() => {
37
+ return Array.from({ length: length.value }, (_, index) => ({
38
+ index,
39
+ value: modelValue.value[index] || ""
40
+ }));
41
+ });
42
+
43
+ function updateValue(event) {
44
+ const nextValue = event.target.value.replace(/\D/g, "").slice(0, length.value);
45
+ emit("update:modelValue", nextValue);
46
+ emit("input", nextValue);
47
+ if (nextValue.length === length.value) emit("complete", nextValue);
48
+ }
49
+ </script>
50
+
51
+ <style scoped>
52
+ .mikuru-input-otp {
53
+ display: grid;
54
+ gap: 8px;
55
+ color: #111827;
56
+ font: inherit;
57
+ }
58
+
59
+ .mikuru-input-otp > span:first-child {
60
+ font-weight: 650;
61
+ }
62
+
63
+ input {
64
+ position: absolute;
65
+ width: 1px;
66
+ height: 1px;
67
+ opacity: 0;
68
+ }
69
+
70
+ .otp-slots {
71
+ display: flex;
72
+ gap: 8px;
73
+ }
74
+
75
+ .otp-slots span {
76
+ display: grid;
77
+ place-items: center;
78
+ width: 38px;
79
+ height: 42px;
80
+ border: 1px solid #cbd5e1;
81
+ border-radius: 8px;
82
+ color: #111827;
83
+ background: #ffffff;
84
+ font-weight: 800;
85
+ }
86
+
87
+ .otp-slots span[data-filled="true"] {
88
+ border-color: #2563eb;
89
+ background: #eff6ff;
90
+ }
91
+ </style>
@@ -0,0 +1,103 @@
1
+ <template>
2
+ <label class="mikuru-number-input">
3
+ <span>{{ label }}</span>
4
+ <span class="number-control">
5
+ <button type="button" :disabled="disabled" @click="stepDown">−</button>
6
+ <input
7
+ type="number"
8
+ :value="modelValue"
9
+ :min="min"
10
+ :max="max"
11
+ :step="step"
12
+ :disabled="disabled"
13
+ @input="updateValue($event)"
14
+ />
15
+ <button type="button" :disabled="disabled" @click="stepUp">+</button>
16
+ </span>
17
+ </label>
18
+ </template>
19
+
20
+ <script>
21
+ const {
22
+ label = "Number",
23
+ modelValue = 0,
24
+ min = 0,
25
+ max = 100,
26
+ step = 1,
27
+ disabled = false
28
+ } = defineProps({
29
+ label: String,
30
+ modelValue: Number,
31
+ min: Number,
32
+ max: Number,
33
+ step: Number,
34
+ disabled: Boolean
35
+ });
36
+
37
+ const emit = defineEmits(["update:modelValue", "input", "change"]);
38
+
39
+ function clamp(value) {
40
+ return Math.min(max.value, Math.max(min.value, value));
41
+ }
42
+
43
+ function emitValue(value) {
44
+ const nextValue = clamp(Number(value));
45
+ emit("update:modelValue", nextValue);
46
+ emit("input", nextValue);
47
+ emit("change", nextValue);
48
+ }
49
+
50
+ function updateValue(event) {
51
+ emitValue(event.target.value);
52
+ }
53
+
54
+ function stepDown() {
55
+ emitValue(modelValue.value - step.value);
56
+ }
57
+
58
+ function stepUp() {
59
+ emitValue(modelValue.value + step.value);
60
+ }
61
+ </script>
62
+
63
+ <style scoped>
64
+ .mikuru-number-input {
65
+ display: grid;
66
+ gap: 6px;
67
+ color: #111827;
68
+ font: inherit;
69
+ }
70
+
71
+ .mikuru-number-input > span:first-child {
72
+ font-weight: 650;
73
+ }
74
+
75
+ .number-control {
76
+ display: inline-grid;
77
+ grid-template-columns: auto minmax(72px, 1fr) auto;
78
+ width: fit-content;
79
+ border: 1px solid #cbd5e1;
80
+ border-radius: 8px;
81
+ overflow: hidden;
82
+ background: #ffffff;
83
+ }
84
+
85
+ input,
86
+ button {
87
+ border: 0;
88
+ padding: 9px 11px;
89
+ color: #111827;
90
+ background: #ffffff;
91
+ font: inherit;
92
+ }
93
+
94
+ input {
95
+ width: 82px;
96
+ border-inline: 1px solid #e5e7eb;
97
+ text-align: center;
98
+ }
99
+
100
+ button {
101
+ cursor: pointer;
102
+ }
103
+ </style>
@@ -0,0 +1,107 @@
1
+ <template>
2
+ <label class="mikuru-password-input">
3
+ <span>{{ label }}</span>
4
+ <span class="password-control">
5
+ <input
6
+ :type="visible ? 'text' : 'password'"
7
+ :value="modelValue"
8
+ :placeholder="placeholder"
9
+ :autocomplete="autocomplete"
10
+ :disabled="disabled"
11
+ @input="updateValue($event)"
12
+ />
13
+ <button type="button" @click="toggleVisible">{{ visible ? hideLabel : showLabel }}</button>
14
+ </span>
15
+ <small m-if="strength">{{ strengthLabel }}</small>
16
+ </label>
17
+ </template>
18
+
19
+ <script>
20
+ import { computed, ref } from "mikuru";
21
+
22
+ const {
23
+ label = "Password",
24
+ modelValue = "",
25
+ placeholder = "",
26
+ autocomplete = "current-password",
27
+ disabled = false,
28
+ strength = true,
29
+ showLabel = "Show",
30
+ hideLabel = "Hide"
31
+ } = defineProps({
32
+ label: String,
33
+ modelValue: String,
34
+ placeholder: String,
35
+ autocomplete: String,
36
+ disabled: Boolean,
37
+ strength: Boolean,
38
+ showLabel: String,
39
+ hideLabel: String
40
+ });
41
+
42
+ const emit = defineEmits(["update:modelValue", "input"]);
43
+ const visible = ref(false);
44
+ const strengthLabel = computed(() => {
45
+ const value = modelValue.value || "";
46
+ if (value.length >= 12) return "Strong password";
47
+ if (value.length >= 8) return "Medium password";
48
+ return "Weak password";
49
+ });
50
+
51
+ function toggleVisible() {
52
+ visible.value = !visible.value;
53
+ }
54
+
55
+ function updateValue(event) {
56
+ emit("update:modelValue", event.target.value);
57
+ emit("input", event.target.value);
58
+ }
59
+ </script>
60
+
61
+ <style scoped>
62
+ .mikuru-password-input {
63
+ display: grid;
64
+ gap: 6px;
65
+ color: #111827;
66
+ font: inherit;
67
+ }
68
+
69
+ .mikuru-password-input > span:first-child {
70
+ font-weight: 650;
71
+ }
72
+
73
+ .password-control {
74
+ display: grid;
75
+ grid-template-columns: 1fr auto;
76
+ gap: 8px;
77
+ }
78
+
79
+ input {
80
+ min-width: 0;
81
+ border: 1px solid #cbd5e1;
82
+ border-radius: 8px;
83
+ padding: 10px 12px;
84
+ color: #111827;
85
+ background: #ffffff;
86
+ font: inherit;
87
+ }
88
+
89
+ input:focus {
90
+ border-color: #2563eb;
91
+ outline: 3px solid rgb(37 99 235 / 18%);
92
+ }
93
+
94
+ button {
95
+ border: 1px solid #cbd5e1;
96
+ border-radius: 8px;
97
+ padding: 10px 12px;
98
+ color: #111827;
99
+ background: #ffffff;
100
+ font: inherit;
101
+ cursor: pointer;
102
+ }
103
+
104
+ small {
105
+ color: #64748b;
106
+ }
107
+ </style>
@@ -0,0 +1,75 @@
1
+ <template>
2
+ <section class="mikuru-virtual-list" :style="listStyle" @scroll="handleScroll">
3
+ <div :style="spacerStyle">
4
+ <article
5
+ m-for="item in visibleItems"
6
+ :key="item.key"
7
+ class="virtual-row"
8
+ :style="item.style"
9
+ >
10
+ <strong>{{ item.data.title || item.data.label || item.data.id }}</strong>
11
+ <span m-if="item.data.description">{{ item.data.description }}</span>
12
+ </article>
13
+ </div>
14
+ </section>
15
+ </template>
16
+
17
+ <script>
18
+ import { computed, ref } from "mikuru";
19
+
20
+ const {
21
+ items = [],
22
+ itemHeight = 48,
23
+ height = 240
24
+ } = defineProps({
25
+ items: Array,
26
+ itemHeight: Number,
27
+ height: Number
28
+ });
29
+
30
+ const scrollTop = ref(0);
31
+ const listStyle = computed(() => `height: ${height.value}px;`);
32
+ const spacerStyle = computed(() => `height: ${items.value.length * itemHeight.value}px; position: relative;`);
33
+ const visibleItems = computed(() => {
34
+ const start = Math.max(0, Math.floor(scrollTop.value / itemHeight.value) - 2);
35
+ const count = Math.ceil(height.value / itemHeight.value) + 5;
36
+ return items.value.slice(start, start + count).map((item, offset) => {
37
+ const index = start + offset;
38
+ return {
39
+ key: item.id || item.value || index,
40
+ data: item,
41
+ style: `height: ${itemHeight.value}px; transform: translateY(${index * itemHeight.value}px);`
42
+ };
43
+ });
44
+ });
45
+
46
+ function handleScroll(event) {
47
+ scrollTop.value = event.target.scrollTop;
48
+ }
49
+ </script>
50
+
51
+ <style scoped>
52
+ .mikuru-virtual-list {
53
+ overflow: auto;
54
+ border: 1px solid #e5e7eb;
55
+ border-radius: 8px;
56
+ background: #ffffff;
57
+ }
58
+
59
+ .virtual-row {
60
+ position: absolute;
61
+ inset-inline: 0;
62
+ display: grid;
63
+ align-content: center;
64
+ gap: 2px;
65
+ box-sizing: border-box;
66
+ border-bottom: 1px solid #f1f5f9;
67
+ padding: 7px 12px;
68
+ color: #111827;
69
+ }
70
+
71
+ span {
72
+ color: #64748b;
73
+ font-size: 0.86rem;
74
+ }
75
+ </style>