diff --git a/packages/frontend/src/components/global/MkAcct.stories.impl.ts b/packages/frontend/src/components/global/MkAcct.stories.impl.ts index d5e3fc3568..68202bb705 100644 --- a/packages/frontend/src/components/global/MkAcct.stories.impl.ts +++ b/packages/frontend/src/components/global/MkAcct.stories.impl.ts @@ -41,3 +41,19 @@ export const Detail = { detail: true, }, } satisfies StoryObj<typeof MkAcct>; +export const Long = { + ...Default, + args: { + ...Default.args, + user: { + ...userDetailed(), + username: '2c7cc62a697ea3a7826521f3fd34f0cb273693cbe5e9310f35449f43622a5cdc', + host: 'nostr.example', + }, + }, + decorators: [ + () => ({ + template: '<div style="width: 360px;"><story/></div>', + }), + ], +} satisfies StoryObj<typeof MkAcct>; diff --git a/packages/frontend/src/components/global/MkAcct.vue b/packages/frontend/src/components/global/MkAcct.vue index 2b9f892fc6..8a93a5adf7 100644 --- a/packages/frontend/src/components/global/MkAcct.vue +++ b/packages/frontend/src/components/global/MkAcct.vue @@ -1,13 +1,14 @@ <template> -<span> +<MkCondensedLine> <span>@{{ user.username }}</span> <span v-if="user.host || detail || defaultStore.state.showFullAcct" style="opacity: 0.5;">@{{ user.host || host }}</span> -</span> +</MkCondensedLine> </template> <script lang="ts" setup> import * as misskey from 'misskey-js'; import { toUnicode } from 'punycode/'; +import MkCondensedLine from './MkCondensedLine.vue'; import { host as hostRaw } from '@/config'; import { defaultStore } from '@/store'; diff --git a/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts b/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts new file mode 100644 index 0000000000..ce985bc59f --- /dev/null +++ b/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts @@ -0,0 +1,39 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import MkCondensedLine from './MkCondensedLine.vue'; +export const Default = { + render(args) { + return { + components: { + MkCondensedLine, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '<MkCondensedLine>{{ props.text }}</MkCondensedLine>', + }; + }, + args: { + text: 'This is a condensed line.', + }, + parameters: { + layout: 'centered', + }, +} satisfies StoryObj<typeof MkCondensedLine>; +export const ContainerIs100px = { + ...Default, + decorators: [ + () => ({ + template: '<div style="width: 100px;"><story/></div>', + }), + ], +} satisfies StoryObj<typeof MkCondensedLine>; diff --git a/packages/frontend/src/components/global/MkCondensedLine.vue b/packages/frontend/src/components/global/MkCondensedLine.vue new file mode 100644 index 0000000000..e3c0a866b7 --- /dev/null +++ b/packages/frontend/src/components/global/MkCondensedLine.vue @@ -0,0 +1,56 @@ +<template> +<span :class="$style.container"> + <span ref="content" :class="$style.content"> + <slot/> + </span> +</span> +</template> + +<script lang="ts"> +const contentSymbol = Symbol(); +const observer = new ResizeObserver((entries) => { + for (const entry of entries) { + const content = (entry.target[contentSymbol] ? entry.target : entry.target.firstElementChild) as HTMLSpanElement; + const container = content.parentElement as HTMLSpanElement; + const contentWidth = content.getBoundingClientRect().width; + const containerWidth = container.getBoundingClientRect().width; + container.style.transform = `scaleX(${Math.min(1, containerWidth / contentWidth)})`; + } +}); +</script> + +<script setup lang="ts"> +import { ref, watch } from 'vue'; + +const content = ref<HTMLSpanElement>(); + +watch(content, (value, oldValue) => { + if (oldValue) { + delete oldValue[contentSymbol]; + observer.unobserve(oldValue); + if (oldValue.parentElement) { + observer.unobserve(oldValue.parentElement); + } + } + if (value) { + value[contentSymbol] = contentSymbol; + observer.observe(value); + if (value.parentElement) { + observer.observe(value.parentElement); + } + } +}); +</script> + +<style module lang="scss"> +.container { + display: inline-block; + width: 100%; + transform-origin: 0; +} + +.content { + display: inline-block; + white-space: nowrap; +} +</style> diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts index 63e8fc225c..4ef8111da9 100644 --- a/packages/frontend/src/components/index.ts +++ b/packages/frontend/src/components/index.ts @@ -5,6 +5,7 @@ import MkA from './global/MkA.vue'; import MkAcct from './global/MkAcct.vue'; import MkAvatar from './global/MkAvatar.vue'; import MkEmoji from './global/MkEmoji.vue'; +import MkCondensedLine from './global/MkCondensedLine.vue'; import MkCustomEmoji from './global/MkCustomEmoji.vue'; import MkUserName from './global/MkUserName.vue'; import MkEllipsis from './global/MkEllipsis.vue'; @@ -33,6 +34,7 @@ export const components = { MkAcct: MkAcct, MkAvatar: MkAvatar, MkEmoji: MkEmoji, + MkCondensedLine: MkCondensedLine, MkCustomEmoji: MkCustomEmoji, MkUserName: MkUserName, MkEllipsis: MkEllipsis, @@ -55,6 +57,7 @@ declare module '@vue/runtime-core' { MkAcct: typeof MkAcct; MkAvatar: typeof MkAvatar; MkEmoji: typeof MkEmoji; + MkCondensedLine: typeof MkCondensedLine; MkCustomEmoji: typeof MkCustomEmoji; MkUserName: typeof MkUserName; MkEllipsis: typeof MkEllipsis;