summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsyuilo <4439005+syuilo@users.noreply.github.com>2026-01-14 14:21:51 +0900
committersyuilo <4439005+syuilo@users.noreply.github.com>2026-01-14 14:21:51 +0900
commitd8318c02a18065892fc9e373b97b56becd355467 (patch)
tree912ddf123fa6ad17ca68c616e04353e07ec5790b
parentrefactor(frontend): MkRadiosの指定をpropsから行うように (#16597) (diff)
downloadmisskey-d8318c02a18065892fc9e373b97b56becd355467.tar.gz
misskey-d8318c02a18065892fc9e373b97b56becd355467.tar.bz2
misskey-d8318c02a18065892fc9e373b97b56becd355467.zip
refactor(frontend): MkRadioをMkRadiosに統合
-rw-r--r--packages/frontend/src/components/MkPreview.vue8
-rw-r--r--packages/frontend/src/components/MkRadio.vue136
-rw-r--r--packages/frontend/src/components/MkRadios.vue141
-rw-r--r--packages/frontend/src/pages/admin/ads.vue9
4 files changed, 119 insertions, 175 deletions
diff --git a/packages/frontend/src/components/MkPreview.vue b/packages/frontend/src/components/MkPreview.vue
index c25f9ab0a9..c589cd9685 100644
--- a/packages/frontend/src/components/MkPreview.vue
+++ b/packages/frontend/src/components/MkPreview.vue
@@ -12,11 +12,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="flag" :class="$style.preview__content1__switch_button">
<span>Switch is now {{ flag ? 'on' : 'off' }}</span>
</MkSwitch>
- <div :class="$style.preview__content1__input">
- <MkRadio v-model="radio" value="misskey">Misskey</MkRadio>
- <MkRadio v-model="radio" value="mastodon">Mastodon</MkRadio>
- <MkRadio v-model="radio" value="pleroma">Pleroma</MkRadio>
- </div>
<div :class="$style.preview__content1__button">
<MkButton inline>This is</MkButton>
<MkButton inline primary>the button</MkButton>
@@ -40,15 +35,12 @@ import * as config from '@@/js/config.js';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkSwitch from '@/components/MkSwitch.vue';
-import MkTextarea from '@/components/MkTextarea.vue';
-import MkRadio from '@/components/MkRadio.vue';
import * as os from '@/os.js';
import { $i } from '@/i.js';
import { chooseDriveFile } from '@/utility/drive.js';
const text = ref('');
const flag = ref(true);
-const radio = ref('misskey');
const mfm = ref(`Hello world! This is an @example mention. BTW you are @${$i ? $i.username : 'guest'}.\nAlso, here is ${config.url} and [example link](${config.url}). for more details, see https://example.com.\nAs you know #misskey is open-source software.`);
const openDialog = async () => {
diff --git a/packages/frontend/src/components/MkRadio.vue b/packages/frontend/src/components/MkRadio.vue
deleted file mode 100644
index 19ba90052c..0000000000
--- a/packages/frontend/src/components/MkRadio.vue
+++ /dev/null
@@ -1,136 +0,0 @@
-<!--
-SPDX-FileCopyrightText: syuilo and misskey-project
-SPDX-License-Identifier: AGPL-3.0-only
--->
-
-<template>
-<div
- v-adaptive-border
- :class="[$style.root, { [$style.disabled]: disabled, [$style.checked]: checked }]"
- :aria-checked="checked"
- :aria-disabled="disabled"
- role="checkbox"
- @click="toggle"
->
- <input
- type="radio"
- :disabled="disabled"
- :class="$style.input"
- >
- <span :class="$style.button">
- <span></span>
- </span>
- <span :class="$style.label"><slot></slot></span>
-</div>
-</template>
-
-<script lang="ts" setup generic="T extends OptionValue | null">
-import { computed } from 'vue';
-import type { OptionValue } from '@/types/option-value.js';
-
-const props = defineProps<{
- modelValue: T;
- value: T;
- disabled?: boolean;
-}>();
-
-const emit = defineEmits<{
- (ev: 'update:modelValue', value: T): void;
-}>();
-
-const checked = computed(() => props.modelValue === props.value);
-
-function toggle(): void {
- if (props.disabled) return;
- emit('update:modelValue', props.value);
-}
-</script>
-
-<style lang="scss" module>
-.root {
- position: relative;
- display: inline-flex;
- align-items: center;
- text-align: left;
- cursor: pointer;
- padding: 8px 10px;
- min-width: 60px;
- background-color: var(--MI_THEME-panel);
- background-clip: padding-box !important;
- border: solid 1px var(--MI_THEME-panel);
- border-radius: 6px;
- font-size: 90%;
- transition: all 0.2s;
- user-select: none;
-
- &.disabled {
- opacity: 0.6;
- cursor: not-allowed !important;
- }
-
- &:hover {
- border-color: var(--MI_THEME-inputBorderHover) !important;
- }
-
- &:focus-within {
- outline: none;
- box-shadow: 0 0 0 2px var(--MI_THEME-focus);
- }
-
- &.checked {
- background-color: var(--MI_THEME-accentedBg) !important;
- border-color: var(--MI_THEME-accentedBg) !important;
- color: var(--MI_THEME-accent);
- cursor: default !important;
-
- > .button {
- border-color: var(--MI_THEME-accent);
-
- &::after {
- background-color: var(--MI_THEME-accent);
- transform: scale(1);
- opacity: 1;
- }
- }
- }
-}
-
-.input {
- position: absolute;
- width: 0;
- height: 0;
- opacity: 0;
- margin: 0;
-}
-
-.button {
- position: relative;
- display: inline-block;
- width: 14px;
- height: 14px;
- background: none;
- border: solid 2px var(--MI_THEME-inputBorder);
- border-radius: 100%;
- transition: inherit;
-
- &::after {
- content: '';
- display: block;
- position: absolute;
- top: 3px;
- right: 3px;
- bottom: 3px;
- left: 3px;
- border-radius: 100%;
- opacity: 0;
- transform: scale(0);
- transition: 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
- }
-}
-
-.label {
- margin-left: 8px;
- display: block;
- cursor: pointer;
-}
-</style>
diff --git a/packages/frontend/src/components/MkRadios.vue b/packages/frontend/src/components/MkRadios.vue
index 43957a0673..e2210e858e 100644
--- a/packages/frontend/src/components/MkRadios.vue
+++ b/packages/frontend/src/components/MkRadios.vue
@@ -8,15 +8,27 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.label">
<slot name="label"></slot>
</div>
+
<div :class="$style.body">
- <MkRadio
+ <div
v-for="option in options"
:key="getKey(option.value)"
- v-model="model"
- :disabled="option.disabled"
- :value="option.value"
+ v-adaptive-border
+ :class="[$style.optionRoot, { [$style.disabled]: option.disabled, [$style.checked]: model === option.value }]"
+ :aria-checked="model === option.value"
+ :aria-disabled="option.disabled"
+ role="checkbox"
+ @click="toggle(option)"
>
- <div :class="[$style.optionContent, { [$style.checked]: model === option.value }]">
+ <input
+ type="radio"
+ :disabled="option.disabled"
+ :class="$style.optionInput"
+ >
+ <span :class="$style.optionButton">
+ <span></span>
+ </span>
+ <div :class="$style.optionContent">
<i v-if="option.icon" :class="[$style.optionIcon, option.icon]" :style="option.iconStyle"></i>
<div>
<slot v-if="option.slotId != null" :name="`option-${option.slotId as SlotNames}`"></slot>
@@ -26,8 +38,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
</div>
</div>
- </MkRadio>
+ </div>
</div>
+
<div :class="$style.caption">
<slot name="caption"></slot>
</div>
@@ -51,8 +64,6 @@ export type MkRadiosOption<T = OptionValue, S = string> = {
</script>
<script setup lang="ts" generic="const T extends MkRadiosOption">
-import MkRadio from './MkRadio.vue';
-
defineProps<{
options: T[];
vertical?: boolean;
@@ -61,18 +72,23 @@ defineProps<{
type SlotNames = NonNullable<T extends MkRadiosOption<any, infer U> ? U : never>;
defineSlots<{
- label?: () => any;
- caption?: () => any;
+ label?: () => void;
+ caption?: () => void;
} & {
- [K in `option-${SlotNames}`]: () => any;
+ [K in `option-${SlotNames}`]: () => void;
}>();
const model = defineModel<T['value']>({ required: true });
function getKey(value: OptionValue): PropertyKey {
- if (value === null) return 'null';
+ if (value === null) return '___null___';
return value;
}
+
+function toggle(o: MkRadiosOption): void {
+ if (o.disabled) return;
+ model.value = o.value;
+}
</script>
<style lang="scss" module>
@@ -102,29 +118,110 @@ function getKey(value: OptionValue): PropertyKey {
}
}
+.vertical > .body {
+ flex-direction: column;
+}
+
+.optionRoot {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ text-align: left;
+ cursor: pointer;
+ padding: 8px 10px;
+ min-width: 60px;
+ background-color: var(--MI_THEME-panel);
+ background-clip: padding-box !important;
+ border: solid 1px var(--MI_THEME-panel);
+ border-radius: 6px;
+ font-size: 90%;
+ transition: all 0.2s;
+ user-select: none;
+
+ &.disabled {
+ opacity: 0.6;
+ cursor: not-allowed !important;
+ }
+
+ &:hover {
+ border-color: var(--MI_THEME-inputBorderHover) !important;
+ }
+
+ &:focus-within {
+ outline: none;
+ box-shadow: 0 0 0 2px var(--MI_THEME-focus);
+ }
+
+ &.checked {
+ background-color: var(--MI_THEME-accentedBg) !important;
+ border-color: var(--MI_THEME-accentedBg) !important;
+ color: var(--MI_THEME-accent);
+ cursor: default !important;
+
+ .optionButton {
+ border-color: var(--MI_THEME-accent);
+
+ &::after {
+ background-color: var(--MI_THEME-accent);
+ transform: scale(1);
+ opacity: 1;
+ }
+ }
+
+ .optionCaption {
+ color: color(from var(--MI_THEME-accent) srgb r g b / 0.75);
+ }
+ }
+}
+
+.optionInput {
+ position: absolute;
+ width: 0;
+ height: 0;
+ opacity: 0;
+ margin: 0;
+}
+
+.optionButton {
+ position: relative;
+ display: inline-block;
+ width: 14px;
+ height: 14px;
+ background: none;
+ border: solid 2px var(--MI_THEME-inputBorder);
+ border-radius: 100%;
+ transition: inherit;
+
+ &::after {
+ content: '';
+ display: block;
+ position: absolute;
+ top: 3px;
+ right: 3px;
+ bottom: 3px;
+ left: 3px;
+ border-radius: 100%;
+ opacity: 0;
+ transform: scale(0);
+ transition: 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
+ }
+}
+
.optionContent {
display: flex;
align-items: center;
gap: 6px;
+ margin-left: 8px;
}
.optionCaption {
font-size: 0.85em;
padding: 2px 0 0 0;
color: color(from var(--MI_THEME-fg) srgb r g b / 0.75);
-}
-
-.optionContent.checked {
- .optionCaption {
- color: color(from var(--MI_THEME-accent) srgb r g b / 0.75);
- }
+ transition: all 0.2s;
}
.optionIcon {
flex-shrink: 0;
}
-
-.vertical > .body {
- flex-direction: column;
-}
</style>
diff --git a/packages/frontend/src/pages/admin/ads.vue b/packages/frontend/src/pages/admin/ads.vue
index e06ea50453..0efd1a2e28 100644
--- a/packages/frontend/src/pages/admin/ads.vue
+++ b/packages/frontend/src/pages/admin/ads.vue
@@ -33,15 +33,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>Form</template>
</MkRadios>
- <!--
- <div style="margin: 32px 0;">
- {{ i18n.ts.priority }}
- <MkRadio v-model="ad.priority" value="high">{{ i18n.ts.high }}</MkRadio>
- <MkRadio v-model="ad.priority" value="middle">{{ i18n.ts.middle }}</MkRadio>
- <MkRadio v-model="ad.priority" value="low">{{ i18n.ts.low }}</MkRadio>
- </div>
- -->
-
<FormSplit>
<MkInput v-model="ad.ratio" type="number">
<template #label>{{ i18n.ts.ratio }}</template>