summaryrefslogtreecommitdiff
path: root/packages/frontend/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'packages/frontend/src/components')
-rw-r--r--packages/frontend/src/components/MkAbuseReport.stories.impl.ts49
-rw-r--r--packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts49
-rw-r--r--packages/frontend/src/components/MkAccountMoved.stories.impl.ts33
-rw-r--r--packages/frontend/src/components/MkAccountMoved.vue4
-rw-r--r--packages/frontend/src/components/MkAchievements.stories.impl.ts56
-rw-r--r--packages/frontend/src/components/MkAnalogClock.stories.impl.ts9
-rw-r--r--packages/frontend/src/components/MkAnalogClock.vue12
-rw-r--r--packages/frontend/src/components/MkAsUi.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkAutocomplete.stories.impl.ts176
-rw-r--r--packages/frontend/src/components/MkAvatars.stories.impl.ts46
-rw-r--r--packages/frontend/src/components/MkButton.stories.impl.ts53
-rw-r--r--packages/frontend/src/components/global/MkError.stories.impl.ts10
-rw-r--r--packages/frontend/src/components/global/MkTime.vue3
13 files changed, 494 insertions, 8 deletions
diff --git a/packages/frontend/src/components/MkAbuseReport.stories.impl.ts b/packages/frontend/src/components/MkAbuseReport.stories.impl.ts
new file mode 100644
index 0000000000..7d27adeb04
--- /dev/null
+++ b/packages/frontend/src/components/MkAbuseReport.stories.impl.ts
@@ -0,0 +1,49 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { action } from '@storybook/addon-actions';
+import { StoryObj } from '@storybook/vue3';
+import { rest } from 'msw';
+import { abuseUserReport } from '../../.storybook/fakes';
+import { commonHandlers } from '../../.storybook/mocks';
+import MkAbuseReport from './MkAbuseReport.vue';
+export const Default = {
+ render(args) {
+ return {
+ components: {
+ MkAbuseReport,
+ },
+ setup() {
+ return {
+ args,
+ };
+ },
+ computed: {
+ props() {
+ return {
+ ...this.args,
+ };
+ },
+ events() {
+ return {
+ resolved: action('resolved'),
+ };
+ },
+ },
+ template: '<MkAbuseReport v-bind="props" v-on="events" />',
+ };
+ },
+ args: {
+ report: abuseUserReport(),
+ },
+ parameters: {
+ layout: 'fullscreen',
+ msw: {
+ handlers: [
+ ...commonHandlers,
+ rest.post('/api/admin/resolve-abuse-user-report', async (req, res, ctx) => {
+ action('POST /api/admin/resolve-abuse-user-report')(await req.json());
+ return res(ctx.json({}));
+ }),
+ ],
+ },
+ },
+} satisfies StoryObj<typeof MkAbuseReport>;
diff --git a/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts
new file mode 100644
index 0000000000..d0877ffd3b
--- /dev/null
+++ b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts
@@ -0,0 +1,49 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { action } from '@storybook/addon-actions';
+import { StoryObj } from '@storybook/vue3';
+import { rest } from 'msw';
+import { userDetailed } from '../../.storybook/fakes';
+import { commonHandlers } from '../../.storybook/mocks';
+import MkAbuseReportWindow from './MkAbuseReportWindow.vue';
+export const Default = {
+ render(args) {
+ return {
+ components: {
+ MkAbuseReportWindow,
+ },
+ setup() {
+ return {
+ args,
+ };
+ },
+ computed: {
+ props() {
+ return {
+ ...this.args,
+ };
+ },
+ events() {
+ return {
+ 'closed': action('closed'),
+ };
+ },
+ },
+ template: '<MkAbuseReportWindow v-bind="props" v-on="events" />',
+ };
+ },
+ args: {
+ user: userDetailed(),
+ },
+ parameters: {
+ layout: 'centered',
+ msw: {
+ handlers: [
+ ...commonHandlers,
+ rest.post('/api/users/report-abuse', async (req, res, ctx) => {
+ action('POST /api/users/report-abuse')(await req.json());
+ return res(ctx.json({}));
+ }),
+ ],
+ },
+ },
+} satisfies StoryObj<typeof MkAbuseReportWindow>;
diff --git a/packages/frontend/src/components/MkAccountMoved.stories.impl.ts b/packages/frontend/src/components/MkAccountMoved.stories.impl.ts
new file mode 100644
index 0000000000..bed9d94311
--- /dev/null
+++ b/packages/frontend/src/components/MkAccountMoved.stories.impl.ts
@@ -0,0 +1,33 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { StoryObj } from '@storybook/vue3';
+import { userDetailed } from '../../.storybook/fakes';
+import MkAccountMoved from './MkAccountMoved.vue';
+export const Default = {
+ render(args) {
+ return {
+ components: {
+ MkAccountMoved,
+ },
+ setup() {
+ return {
+ args,
+ };
+ },
+ computed: {
+ props() {
+ return {
+ ...this.args,
+ };
+ },
+ },
+ template: '<MkAccountMoved v-bind="props" />',
+ };
+ },
+ args: {
+ username: userDetailed().username,
+ host: userDetailed().host,
+ },
+ parameters: {
+ layout: 'centered',
+ },
+} satisfies StoryObj<typeof MkAccountMoved>;
diff --git a/packages/frontend/src/components/MkAccountMoved.vue b/packages/frontend/src/components/MkAccountMoved.vue
index fd472de6c1..98979de236 100644
--- a/packages/frontend/src/components/MkAccountMoved.vue
+++ b/packages/frontend/src/components/MkAccountMoved.vue
@@ -2,7 +2,7 @@
<div :class="$style.root">
<i class="ti ti-plane-departure" style="margin-right: 8px;"></i>
{{ i18n.ts.accountMoved }}
- <MkMention :class="$style.link" :username="acct" :host="host ?? localHost"/>
+ <MkMention :class="$style.link" :username="username" :host="host ?? localHost"/>
</div>
</template>
@@ -12,7 +12,7 @@ import { i18n } from '@/i18n';
import { host as localHost } from '@/config';
defineProps<{
- acct: string;
+ username: string;
host: string;
}>();
</script>
diff --git a/packages/frontend/src/components/MkAchievements.stories.impl.ts b/packages/frontend/src/components/MkAchievements.stories.impl.ts
new file mode 100644
index 0000000000..477152a47b
--- /dev/null
+++ b/packages/frontend/src/components/MkAchievements.stories.impl.ts
@@ -0,0 +1,56 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { StoryObj } from '@storybook/vue3';
+import { rest } from 'msw';
+import { userDetailed } from '../../.storybook/fakes';
+import { commonHandlers } from '../../.storybook/mocks';
+import MkAchievements from './MkAchievements.vue';
+import { ACHIEVEMENT_TYPES } from '@/scripts/achievements';
+export const Empty = {
+ render(args) {
+ return {
+ components: {
+ MkAchievements,
+ },
+ setup() {
+ return {
+ args,
+ };
+ },
+ computed: {
+ props() {
+ return {
+ ...this.args,
+ };
+ },
+ },
+ template: '<MkAchievements v-bind="props" />',
+ };
+ },
+ args: {
+ user: userDetailed(),
+ },
+ parameters: {
+ layout: 'fullscreen',
+ msw: {
+ handlers: [
+ ...commonHandlers,
+ rest.post('/api/users/achievements', (req, res, ctx) => {
+ return res(ctx.json([]));
+ }),
+ ],
+ },
+ },
+} satisfies StoryObj<typeof MkAchievements>;
+export const All = {
+ ...Empty,
+ parameters: {
+ msw: {
+ handlers: [
+ ...commonHandlers,
+ rest.post('/api/users/achievements', (req, res, ctx) => {
+ return res(ctx.json(ACHIEVEMENT_TYPES.map((name) => ({ name, unlockedAt: 0 }))));
+ }),
+ ],
+ },
+ },
+} satisfies StoryObj<typeof MkAchievements>;
diff --git a/packages/frontend/src/components/MkAnalogClock.stories.impl.ts b/packages/frontend/src/components/MkAnalogClock.stories.impl.ts
index 05190aa268..e7fbb47284 100644
--- a/packages/frontend/src/components/MkAnalogClock.stories.impl.ts
+++ b/packages/frontend/src/components/MkAnalogClock.stories.impl.ts
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3';
import MkAnalogClock from './MkAnalogClock.vue';
+import isChromatic from 'chromatic';
export const Default = {
render(args) {
return {
@@ -22,6 +23,14 @@ export const Default = {
template: '<MkAnalogClock v-bind="props" />',
};
},
+ args: {
+ now: isChromatic() ? () => new Date('2023-01-01T10:10:30') : undefined,
+ },
+ decorators: [
+ () => ({
+ template: '<div style="container-type:inline-size;height:100%"><div style="height:100cqmin;margin:auto;width:100cqmin"><story/></div></div>',
+ }),
+ ],
parameters: {
layout: 'fullscreen',
},
diff --git a/packages/frontend/src/components/MkAnalogClock.vue b/packages/frontend/src/components/MkAnalogClock.vue
index 1218202616..f12020f810 100644
--- a/packages/frontend/src/components/MkAnalogClock.vue
+++ b/packages/frontend/src/components/MkAnalogClock.vue
@@ -99,6 +99,7 @@ const props = withDefaults(defineProps<{
graduations?: 'none' | 'dots' | 'numbers';
fadeGraduations?: boolean;
sAnimation?: 'none' | 'elastic' | 'easeOut';
+ now?: () => Date;
}>(), {
numbers: false,
thickness: 0.1,
@@ -107,6 +108,7 @@ const props = withDefaults(defineProps<{
graduations: 'dots',
fadeGraduations: true,
sAnimation: 'elastic',
+ now: () => new Date(),
});
const graduationsMajor = computed(() => {
@@ -145,11 +147,17 @@ let disableSAnimate = $ref(false);
let sOneRound = false;
function tick() {
- const now = new Date();
- now.setMinutes(now.getMinutes() + (new Date().getTimezoneOffset() + props.offset));
+ const now = props.now();
+ now.setMinutes(now.getMinutes() + now.getTimezoneOffset() + props.offset);
+ const previousS = s;
+ const previousM = m;
+ const previousH = h;
s = now.getSeconds();
m = now.getMinutes();
h = now.getHours();
+ if (previousS === s && previousM === m && previousH === h) {
+ return;
+ }
hAngle = Math.PI * (h % (props.twentyfour ? 24 : 12) + (m + s / 60) / 60) / (props.twentyfour ? 12 : 6);
mAngle = Math.PI * (m + s / 60) / 30;
if (sOneRound) { // 秒針が一周した際のアニメーションをよしなに処理する(これが無いと秒が59->0になったときに期待したアニメーションにならない)
diff --git a/packages/frontend/src/components/MkAsUi.stories.impl.ts b/packages/frontend/src/components/MkAsUi.stories.impl.ts
new file mode 100644
index 0000000000..b67c0e679d
--- /dev/null
+++ b/packages/frontend/src/components/MkAsUi.stories.impl.ts
@@ -0,0 +1,2 @@
+import MkAsUi from './MkAsUi.vue';
+void MkAsUi;
diff --git a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts
new file mode 100644
index 0000000000..075904d6a3
--- /dev/null
+++ b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts
@@ -0,0 +1,176 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { action } from '@storybook/addon-actions';
+import { expect } from '@storybook/jest';
+import { userEvent, waitFor, within } from '@storybook/testing-library';
+import { StoryObj } from '@storybook/vue3';
+import { rest } from 'msw';
+import { userDetailed } from '../../.storybook/fakes';
+import { commonHandlers } from '../../.storybook/mocks';
+import MkAutocomplete from './MkAutocomplete.vue';
+import MkInput from './MkInput.vue';
+import { tick } from '@/scripts/test-utils';
+const common = {
+ render(args) {
+ return {
+ components: {
+ MkAutocomplete,
+ },
+ setup() {
+ return {
+ args,
+ };
+ },
+ computed: {
+ props() {
+ return {
+ ...this.args,
+ };
+ },
+ events() {
+ return {
+ open: action('open'),
+ closed: action('closed'),
+ };
+ },
+ },
+ template: '<MkAutocomplete v-bind="props" v-on="events" :textarea="textarea" />',
+ };
+ },
+ args: {
+ close: action('close'),
+ x: 0,
+ y: 0,
+ },
+ decorators: [
+ (_, context) => ({
+ components: {
+ MkInput,
+ },
+ data() {
+ return {
+ q: context.args.q,
+ textarea: null,
+ };
+ },
+ methods: {
+ inputMounted() {
+ this.textarea = this.$refs.input.$refs.inputEl;
+ },
+ },
+ template: '<MkInput v-model="q" ref="input" @vue:mounted="inputMounted"/><story v-if="textarea" :q="q" :textarea="textarea"/>',
+ }),
+ ],
+ parameters: {
+ controls: {
+ exclude: ['textarea'],
+ },
+ layout: 'centered',
+ chromatic: {
+ // FIXME: flaky
+ disableSnapshot: true,
+ },
+ },
+} satisfies StoryObj<typeof MkAutocomplete>;
+export const User = {
+ ...common,
+ args: {
+ ...common.args,
+ type: 'user',
+ },
+ async play({ canvasElement }) {
+ const canvas = within(canvasElement);
+ const input = canvas.getByRole('combobox');
+ await waitFor(() => userEvent.hover(input));
+ await waitFor(() => userEvent.click(input));
+ await waitFor(() => userEvent.type(input, 'm'));
+ await waitFor(async () => {
+ await userEvent.type(input, ' ', { delay: 256 });
+ await tick();
+ return await expect(canvas.getByRole('list')).toBeInTheDocument();
+ }, { timeout: 16384 });
+ },
+ parameters: {
+ ...common.parameters,
+ msw: {
+ handlers: [
+ ...commonHandlers,
+ rest.post('/api/users/search-by-username-and-host', (req, res, ctx) => {
+ return res(ctx.json([
+ userDetailed('44', 'mizuki', 'misskey-hub.net', 'Mizuki'),
+ userDetailed('49', 'momoko', 'misskey-hub.net', 'Momoko'),
+ ]));
+ }),
+ ],
+ },
+ },
+};
+export const Hashtag = {
+ ...common,
+ args: {
+ ...common.args,
+ type: 'hashtag',
+ },
+ async play({ canvasElement }) {
+ const canvas = within(canvasElement);
+ const input = canvas.getByRole('combobox');
+ await waitFor(() => userEvent.hover(input));
+ await waitFor(() => userEvent.click(input));
+ await waitFor(() => userEvent.type(input, '気象'));
+ await waitFor(async () => {
+ await userEvent.type(input, ' ', { delay: 256 });
+ await tick();
+ return await expect(canvas.getByRole('list')).toBeInTheDocument();
+ }, { interval: 256, timeout: 16384 });
+ },
+ parameters: {
+ ...common.parameters,
+ msw: {
+ handlers: [
+ ...commonHandlers,
+ rest.post('/api/hashtags/search', (req, res, ctx) => {
+ return res(ctx.json([
+ '気象警報注意報',
+ '気象警報',
+ '気象情報',
+ ]));
+ }),
+ ],
+ },
+ },
+};
+export const Emoji = {
+ ...common,
+ args: {
+ ...common.args,
+ type: 'emoji',
+ },
+ async play({ canvasElement }) {
+ const canvas = within(canvasElement);
+ const input = canvas.getByRole('combobox');
+ await waitFor(() => userEvent.hover(input));
+ await waitFor(() => userEvent.click(input));
+ await waitFor(() => userEvent.type(input, 'smile'));
+ await waitFor(async () => {
+ await userEvent.type(input, ' ', { delay: 256 });
+ await tick();
+ return await expect(canvas.getByRole('list')).toBeInTheDocument();
+ }, { interval: 256, timeout: 16384 });
+ },
+} satisfies StoryObj<typeof MkAutocomplete>;
+export const MfmTag = {
+ ...common,
+ args: {
+ ...common.args,
+ type: 'mfmTag',
+ },
+ async play({ canvasElement }) {
+ const canvas = within(canvasElement);
+ const input = canvas.getByRole('combobox');
+ await waitFor(() => userEvent.hover(input));
+ await waitFor(() => userEvent.click(input));
+ await waitFor(async () => {
+ await tick();
+ return await expect(canvas.getByRole('list')).toBeInTheDocument();
+ }, { interval: 256, timeout: 16384 });
+ },
+} satisfies StoryObj<typeof MkAutocomplete>;
diff --git a/packages/frontend/src/components/MkAvatars.stories.impl.ts b/packages/frontend/src/components/MkAvatars.stories.impl.ts
new file mode 100644
index 0000000000..14052c7343
--- /dev/null
+++ b/packages/frontend/src/components/MkAvatars.stories.impl.ts
@@ -0,0 +1,46 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { StoryObj } from '@storybook/vue3';
+import { rest } from 'msw';
+import { userDetailed } from '../../.storybook/fakes';
+import { commonHandlers } from '../../.storybook/mocks';
+import MkAvatars from './MkAvatars.vue';
+export const Default = {
+ render(args) {
+ return {
+ components: {
+ MkAvatars,
+ },
+ setup() {
+ return {
+ args,
+ };
+ },
+ computed: {
+ props() {
+ return {
+ ...this.args,
+ };
+ },
+ },
+ template: '<MkAvatars v-bind="props" />',
+ };
+ },
+ args: {
+ userIds: ['17', '20', '18'],
+ },
+ parameters: {
+ layout: 'centered',
+ msw: {
+ handlers: [
+ ...commonHandlers,
+ rest.post('/api/users/show', (req, res, ctx) => {
+ return res(ctx.json([
+ userDetailed('17'),
+ userDetailed('20'),
+ userDetailed('18'),
+ ]));
+ }),
+ ],
+ },
+ },
+} satisfies StoryObj<typeof MkAvatars>;
diff --git a/packages/frontend/src/components/MkButton.stories.impl.ts b/packages/frontend/src/components/MkButton.stories.impl.ts
index e1c1c54d10..982a8b3be1 100644
--- a/packages/frontend/src/components/MkButton.stories.impl.ts
+++ b/packages/frontend/src/components/MkButton.stories.impl.ts
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-/* eslint-disable import/no-duplicates */
+import { action } from '@storybook/addon-actions';
import { StoryObj } from '@storybook/vue3';
import MkButton from './MkButton.vue';
export const Default = {
@@ -20,11 +20,60 @@ export const Default = {
...this.args,
};
},
+ events() {
+ return {
+ click: action('click'),
+ };
+ },
},
- template: '<MkButton v-bind="props">Text</MkButton>',
+ template: '<MkButton v-bind="props" v-on="events">Text</MkButton>',
};
},
+ args: {
+ },
parameters: {
layout: 'centered',
},
} satisfies StoryObj<typeof MkButton>;
+export const Primary = {
+ ...Default,
+ args: {
+ ...Default.args,
+ primary: true,
+ },
+} satisfies StoryObj<typeof MkButton>;
+export const Gradate = {
+ ...Default,
+ args: {
+ ...Default.args,
+ gradate: true,
+ },
+} satisfies StoryObj<typeof MkButton>;
+export const Rounded = {
+ ...Default,
+ args: {
+ ...Default.args,
+ rounded: true,
+ },
+} satisfies StoryObj<typeof MkButton>;
+export const Danger = {
+ ...Default,
+ args: {
+ ...Default.args,
+ danger: true,
+ },
+} satisfies StoryObj<typeof MkButton>;
+export const Small = {
+ ...Default,
+ args: {
+ ...Default.args,
+ small: true,
+ },
+} satisfies StoryObj<typeof MkButton>;
+export const Large = {
+ ...Default,
+ args: {
+ ...Default.args,
+ large: true,
+ },
+} satisfies StoryObj<typeof MkButton>;
diff --git a/packages/frontend/src/components/global/MkError.stories.impl.ts b/packages/frontend/src/components/global/MkError.stories.impl.ts
index 60ac5c91ad..8252a4d76e 100644
--- a/packages/frontend/src/components/global/MkError.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkError.stories.impl.ts
@@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { action } from '@storybook/addon-actions';
import { expect } from '@storybook/jest';
import { waitFor } from '@storybook/testing-library';
import { StoryObj } from '@storybook/vue3';
@@ -20,14 +21,21 @@ export const Default = {
...this.args,
};
},
+ events() {
+ return {
+ retry: action('retry'),
+ };
+ },
},
- template: '<MkError v-bind="props" />',
+ template: '<MkError v-bind="props" v-on="events" />',
};
},
async play({ canvasElement }) {
await expect(canvasElement.firstElementChild).not.toBeNull();
await waitFor(async () => expect(canvasElement.firstElementChild?.classList).not.toContain('_transition_zoom-enter-active'));
},
+ args: {
+ },
parameters: {
layout: 'centered',
},
diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue
index 99169512db..261cc0ee18 100644
--- a/packages/frontend/src/components/global/MkTime.vue
+++ b/packages/frontend/src/components/global/MkTime.vue
@@ -8,6 +8,7 @@
</template>
<script lang="ts" setup>
+import isChromatic from 'chromatic/isChromatic';
import { onUnmounted } from 'vue';
import { i18n } from '@/i18n';
import { dateTimeFormat } from '@/scripts/intl-const';
@@ -17,7 +18,7 @@ const props = withDefaults(defineProps<{
origin?: Date | null;
mode?: 'relative' | 'absolute' | 'detail';
}>(), {
- origin: null,
+ origin: isChromatic() ? new Date('2023-04-01T00:00:00Z') : null,
mode: 'relative',
});