Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions client/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ const actions = ['Save changes', 'Duplicate', 'Archive']

<template>
<main class="app-shell">
<section class="hero" aria-labelledby="app-title">
<section class="hero" aria-labelledby="app-name">
<p class="eyebrow">1Monthon 2026 · Team 11</p>
<h1 id="app-title">Frontend is ready.</h1>
<h1 id="app-name">Frontend is ready.</h1>
<p class="description">
Vue と Rust server をつなぐための、シンプルな開発環境をセットアップしました。
</p>
Expand Down
27 changes: 27 additions & 0 deletions client/src/RoomCard.story.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script setup lang="ts">
import RoomCard from './RoomCard.vue'

const roomFixture = {
id: 'room-01',
number: 1,
title: 'Room 01',
genre: 'General',
description: 'A cozy room for general discussions.',
}
</script>

<template>
<Story title="RoomCard">
<Variant title="Closed">
<div style="width: 24rem; padding: 2rem">
<RoomCard :room="roomFixture" />
</div>
</Variant>

<Variant title="Open">
<div style="width: 24rem; padding: 2rem">
<RoomCard :room="roomFixture" default-open />
</div>
</Variant>
</Story>
</template>
176 changes: 176 additions & 0 deletions client/src/RoomCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
<script setup lang="ts">
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'

export interface Room {
id: string
number: number
title: string
genre: string
description: string
}

withDefaults(
defineProps<{
room: Room
defaultOpen?: boolean
starting?: boolean
}>(),
{
defaultOpen: false,
starting: false,
},
)

const emit = defineEmits<{
start: [room: Room]
}>()
Comment thread
sahara2006 marked this conversation as resolved.
</script>

<template>
<Disclosure v-slot="{ open }" as="article" class="room-card" :default-open="defaultOpen">
<DisclosureButton class="room-card__header" type="button">
<span class="room-card__number" aria-hidden="true">{{ room.number }}</span>
<span class="room-card__heading">
<span class="room-card__genre">{{ room.genre }}</span>
<span class="room-card__title">{{ room.title }}</span>
</span>
<span
class="material-symbols-outlined room-card__toggle"
Comment thread
sahara2006 marked this conversation as resolved.
Outdated
aria-hidden="true"
:data-open="open"
>
<img
src="@/assets/icon/keyboard_arrow_down_24dp_1F1F1F_FILL0_wght400_GRAD0_opsz24.svg"
alt="Toggle"
/>
Comment thread
sahara2006 marked this conversation as resolved.
</span>
</DisclosureButton>

<DisclosurePanel class="room-card__body">
<p class="room-card__description">{{ room.description }}</p>
<button
class="room-card__start"
type="button"
:disabled="starting"
@click="emit('start', room)"
>
{{ starting ? '開始しています…' : '開始する' }}
</button>
</DisclosurePanel>
</Disclosure>
</template>

<style scoped>
.room-card {
width: 100%;
overflow: hidden;
border: 1px solid rgb(255 255 255 / 12%);
border-radius: 1.25rem;
background: rgba(15, 28, 36, 0.76);
box-shadow: 0 0.25rem 1.75rem rgb(0 0 0 / 28%);
backdrop-filter: blur(1rem);
}

.room-card__header {
display: flex;
width: 100%;
align-items: center;
justify-content: flex-start;
gap: 1rem;
padding: 1.25rem 1.5rem;
border: 0;
color: inherit;
background: transparent;
cursor: pointer;
text-align: left;
}

.room-card__heading {
display: grid;
flex: 1;
gap: 0.2rem;
}

.room-card__genre {
color: #5eead4;
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}

.room-card__header:hover {
background: rgb(255 255 255 / 4%);
}

.room-card__header:focus-visible {
outline: 2px solid #5eead4;
outline-offset: -2px;
}

.room-card__title {
font-size: 2rem;
font-weight: 700;
letter-spacing: -0.01em;
}

.room-card__toggle {
color: #aeb7d1;
font-size: 1.25rem;
font-variation-settings:
'FILL' 0,
'wght' 400,
'GRAD' 0,
'opsz' 24;
line-height: 1;
transition: transform 160ms ease;
}

.room-card__toggle[data-open='true'] {
transform: rotate(180deg);
}

.room-card__body {
display: grid;
gap: 1.25rem;
padding: 1.25rem 1.5rem 1.5rem;
border-top: 1px solid rgb(255 255 255 / 8%);
}

.room-card__description {
margin: 0;
color: #aeb7d1;
line-height: 1.7;
}

.room-card__start {
justify-self: end;
padding: 0.7rem 1.1rem;
border: 0;
border-radius: 0.7rem;
color: #07111f;
background: #5eead4;
font-weight: 700;
cursor: pointer;
}

.room-card__start:hover:not(:disabled) {
background: #99f6e4;
}

.room-card__start:focus-visible {
outline: 2px solid #c7d2fe;
outline-offset: 3px;
}

.room-card__start:disabled {
cursor: wait;
opacity: 0.6;
}

.room-card__number {
font-size: 3rem;
font-weight: 700;
letter-spacing: -0.01em;
}
</style>
40 changes: 40 additions & 0 deletions client/src/__tests__/RoomCard.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { describe, expect, it } from 'vitest'
import { mount } from '@vue/test-utils'

import RoomCard from '../RoomCard.vue'

const room = {
id: 'room-01',
number: 1,
title: 'Room 01',
genre: 'General',
description: 'A cozy room for general discussions.',
}
Comment thread
sahara2006 marked this conversation as resolved.

describe('RoomCard', () => {
it('renders the collapsed card shell', () => {
const wrapper = mount(RoomCard, { props: { room } })

expect(wrapper.get('article').classes()).toContain('room-card')
expect(wrapper.get('.room-card__header').text()).toContain('Room 01')
expect(wrapper.find('.room-card__body').exists()).toBe(false)
Comment thread
sahara2006 marked this conversation as resolved.
})

it('opens and closes its content from the header button', async () => {
const wrapper = mount(RoomCard, { props: { room } })

await wrapper.get('.room-card__header').trigger('click')
expect(wrapper.get('.room-card__description').text()).toBe(room.description)

await wrapper.get('.room-card__header').trigger('click')
expect(wrapper.find('.room-card__body').exists()).toBe(false)
})

it('emits the room from the start button', async () => {
const wrapper = mount(RoomCard, { props: { room, defaultOpen: true } })

await wrapper.get('.room-card__start').trigger('click')

expect(wrapper.emitted('start')).toEqual([[room]])
})
})
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions client/src/assets/main.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0&icon_names=keyboard_arrow_down');
Comment thread
sahara2006 marked this conversation as resolved.
Outdated
@import 'tailwindcss';
Comment thread
sahara2006 marked this conversation as resolved.
Outdated

:root {
Expand Down