Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@
"dependencies": {
"@rei/cdr-tokens": "^12.7.0",
"@vueuse/core": "^12.7.0",
"radix-vue": "^1.9.16",
"radix-vue": "^1.9.17",
"tabbable": "^4.0.0"
},
"peerDependencies": {
Expand Down
40 changes: 21 additions & 19 deletions src/components/filmstrip/CdrFilmstrip.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
>
<!-- @slot Optional injection of a heading element for the filmstrip -->
<slot name="heading" />
<CdrFilmstripEngine
<CdrSurfaceScroll
:class="classAttr"
:id="filmstripId"
:description="description"
Expand All @@ -25,30 +25,32 @@
v-bind="frameProps"
/>
</template>
</CdrFilmstripEngine>
</CdrSurfaceScroll>
</div>
</template>

<script setup lang="ts">
import CdrFilmstripEngine from './CdrFilmstripEngine.vue';
import CdrSurfaceScroll from '../surfaceScroll/CdrSurfaceScroll.vue';
import { useResizeObserver, useDebounceFn } from '@vueuse/core';
import type {
CdrFilmstripFrame,
CdrFilmstripArrowClickPayload,
CdrFilmstripResizePayload,
CdrFilmstripEventEmitter,
CdrFilmstripConfig,
CdrFilmstrip,
CdrFilmstripScrollPayload,
CdrFilmstripAdapter,
} from './interfaces';
import type {
CdrSurfaceScrollFrame,
CdrSurfaceScrollArrowClickPayload,
CdrSurfaceScrollPayload,
} from '../surfaceScroll/interfaces';
import { computed, h, provide, ref, useAttrs, useId, watch } from 'vue';
import { CdrFilmstripEventKey } from '../../types/symbols';

/**
* Responsive, accessible filmstrip for displaying a horizontal list of content frames.
*
* @uses CdrFilmstripEngine
* @uses CdrSurfaceScroll
*/
defineOptions({ name: 'CdrFilmstrip' });

Expand All @@ -70,7 +72,7 @@ const props = withDefaults(defineProps<CdrFilmstrip<unknown>>(), {
* @default defaultAdapter
*/
adapter: (): CdrFilmstripAdapter<Record<string, unknown>> => {
return (_modelData: unknown): CdrFilmstripConfig<Record<string, unknown>> => {
return (): CdrFilmstripConfig<Record<string, unknown>> => {
console.warn(`No adapter provided for CdrFilmstrip`);
return {
frames: [],
Expand All @@ -87,13 +89,13 @@ const emit = defineEmits<{
* Emitted when a user clicks the navigation arrows.
* @param payload - The arrow click event metadata.
*/
(e: 'arrowClick', payload: CdrFilmstripArrowClickPayload): void;
(e: 'arrowClick', payload: CdrSurfaceScrollArrowClickPayload): void;

/**
* Emitted when the filmstrip scrolls to a new frame.
* @param payload - The scroll event metadata including target index.
*/
(e: 'scrollNavigate', payload: CdrFilmstripScrollPayload): void;
(e: 'scrollNavigate', payload: CdrSurfaceScrollPayload): void;

/**
* Emitted when the layout changes due to screen or container resize.
Expand Down Expand Up @@ -128,7 +130,7 @@ const emitEvent: CdrFilmstripEventEmitter = (eventName, payload) => {
const attrs = useAttrs();
/**
* Extracts the class attribute from the component's attributes.
* This class is applied to the CdrFilmstripEngine for styling purposes.
* This class is applied to the CdrSurfaceScroll for styling purposes.
*/
const classAttr = attrs.class || '';

Expand Down Expand Up @@ -167,9 +169,9 @@ const framesToScroll = ref<number>(framesToShow.value);
/**
* Extracts frames from the resolved filmstrip model.
*
* @returns {CdrFilmstripFrame<never>[]} An array of frames to be rendered by the filmstrip engine.
* @returns {CdrSurfaceScrollFrame<never>[]} An array of frames to be rendered by the filmstrip engine.
*/
const frames = computed(() => filmstripConfig.value.frames as CdrFilmstripFrame<never>[]);
const frames = computed(() => filmstripConfig.value.frames as CdrSurfaceScrollFrame<never>[]);

/**
* Checks if the filmstrip has any frames to display.
Expand Down Expand Up @@ -233,10 +235,10 @@ const dataAttributes = computed(() => filmstripConfig.value?.dataAttributes || {
* Handles arrow click events for navigating through the filmstrip.
* Constructs an arrow click payload and emits the 'arrowClick' event.
*
* @param {CdrFilmstripArrowClickPayload} param0 - The arrow click event payload.
* @param {CdrSurfaceScrollArrowClickPayload} param0 - The arrow click event payload.
*/
function onArrowClick({ event, direction }: CdrFilmstripArrowClickPayload) {
const arrowClickPayload: CdrFilmstripArrowClickPayload = {
function onArrowClick({ event, direction }: CdrSurfaceScrollArrowClickPayload) {
const arrowClickPayload: CdrSurfaceScrollArrowClickPayload = {
event,
direction,
model: props.model as Record<string, unknown>,
Expand All @@ -248,10 +250,10 @@ function onArrowClick({ event, direction }: CdrFilmstripArrowClickPayload) {
* Handles scroll navigation events in the filmstrip.
* Constructs a scroll payload and emits the 'scrollNavigate' event.
*
* @param {CdrFilmstripScrollPayload} param0 - The scroll event payload.
* @param {CdrSurfaceScrollPayload} param0 - The scroll event payload.
*/
function onScrollNavigate({ index, event }: CdrFilmstripScrollPayload): void {
const scrollPayload: CdrFilmstripScrollPayload = {
function onScrollNavigate({ index, event }: CdrSurfaceScrollPayload): void {
const scrollPayload: CdrSurfaceScrollPayload = {
event,
index,
model: props.model as Record<string, unknown>,
Expand Down
30 changes: 16 additions & 14 deletions src/components/filmstrip/__tests__/CdrFilmstrip.spec.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { mount, VueWrapper } from '@vue/test-utils';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import CdrFilmstrip from '../CdrFilmstrip.vue';
import CdrFilmstripEngine from '../CdrFilmstripEngine.vue';
import CdrSurfaceScroll from '../../surfaceScroll/CdrSurfaceScroll.vue';
import { h, nextTick } from 'vue';
import type {
CdrFilmstripFrame,
CdrFilmstripConfig,
CdrFilmstripArrowClickPayload,
CdrFilmstripResizePayload,
} from '../interfaces';
import type {
CdrSurfaceScrollFrame,
CdrSurfaceScrollArrowClickPayload,
} from '../../surfaceScroll/interfaces';

describe('CdrFilmstrip.vue', () => {
const sampleFrames: CdrFilmstripFrame[] = [
const sampleFrames: CdrSurfaceScrollFrame[] = [
{ key: 'frame-1', props: { text: 'Frame 1' } },
{ key: 'frame-2', props: { text: 'Frame 2' } },
{ key: 'frame-3', props: { text: 'Frame 3' } },
Expand Down Expand Up @@ -49,8 +51,8 @@ describe('CdrFilmstrip.vue', () => {
// βœ… 1. Basic Rendering
it('renders correctly when frames exist', async () => {
await nextTick();
expect(wrapper.findComponent(CdrFilmstripEngine).exists()).toBe(true);
expect(wrapper.find('[data-ui="cdr-filmstrip__frames"]').exists()).toBe(true);
expect(wrapper.findComponent(CdrSurfaceScroll).exists()).toBe(true);
expect(wrapper.find('[data-ui="cdr-surface-scroll__frames"]').exists()).toBe(true);
});

it('does not render when there are no frames', async () => {
Expand All @@ -69,13 +71,13 @@ describe('CdrFilmstrip.vue', () => {
});

await nextTick();
expect(wrapper.findComponent(CdrFilmstripEngine).exists()).toBe(false);
expect(wrapper.findComponent(CdrSurfaceScroll).exists()).toBe(false);
});

// βœ… 2. Prop forwarding
it('passes the correct props to CdrFilmstripEngine', async () => {
it('passes the correct props to CdrSurfaceScroll', async () => {
await nextTick();
const engine = wrapper.findComponent(CdrFilmstripEngine);
const engine = wrapper.findComponent(CdrSurfaceScroll);

expect(engine.props('id')).toMatch(/^test-filmstrip/);
expect(engine.props('description')).toBe('Test filmstrip description');
Expand All @@ -86,21 +88,21 @@ describe('CdrFilmstrip.vue', () => {
// βœ… 3. Event Bubbling
it('bubbles up ariaMessage event to the parent', async () => {
const message = 'Now showing frames 1-3';
await wrapper.findComponent(CdrFilmstripEngine).vm.$emit('ariaMessage', message);
await wrapper.findComponent(CdrSurfaceScroll).vm.$emit('ariaMessage', message);
await nextTick();

expect(wrapper.emitted('ariaMessage')).toBeTruthy();
expect(wrapper.emitted('ariaMessage')?.[0]).toEqual([message]);
});

it('bubbles up arrowClick event with the correct payload', async () => {
const arrowEvent: CdrFilmstripArrowClickPayload = {
const arrowEvent: CdrSurfaceScrollArrowClickPayload = {
event: new Event('click'),
direction: 'right',
model: {},
};

await wrapper.findComponent(CdrFilmstripEngine).vm.$emit('arrowClick', arrowEvent);
await wrapper.findComponent(CdrSurfaceScroll).vm.$emit('arrowClick', arrowEvent);
await nextTick();

expect(wrapper.emitted('arrowClick')).toBeTruthy();
Expand Down Expand Up @@ -156,7 +158,7 @@ describe('CdrFilmstrip.vue', () => {

await nextTick();

expect(wrapper.findComponent(CdrFilmstripEngine).exists()).toBe(false);
expect(wrapper.findComponent(CdrSurfaceScroll).exists()).toBe(false);

// βœ… Instead of checking <div>, check the `hasFilmstripFrames` computed value
expect(wrapper.vm.hasFilmstripFrames).toBe(false);
Expand All @@ -181,6 +183,6 @@ describe('CdrFilmstrip.vue', () => {
});

await nextTick();
expect(wrapper.findComponent(CdrFilmstripEngine).exists()).toBe(false);
expect(wrapper.findComponent(CdrSurfaceScroll).exists()).toBe(false);
});
});
7 changes: 4 additions & 3 deletions src/components/filmstrip/examples/Lifestyle/adapter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { CdrSpaceThreeQuarterX } from '@rei/cdr-tokens';
import type { Lifestyle, LifestyleFrameExtended } from '.';
import type { CdrFilmstripAdapter, CdrFilmstripConfig, CdrFilmstripFrame } from '../../interfaces';
import type { CdrFilmstripAdapter, CdrFilmstripConfig } from '../../interfaces';
import type { CdrSurfaceScrollFrame } from '../../../surfaceScroll/interfaces';
import FrameComponent from './LifestyleFrame.vue';

export const adapter: CdrFilmstripAdapter<LifestyleFrameExtended> = (modelData) => {
Expand All @@ -21,9 +22,9 @@ export const adapter: CdrFilmstripAdapter<LifestyleFrameExtended> = (modelData)
/**
* Transforms raw items into an array of frames for the filmstrip.
*
* @type {CdrFilmstripFrame<LifestyleFrameExtended>[]}
* @type {CdrSurfaceScrollFrame<LifestyleFrameExtended>[]}
*/
const frames: CdrFilmstripFrame<LifestyleFrameExtended>[] = Array.isArray(frameItems)
const frames: CdrSurfaceScrollFrame<LifestyleFrameExtended>[] = Array.isArray(frameItems)
? frameItems.map((frame, index) => ({
key: `lifestyle-frame-${index}`,
props: {
Expand Down
5 changes: 3 additions & 2 deletions src/components/filmstrip/examples/Lifestyle/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { CdrFilmstripArrowClickPayload, CdrFilmstripResizePayload } from '../../interfaces';
import type { CdrFilmstripResizePayload } from '../../interfaces';
import type { CdrSurfaceScrollArrowClickPayload } from '../../../surfaceScroll/interfaces';
import type { Lifestyle, LifestyleFrameClickPayload } from '.';
import { CdrBreakpointLg, CdrBreakpointMd } from '@rei/cdr-tokens';

Expand Down Expand Up @@ -27,7 +28,7 @@ export function onFrameClick(payload: unknown): void {
* @return {void}
*/
export function onArrowClick(payload: unknown): void {
const { direction, event, model = {} } = payload as CdrFilmstripArrowClickPayload;
const { direction, event, model = {} } = payload as CdrSurfaceScrollArrowClickPayload;
const { framesVisible, frameStyle } = model as Partial<Lifestyle>;

const scrollDirection = direction === 'right' ? 'forwardScroll' : 'backScroll';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { CdrFilmstripAdapter, CdrFilmstripConfig, CdrFilmstripFrame } from '../../interfaces';
import type { CdrFilmstripAdapter, CdrFilmstripConfig } from '../../interfaces';
import type { CdrSurfaceScrollFrame } from '../../../surfaceScroll/interfaces';
import FrameComponent from './components/ProductRecommendationFrame.vue';
import type { ProductRecommendation, ProductRecommendationFrame } from '.';

Expand All @@ -7,7 +8,7 @@ import type { ProductRecommendation, ProductRecommendationFrame } from '.';
*
* This function takes `modelData`, extracts relevant product information,
* and transforms it into a structured filmstrip model that conforms to the
* `CdrFilmstripConfig<CdrFilmstripFrame>` format.
* `CdrFilmstripConfig<CdrSurfaceScrollFrame>` format.
*
* @param {Record<string, unknown>} modelData - The raw data used to populate the filmstrip.
* @returns {CdrFilmstripConfig<ProductRecommendationFrame>} The resolved product filmstrip model.
Expand All @@ -23,9 +24,9 @@ export const adapter: CdrFilmstripAdapter<ProductRecommendationFrame> = (modelDa
/**
* Transforms raw items into an array of frames for the filmstrip.
*
* @type {CdrFilmstripFrame<ProductRecommendationFrame>[]}
* @type {CdrSurfaceScrollFrame<ProductRecommendationFrame>[]}
*/
const frames: CdrFilmstripFrame<ProductRecommendationFrame>[] = Array.isArray(items)
const frames: CdrSurfaceScrollFrame<ProductRecommendationFrame>[] = Array.isArray(items)
? items.map((item, index) => ({
key: `product-frame-${index}`,
props: item,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CdrFilmstripArrowClickPayload, CdrFilmstripScrollPayload } from '../../interfaces';
import type { CdrSurfaceScrollArrowClickPayload, CdrSurfaceScrollPayload } from '../../../surfaceScroll/interfaces';

import type { ProductRecommendation, ProductRecommendationFrameClickPayload } from '.';

Expand Down Expand Up @@ -27,7 +27,7 @@ export function onFrameClick(payload: unknown): void {
* @param {unknown} payload - The event payload containing navigation details.
*/
export function onArrowClick(payload: unknown): void {
const { direction, event, model = {} } = payload as CdrFilmstripArrowClickPayload;
const { direction, event, model = {} } = payload as CdrSurfaceScrollArrowClickPayload;
const { placementName, strategy } = model as Partial<ProductRecommendation>;

const scrollDirection = direction === 'right' ? 'forwardScroll' : 'backScroll';
Expand All @@ -51,7 +51,7 @@ export function onArrowClick(payload: unknown): void {
* @param {unknown} payload - The event payload containing scroll details.
*/
export function onScrollNavigate(payload: unknown): void {
const { index, event, model = {} } = payload as CdrFilmstripScrollPayload;
const { index, event, model = {} } = payload as CdrSurfaceScrollPayload;
const { placementName, strategy } = model as Partial<ProductRecommendation>;

const format = (str?: string): string | undefined => str?.replace(/_/g, '-');
Expand Down
Loading