Feed
읽던 위치를 유지하며 새 활동이나 이전 기록을 불러오는 제품 활동 목록을 제공합니다.
용법
유한 피드
하나의 릴리스에서 발생한 전체 활동 기록을 확인합니다.
<!-- Feed / Finite -->
<script setup lang="ts">
import { FeedItem, FeedRoot } from '@sectile/vue/feed'
const activities = [
{ id: 'deployed', title: 'Production deployment completed', summary: 'Release 2026.08 is healthy in all regions.', actor: 'Deploy bot', occurredAt: 'Just now' },
{ id: 'approved', title: 'Release approved', summary: 'Mina approved the production promotion.', actor: 'Mina Kim', occurredAt: '18 min ago' },
{ id: 'checks', title: 'Required checks passed', summary: 'All 12 release checks completed.', actor: 'CI', occurredAt: '24 min ago' },
] as const
const itemIDs = activities.map(({ id }) => id)
const getPosition = (id: string) => itemIDs.indexOf(id) + 1
</script>
<template>
<FeedRoot :items="itemIDs" :set-size="activities.length" :get-position="getPosition" label="Release activity">
<FeedItem v-for="event in activities" :key="event.id" :value="event.id" as="article">
<strong>{{ event.title }}</strong><p>{{ event.summary }}</p>
<small>{{ event.actor }} · {{ event.occurredAt }}</small>
</FeedItem>
</FeedRoot>
</template>예시
새 항목 불러오기
읽던 위치를 유지하면서 새 배포 활동을 불러옵니다.
<!-- Feed / Load After -->
<script setup lang="ts">
import { computed, ref } from 'vue'
import { FeedItem, FeedLoadNewer, FeedRoot } from '@sectile/vue/feed'
interface Activity { id: string; title: string; summary: string; actor: string; occurredAt: string }
interface ActivityWindow { items: Activity[]; revision: number }
const initialActivity: Activity[] = [
{ id: 'approved', title: 'Release approved', summary: 'Mina approved the production promotion.', actor: 'Mina Kim', occurredAt: '18 min ago' },
{ id: 'checks', title: 'Required checks passed', summary: 'All 12 release checks completed.', actor: 'CI', occurredAt: '24 min ago' },
]
const activities = ref<Activity[]>(initialActivity)
const revision = ref(20)
const availableUpdates = ref(2)
const itemIDs = computed(() => activities.value.map(({ id }) => id))
async function loadNewer(direction: 'before' | 'after', anchor: string | null, currentRevision: number) {
if (direction !== 'after') return
const query = new URLSearchParams({ after: anchor ?? '', revision: String(currentRevision) })
const response = await fetch(`/api/releases/2026.08/activity?${query}`)
if (!response.ok) throw new Error('Could not load recent activity')
const window = await response.json() as ActivityWindow
activities.value = [...window.items, ...activities.value]
revision.value = window.revision
availableUpdates.value = 0
}
</script>
<template>
<FeedRoot :items="itemIDs" :revision="revision" label="Live release activity" @request-window="loadNewer">
<FeedLoadNewer v-if="availableUpdates > 0" v-slot="{ pending }">
{{ pending === 'after' ? 'Checking for updates…' : `${availableUpdates} new updates` }}
</FeedLoadNewer>
<FeedItem v-for="event in activities" :key="event.id" :value="event.id" as="article">
<strong>{{ event.title }}</strong><p>{{ event.summary }}</p>
<small>{{ event.actor }} · {{ event.occurredAt }}</small>
</FeedItem>
</FeedRoot>
</template>이전 항목 불러오기
현재 활동 순서를 유지하면서 이전 릴리스 기록을 이어 붙입니다.
<!-- Feed / Load Before -->
<script setup lang="ts">
import { computed, ref } from 'vue'
import { FeedItem, FeedLoadEarlier, FeedRoot } from '@sectile/vue/feed'
interface Activity { id: string; title: string; summary: string; actor: string; occurredAt: string }
interface ActivityWindow { items: Activity[]; revision: number; hasEarlier: boolean; total: number }
const initialActivity: Activity[] = [
{ id: 'deployed', title: 'Production deployment completed', summary: 'Release 2026.08 is healthy in all regions.', actor: 'Deploy bot', occurredAt: 'Just now' },
{ id: 'approved', title: 'Release approved', summary: 'Mina approved the production promotion.', actor: 'Mina Kim', occurredAt: '18 min ago' },
]
const activities = ref<Activity[]>(initialActivity)
const revision = ref(12)
const hasEarlier = ref(true)
const total = ref(48)
const itemIDs = computed(() => activities.value.map(({ id }) => id))
async function loadEarlier(direction: 'before' | 'after', anchor: string | null, currentRevision: number) {
if (direction !== 'before' || !hasEarlier.value) return
const query = new URLSearchParams({ before: anchor ?? '', revision: String(currentRevision) })
const response = await fetch(`/api/releases/2026.08/activity?${query}`)
if (!response.ok) throw new Error('Could not load release history')
const window = await response.json() as ActivityWindow
activities.value = [...activities.value, ...window.items]
revision.value = window.revision
hasEarlier.value = window.hasEarlier
total.value = window.total
}
</script>
<template>
<FeedRoot :items="itemIDs" :revision="revision" :set-size="total" label="Release activity" @request-window="loadEarlier">
<FeedItem v-for="event in activities" :key="event.id" :value="event.id" as="article">
<strong>{{ event.title }}</strong><p>{{ event.summary }}</p>
<small>{{ event.actor }} · {{ event.occurredAt }}</small>
</FeedItem>
<FeedLoadEarlier v-if="hasEarlier" v-slot="{ pending }">
{{ pending === 'before' ? 'Loading history…' : 'Load earlier events' }}
</FeedLoadEarlier>
</FeedRoot>
</template>API
Vue 패키지: @sectile/vue/feed
FeedRootFeedItemFeedLoadEarlierFeedLoadNewer
Props
FeedRootProps
as이 파트가 렌더링할 요소 또는 컴포넌트입니다.
asChild하나뿐인 자식 요소에 파트 속성을 직접 합칠지 여부입니다.
defaultHighlightedValue컴포넌트가 관리하는 처음 강조 값입니다.
disabled사용자 조작을 막을지 여부입니다.
getPosition항목이 나타내는 수치 위치를 반환하는 함수입니다.
items컴포넌트가 관리할 순서 있는 항목 값입니다.
label보조 기술이 읽는 컨트롤 이름입니다.
requestGeneration완료할 구간 요청이 발급한 generation 토큰입니다.
revision파생 콘텐츠를 다시 계산할 때 사용할 애플리케이션 변경 차수입니다.
setSize현재 피드 구간이 나타내는 전체 항목 수입니다.
FeedPartProps
as이 파트가 렌더링할 요소 또는 컴포넌트입니다.
asChild하나뿐인 자식 요소에 파트 속성을 직접 합칠지 여부입니다.
슬롯
FeedRootSlotProps
disabled사용자 조작을 막을지 여부입니다.
highlightedValue조작 대상으로 강조된 현재 값입니다.
pending요청 처리 중인지 여부입니다.
requestGeneration현재 또는 가장 최근에 발급한 구간 요청의 generation입니다.
revision현재 상태 스냅샷의 변경 차수입니다.
FeedItemSlotProps
disabled사용자 조작을 막을지 여부입니다.
highlighted조작 대상으로 강조된 항목인지 여부입니다.
highlightedValue조작 대상으로 강조된 현재 값입니다.
pending요청 처리 중인지 여부입니다.
requestGeneration현재 또는 가장 최근에 발급한 구간 요청의 generation입니다.
revision현재 상태 스냅샷의 변경 차수입니다.
value이 계약이 노출하는 현재 값입니다.
이벤트
FeedRoot
highlight강조된 항목이 바뀔 때 발생합니다.
requestWindow피드가 현재 구간 밖의 항목을 요청할 때 발생합니다.
기타 타입
FeedPositionResolver
type FeedPositionResolver = NonNullable<FeedRootProps['getPosition']>FeedHighlightHandler
type FeedHighlightHandler = (value: string | null) => voidFeedRequestWindowHandler
type FeedRequestWindowHandler = (direction: FeedDirection, anchor: string | null, revision: number, requestGeneration: number) => voidFeedDirection
type FeedDirection = CollectionWindowDirection파트
공통 범위: [data-scope="feed"]. 컴포넌트 내부로 스타일을 제한할 때 파트 선택자와 함께 사용합니다.
| 파트 | 선택자 | 역할 | 추가 속성 |
|---|---|---|---|
root | [data-part="root"] | 컴포넌트 경계와 내부 파트를 묶습니다. | — |
item | [data-part="item"] | 선택하거나 실행할 수 있는 항목 하나입니다. | — |
load-earlier | [data-part="load-earlier"] | 현재 피드보다 이전 항목을 요청합니다. | — |
load-newer | [data-part="load-newer"] | 현재 피드보다 이후 항목을 요청합니다. | — |
키보드 동작
| 키 | 동작 |
|---|---|
| Arrow Down / Page Down | 다음 글로 이동합니다. |
| Arrow Up / Page Up | 이전 글로 이동합니다. |
| Tab | 현재 글 안의 상호작용 컨트롤로 이동합니다. |
접근성
루트는 피드 의미를 사용하며 각 항목은 선택적인 위치와 전체 크기 정보가 있는 글로 노출됩니다.
관련 WAI-ARIA 패턴에서 호스트 접근성 규칙을 확인할 수 있습니다.
