Skip to content

Commit a8a0933

Browse files
authored
Merge pull request #216 from rei/pr/surface-scroll
Add surface scroll
2 parents 02eb840 + 83cbfe6 commit a8a0933

14 files changed

Lines changed: 192 additions & 181 deletions

File tree

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@
133133
"dependencies": {
134134
"@rei/cdr-tokens": "^12.7.0",
135135
"@vueuse/core": "^12.7.0",
136-
"radix-vue": "^1.9.16",
136+
"radix-vue": "^1.9.17",
137137
"tabbable": "^4.0.0"
138138
},
139139
"peerDependencies": {

src/components/filmstrip/CdrFilmstrip.vue

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
>
77
<!-- @slot Optional injection of a heading element for the filmstrip -->
88
<slot name="heading" />
9-
<CdrFilmstripEngine
9+
<CdrSurfaceScroll
1010
:class="classAttr"
1111
:id="filmstripId"
1212
:description="description"
@@ -25,30 +25,32 @@
2525
v-bind="frameProps"
2626
/>
2727
</template>
28-
</CdrFilmstripEngine>
28+
</CdrSurfaceScroll>
2929
</div>
3030
</template>
3131

3232
<script setup lang="ts">
33-
import CdrFilmstripEngine from './CdrFilmstripEngine.vue';
33+
import CdrSurfaceScroll from '../surfaceScroll/CdrSurfaceScroll.vue';
3434
import { useResizeObserver, useDebounceFn } from '@vueuse/core';
3535
import type {
36-
CdrFilmstripFrame,
37-
CdrFilmstripArrowClickPayload,
3836
CdrFilmstripResizePayload,
3937
CdrFilmstripEventEmitter,
4038
CdrFilmstripConfig,
4139
CdrFilmstrip,
42-
CdrFilmstripScrollPayload,
4340
CdrFilmstripAdapter,
4441
} from './interfaces';
42+
import type {
43+
CdrSurfaceScrollFrame,
44+
CdrSurfaceScrollArrowClickPayload,
45+
CdrSurfaceScrollPayload,
46+
} from '../surfaceScroll/interfaces';
4547
import { computed, h, provide, ref, useAttrs, useId, watch } from 'vue';
4648
import { CdrFilmstripEventKey } from '../../types/symbols';
4749
4850
/**
4951
* Responsive, accessible filmstrip for displaying a horizontal list of content frames.
5052
*
51-
* @uses CdrFilmstripEngine
53+
* @uses CdrSurfaceScroll
5254
*/
5355
defineOptions({ name: 'CdrFilmstrip' });
5456
@@ -70,7 +72,7 @@ const props = withDefaults(defineProps<CdrFilmstrip<unknown>>(), {
7072
* @default defaultAdapter
7173
*/
7274
adapter: (): CdrFilmstripAdapter<Record<string, unknown>> => {
73-
return (_modelData: unknown): CdrFilmstripConfig<Record<string, unknown>> => {
75+
return (): CdrFilmstripConfig<Record<string, unknown>> => {
7476
console.warn(`No adapter provided for CdrFilmstrip`);
7577
return {
7678
frames: [],
@@ -87,13 +89,13 @@ const emit = defineEmits<{
8789
* Emitted when a user clicks the navigation arrows.
8890
* @param payload - The arrow click event metadata.
8991
*/
90-
(e: 'arrowClick', payload: CdrFilmstripArrowClickPayload): void;
92+
(e: 'arrowClick', payload: CdrSurfaceScrollArrowClickPayload): void;
9193
9294
/**
9395
* Emitted when the filmstrip scrolls to a new frame.
9496
* @param payload - The scroll event metadata including target index.
9597
*/
96-
(e: 'scrollNavigate', payload: CdrFilmstripScrollPayload): void;
98+
(e: 'scrollNavigate', payload: CdrSurfaceScrollPayload): void;
9799
98100
/**
99101
* Emitted when the layout changes due to screen or container resize.
@@ -128,7 +130,7 @@ const emitEvent: CdrFilmstripEventEmitter = (eventName, payload) => {
128130
const attrs = useAttrs();
129131
/**
130132
* Extracts the class attribute from the component's attributes.
131-
* This class is applied to the CdrFilmstripEngine for styling purposes.
133+
* This class is applied to the CdrSurfaceScroll for styling purposes.
132134
*/
133135
const classAttr = attrs.class || '';
134136
@@ -167,9 +169,9 @@ const framesToScroll = ref<number>(framesToShow.value);
167169
/**
168170
* Extracts frames from the resolved filmstrip model.
169171
*
170-
* @returns {CdrFilmstripFrame<never>[]} An array of frames to be rendered by the filmstrip engine.
172+
* @returns {CdrSurfaceScrollFrame<never>[]} An array of frames to be rendered by the filmstrip engine.
171173
*/
172-
const frames = computed(() => filmstripConfig.value.frames as CdrFilmstripFrame<never>[]);
174+
const frames = computed(() => filmstripConfig.value.frames as CdrSurfaceScrollFrame<never>[]);
173175
174176
/**
175177
* Checks if the filmstrip has any frames to display.
@@ -233,10 +235,10 @@ const dataAttributes = computed(() => filmstripConfig.value?.dataAttributes || {
233235
* Handles arrow click events for navigating through the filmstrip.
234236
* Constructs an arrow click payload and emits the 'arrowClick' event.
235237
*
236-
* @param {CdrFilmstripArrowClickPayload} param0 - The arrow click event payload.
238+
* @param {CdrSurfaceScrollArrowClickPayload} param0 - The arrow click event payload.
237239
*/
238-
function onArrowClick({ event, direction }: CdrFilmstripArrowClickPayload) {
239-
const arrowClickPayload: CdrFilmstripArrowClickPayload = {
240+
function onArrowClick({ event, direction }: CdrSurfaceScrollArrowClickPayload) {
241+
const arrowClickPayload: CdrSurfaceScrollArrowClickPayload = {
240242
event,
241243
direction,
242244
model: props.model as Record<string, unknown>,
@@ -248,10 +250,10 @@ function onArrowClick({ event, direction }: CdrFilmstripArrowClickPayload) {
248250
* Handles scroll navigation events in the filmstrip.
249251
* Constructs a scroll payload and emits the 'scrollNavigate' event.
250252
*
251-
* @param {CdrFilmstripScrollPayload} param0 - The scroll event payload.
253+
* @param {CdrSurfaceScrollPayload} param0 - The scroll event payload.
252254
*/
253-
function onScrollNavigate({ index, event }: CdrFilmstripScrollPayload): void {
254-
const scrollPayload: CdrFilmstripScrollPayload = {
255+
function onScrollNavigate({ index, event }: CdrSurfaceScrollPayload): void {
256+
const scrollPayload: CdrSurfaceScrollPayload = {
255257
event,
256258
index,
257259
model: props.model as Record<string, unknown>,

src/components/filmstrip/__tests__/CdrFilmstrip.spec.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import { mount, VueWrapper } from '@vue/test-utils';
22
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
33
import CdrFilmstrip from '../CdrFilmstrip.vue';
4-
import CdrFilmstripEngine from '../CdrFilmstripEngine.vue';
4+
import CdrSurfaceScroll from '../../surfaceScroll/CdrSurfaceScroll.vue';
55
import { h, nextTick } from 'vue';
66
import type {
7-
CdrFilmstripFrame,
87
CdrFilmstripConfig,
9-
CdrFilmstripArrowClickPayload,
108
CdrFilmstripResizePayload,
119
} from '../interfaces';
10+
import type {
11+
CdrSurfaceScrollFrame,
12+
CdrSurfaceScrollArrowClickPayload,
13+
} from '../../surfaceScroll/interfaces';
1214

1315
describe('CdrFilmstrip.vue', () => {
14-
const sampleFrames: CdrFilmstripFrame[] = [
16+
const sampleFrames: CdrSurfaceScrollFrame[] = [
1517
{ key: 'frame-1', props: { text: 'Frame 1' } },
1618
{ key: 'frame-2', props: { text: 'Frame 2' } },
1719
{ key: 'frame-3', props: { text: 'Frame 3' } },
@@ -49,8 +51,8 @@ describe('CdrFilmstrip.vue', () => {
4951
// ✅ 1. Basic Rendering
5052
it('renders correctly when frames exist', async () => {
5153
await nextTick();
52-
expect(wrapper.findComponent(CdrFilmstripEngine).exists()).toBe(true);
53-
expect(wrapper.find('[data-ui="cdr-filmstrip__frames"]').exists()).toBe(true);
54+
expect(wrapper.findComponent(CdrSurfaceScroll).exists()).toBe(true);
55+
expect(wrapper.find('[data-ui="cdr-surface-scroll__frames"]').exists()).toBe(true);
5456
});
5557

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

7173
await nextTick();
72-
expect(wrapper.findComponent(CdrFilmstripEngine).exists()).toBe(false);
74+
expect(wrapper.findComponent(CdrSurfaceScroll).exists()).toBe(false);
7375
});
7476

7577
// ✅ 2. Prop forwarding
76-
it('passes the correct props to CdrFilmstripEngine', async () => {
78+
it('passes the correct props to CdrSurfaceScroll', async () => {
7779
await nextTick();
78-
const engine = wrapper.findComponent(CdrFilmstripEngine);
80+
const engine = wrapper.findComponent(CdrSurfaceScroll);
7981

8082
expect(engine.props('id')).toMatch(/^test-filmstrip/);
8183
expect(engine.props('description')).toBe('Test filmstrip description');
@@ -86,21 +88,21 @@ describe('CdrFilmstrip.vue', () => {
8688
// ✅ 3. Event Bubbling
8789
it('bubbles up ariaMessage event to the parent', async () => {
8890
const message = 'Now showing frames 1-3';
89-
await wrapper.findComponent(CdrFilmstripEngine).vm.$emit('ariaMessage', message);
91+
await wrapper.findComponent(CdrSurfaceScroll).vm.$emit('ariaMessage', message);
9092
await nextTick();
9193

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

9698
it('bubbles up arrowClick event with the correct payload', async () => {
97-
const arrowEvent: CdrFilmstripArrowClickPayload = {
99+
const arrowEvent: CdrSurfaceScrollArrowClickPayload = {
98100
event: new Event('click'),
99101
direction: 'right',
100102
model: {},
101103
};
102104

103-
await wrapper.findComponent(CdrFilmstripEngine).vm.$emit('arrowClick', arrowEvent);
105+
await wrapper.findComponent(CdrSurfaceScroll).vm.$emit('arrowClick', arrowEvent);
104106
await nextTick();
105107

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

157159
await nextTick();
158160

159-
expect(wrapper.findComponent(CdrFilmstripEngine).exists()).toBe(false);
161+
expect(wrapper.findComponent(CdrSurfaceScroll).exists()).toBe(false);
160162

161163
// ✅ Instead of checking <div>, check the `hasFilmstripFrames` computed value
162164
expect(wrapper.vm.hasFilmstripFrames).toBe(false);
@@ -181,6 +183,6 @@ describe('CdrFilmstrip.vue', () => {
181183
});
182184

183185
await nextTick();
184-
expect(wrapper.findComponent(CdrFilmstripEngine).exists()).toBe(false);
186+
expect(wrapper.findComponent(CdrSurfaceScroll).exists()).toBe(false);
185187
});
186188
});

src/components/filmstrip/examples/Lifestyle/adapter.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { CdrSpaceThreeQuarterX } from '@rei/cdr-tokens';
22
import type { Lifestyle, LifestyleFrameExtended } from '.';
3-
import type { CdrFilmstripAdapter, CdrFilmstripConfig, CdrFilmstripFrame } from '../../interfaces';
3+
import type { CdrFilmstripAdapter, CdrFilmstripConfig } from '../../interfaces';
4+
import type { CdrSurfaceScrollFrame } from '../../../surfaceScroll/interfaces';
45
import FrameComponent from './LifestyleFrame.vue';
56

67
export const adapter: CdrFilmstripAdapter<LifestyleFrameExtended> = (modelData) => {
@@ -21,9 +22,9 @@ export const adapter: CdrFilmstripAdapter<LifestyleFrameExtended> = (modelData)
2122
/**
2223
* Transforms raw items into an array of frames for the filmstrip.
2324
*
24-
* @type {CdrFilmstripFrame<LifestyleFrameExtended>[]}
25+
* @type {CdrSurfaceScrollFrame<LifestyleFrameExtended>[]}
2526
*/
26-
const frames: CdrFilmstripFrame<LifestyleFrameExtended>[] = Array.isArray(frameItems)
27+
const frames: CdrSurfaceScrollFrame<LifestyleFrameExtended>[] = Array.isArray(frameItems)
2728
? frameItems.map((frame, index) => ({
2829
key: `lifestyle-frame-${index}`,
2930
props: {

src/components/filmstrip/examples/Lifestyle/handlers.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type { CdrFilmstripArrowClickPayload, CdrFilmstripResizePayload } from '../../interfaces';
1+
import type { CdrFilmstripResizePayload } from '../../interfaces';
2+
import type { CdrSurfaceScrollArrowClickPayload } from '../../../surfaceScroll/interfaces';
23
import type { Lifestyle, LifestyleFrameClickPayload } from '.';
34
import { CdrBreakpointLg, CdrBreakpointMd } from '@rei/cdr-tokens';
45

@@ -27,7 +28,7 @@ export function onFrameClick(payload: unknown): void {
2728
* @return {void}
2829
*/
2930
export function onArrowClick(payload: unknown): void {
30-
const { direction, event, model = {} } = payload as CdrFilmstripArrowClickPayload;
31+
const { direction, event, model = {} } = payload as CdrSurfaceScrollArrowClickPayload;
3132
const { framesVisible, frameStyle } = model as Partial<Lifestyle>;
3233

3334
const scrollDirection = direction === 'right' ? 'forwardScroll' : 'backScroll';

src/components/filmstrip/examples/ProductRecommendation/adapter.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type { CdrFilmstripAdapter, CdrFilmstripConfig, CdrFilmstripFrame } from '../../interfaces';
1+
import type { CdrFilmstripAdapter, CdrFilmstripConfig } from '../../interfaces';
2+
import type { CdrSurfaceScrollFrame } from '../../../surfaceScroll/interfaces';
23
import FrameComponent from './components/ProductRecommendationFrame.vue';
34
import type { ProductRecommendation, ProductRecommendationFrame } from '.';
45

@@ -7,7 +8,7 @@ import type { ProductRecommendation, ProductRecommendationFrame } from '.';
78
*
89
* This function takes `modelData`, extracts relevant product information,
910
* and transforms it into a structured filmstrip model that conforms to the
10-
* `CdrFilmstripConfig<CdrFilmstripFrame>` format.
11+
* `CdrFilmstripConfig<CdrSurfaceScrollFrame>` format.
1112
*
1213
* @param {Record<string, unknown>} modelData - The raw data used to populate the filmstrip.
1314
* @returns {CdrFilmstripConfig<ProductRecommendationFrame>} The resolved product filmstrip model.
@@ -23,9 +24,9 @@ export const adapter: CdrFilmstripAdapter<ProductRecommendationFrame> = (modelDa
2324
/**
2425
* Transforms raw items into an array of frames for the filmstrip.
2526
*
26-
* @type {CdrFilmstripFrame<ProductRecommendationFrame>[]}
27+
* @type {CdrSurfaceScrollFrame<ProductRecommendationFrame>[]}
2728
*/
28-
const frames: CdrFilmstripFrame<ProductRecommendationFrame>[] = Array.isArray(items)
29+
const frames: CdrSurfaceScrollFrame<ProductRecommendationFrame>[] = Array.isArray(items)
2930
? items.map((item, index) => ({
3031
key: `product-frame-${index}`,
3132
props: item,

src/components/filmstrip/examples/ProductRecommendation/handlers.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { CdrFilmstripArrowClickPayload, CdrFilmstripScrollPayload } from '../../interfaces';
1+
import type { CdrSurfaceScrollArrowClickPayload, CdrSurfaceScrollPayload } from '../../../surfaceScroll/interfaces';
22

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

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

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

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

0 commit comments

Comments
 (0)