Skip to content

Commit bf23abd

Browse files
authored
Media Object (#192)
Creates MediaObject component using Layout component Lots of examples Some new supporting Sass mixins Some new supporting JS utilities Updated and pinned vue-tsc and typescript because of vuejs/language-tools#5018
1 parent eeb1f87 commit bf23abd

27 files changed

+3302
-1623
lines changed

package-lock.json

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

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,14 @@
107107
"stylelint-config-standard-scss": "^13.1.0",
108108
"stylelint-scss": "^6.8.1",
109109
"ts-node": "^10.9.1",
110-
"typescript": "^5.1.6",
110+
"typescript": "5.6.3",
111111
"vite": "^5.0.1",
112112
"vite-plugin-dts": "^3.3.1",
113113
"vitest": "^1.1.1",
114114
"vue-docgen-cli": "^4.79.0",
115115
"vue-eslint-parser": "^9.3.0",
116116
"vue-router": "^4.0.10",
117-
"vue-tsc": "^2.0.29"
117+
"vue-tsc": "2.1.10"
118118
},
119119
"engines": {
120120
"node": ">= 20.0.0",

src/components/examples.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import landingLead from 'componentsdir/landingLead/examples/LandingLead.vue';
2020
import layout from 'componentsdir/layout/examples/Layout.vue';
2121
import links from 'componentsdir/link/examples/Links.vue';
2222
import list from 'componentsdir/list/examples/Lists.vue';
23-
// import mediaObject from 'componentsdir/mediaObject/examples/MediaObject.vue';
23+
import mediaObject from 'componentsdir/mediaObject/examples/MediaObject.vue';
2424
import modal from 'componentsdir/modal/examples/Modal.vue';
2525
import pagination from 'componentsdir/pagination/examples/Pagination.vue';
2626
import picture from 'componentsdir/picture/examples/Picture.vue';
@@ -67,7 +67,7 @@ export default {
6767
layout,
6868
links,
6969
list,
70-
// mediaObject,
70+
mediaObject,
7171
modal,
7272
pagination,
7373
picture,

src/components/fulfillmentTile/CdrFulfillmentTileContent.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ const rootProps = computed(
1818
(): bodyTextProps => ({
1919
tag: 'div',
2020
scale: props.scale,
21-
class: [style[baseClass]],
2221
}),
2322
);
2423
</script>
2524

2625
<template>
27-
<CdrBody v-bind="rootProps">
26+
<CdrBody
27+
v-bind="rootProps"
28+
:class="[style[baseClass]]"
29+
>
2830
<!-- @slot Where all default content should be placed. -->
2931
<slot />
3032
</CdrBody>

src/components/fulfillmentTile/examples/FulfillmentTile.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ const toggleCheckbox2 = (value: number) => {
251251
class="example__search-tile-other-header-button"
252252
:checked="checkbox2.includes(1)"
253253
role="checkbox"
254-
:layout="{ flow: 'rows' }"
254+
:layout="{ flow: 'row' }"
255255
@click="toggleCheckbox2(1)"
256256
>
257257
<CdrFulfillmentTileHeader>

src/components/layout/CdrLayout.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ const rootProps = computed(() => {
7676

7777
<template>
7878
<component
79-
:is="as"
79+
:is="props.as"
8080
v-bind="rootProps"
8181
>
8282
<!-- @slot Where all default content should be placed. -->

src/components/layout/examples/Layout.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
import CdrLayout from '../CdrLayout.vue';
33
import CdrSurface from '../../surface/CdrSurface.vue';
44
import CdrText from '../../text/CdrText.vue';
5-
import type { Layout } from '../../../types/interfaces';
5+
import type { Layout, HtmlAttributes } from '../../../types/interfaces';
66
77
defineOptions({ name: 'Layout' });
88
99
interface LayoutExample {
10-
props: Layout;
10+
props: Layout | HtmlAttributes;
1111
children: number;
1212
label: string;
1313
isNarrow?: boolean;
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<script setup lang="ts">
2+
import { useCssModule, computed } from 'vue';
3+
import mapClasses from '../../utils/mapClasses';
4+
import { MediaObject, NameValuePair, HtmlAttributes } from '../../types/interfaces';
5+
import type { Breakpoint } from '../../types/other';
6+
import { modifyClassName } from '../../utils/buildClass';
7+
import { getLayoutStyling } from '../../utils/mediaObject';
8+
import CdrLayout from '../layout/CdrLayout.vue';
9+
import { breakpoints, spacing } from '../../utils/other';
10+
11+
/** Component for buttons that have a checked state. */
12+
13+
defineOptions({ name: 'CdrMediaObject' });
14+
15+
const props = withDefaults(defineProps<MediaObject>(), {
16+
align: 'start',
17+
mediaPosition: 'left',
18+
mediaWidth: '1fr',
19+
mediaHeight: 'auto',
20+
mediaCover: false,
21+
overlay: false,
22+
overlayRowAlign: 'start',
23+
overlayColumnAlign: 'start',
24+
contentPadding: 'zero',
25+
});
26+
27+
const style = useCssModule();
28+
29+
const rootProps = computed(() => {
30+
const baseClass = 'cdr-media-object';
31+
const classes = [baseClass];
32+
const inlineStyles: NameValuePair = {};
33+
const {
34+
align,
35+
mediaPosition,
36+
mediaWidth,
37+
mediaHeight,
38+
mediaCover,
39+
overlay,
40+
overlayRowAlign,
41+
overlayColumnAlign,
42+
contentPadding,
43+
...otherProps
44+
} = props;
45+
const additionalProps: HtmlAttributes = { ...otherProps };
46+
47+
if (contentPadding !== 'zero') {
48+
if (typeof contentPadding === 'string') {
49+
inlineStyles['--cdr-media-object-content-padding'] = spacing[contentPadding];
50+
} else {
51+
classes.push(modifyClassName(baseClass, 'content-padding-cq'));
52+
53+
breakpoints.forEach((breakpoint: Breakpoint) => {
54+
// Add in padding styles for various breakpoints
55+
inlineStyles[`--cdr-media-object-content-padding-${breakpoint}`] =
56+
spacing[contentPadding[breakpoint]];
57+
});
58+
}
59+
}
60+
61+
// Enter overlay mode, which does not use these props, because they are not relevant:
62+
// mediaPosition, mediaWidth, mediaHeight, mediaCover, align
63+
if (overlay) {
64+
classes.push(modifyClassName(baseClass, 'overlay'));
65+
Object.assign(additionalProps, { rows: 'auto', columns: 'auto' });
66+
inlineStyles['--cdr-media-object-row-align'] = overlayRowAlign;
67+
inlineStyles['--cdr-media-object-column-align'] = overlayColumnAlign;
68+
} else {
69+
// Get layout related props and inline styles based on media measurements and
70+
// content position, both of which can be dynamic
71+
const layoutStyling = getLayoutStyling(mediaPosition, mediaWidth, mediaHeight);
72+
Object.assign(inlineStyles, layoutStyling.inlineStyles);
73+
Object.assign(additionalProps, layoutStyling.props);
74+
75+
// Add in class for allowing dynamic content positioning
76+
if (typeof mediaPosition !== 'string') {
77+
classes.push(modifyClassName(baseClass, 'media-position-cq'));
78+
}
79+
80+
// Add align class and inline styles for allowing dynamic align values
81+
// or set the static value
82+
if (typeof align !== 'string') {
83+
classes.push(modifyClassName(baseClass, 'align-cq'));
84+
85+
breakpoints.forEach((breakpoint: Breakpoint) => {
86+
// Add in media position styles for various breakpoints
87+
inlineStyles[`--cdr-media-object-align-${breakpoint}`] = align[breakpoint];
88+
});
89+
} else {
90+
inlineStyles['--cdr-media-object-align'] = align;
91+
}
92+
93+
// Add cover class
94+
if (mediaCover) {
95+
classes.push(modifyClassName(baseClass, 'cover'));
96+
}
97+
}
98+
99+
return {
100+
...additionalProps,
101+
class: mapClasses(style, ...classes) || undefined,
102+
style: inlineStyles,
103+
};
104+
});
105+
</script>
106+
107+
<template>
108+
<CdrLayout v-bind="rootProps">
109+
<div :class="style['cdr-media-object__media']">
110+
<!-- @slot Where the media should be placed. Should be a single node. -->
111+
<slot name="media" />
112+
</div>
113+
<div :class="style['cdr-media-object__content']">
114+
<!-- @slot Where all content should be placed. Can be multiple nodes. -->
115+
<slot name="content" />
116+
</div>
117+
</CdrLayout>
118+
</template>
119+
120+
<style lang="scss" module src="./styles/CdrMediaObject.module.scss"></style>
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { markRaw } from 'vue';
2+
import { mount } from '../../../../test/vue-jest-style-workaround.js';
3+
import CdrMediaObject from '../CdrMediaObject.vue';
4+
import CdrSurface from '../../surface/CdrSurface.vue';
5+
6+
const CdrSurfaceRaw = markRaw(CdrSurface);
7+
8+
export const examples = [
9+
{
10+
label: 'default: media position left, media width 1fr, media height auto, align start',
11+
props: {},
12+
},
13+
{
14+
label: 'content padding',
15+
props: { contentPadding: 'two-x' },
16+
},
17+
{
18+
label: 'content padding dynamic',
19+
props: { contentPadding: { xs: 'one-x', sm: 'one-x', md: 'three-x', lg: 'three-x' } },
20+
},
21+
{
22+
label: 'media width 100px',
23+
props: { mediaWidth: '100px' },
24+
},
25+
{
26+
label: 'media width dynamic',
27+
props: { mediaWidth: { xs: '100px', sm: '100px', md: '300px', lg: '300px' } },
28+
},
29+
{
30+
label: 'align center',
31+
props: { mediaWidth: 'auto', align: 'center' },
32+
},
33+
{
34+
label: 'dynamic align',
35+
props: { mediaWidth: 'auto', align: { xs: 'center', sm: 'center', md: 'end', lg: 'start' } },
36+
},
37+
{
38+
label: 'cover',
39+
props: { mediaWidth: '125px', mediaCover: true },
40+
},
41+
{
42+
label: 'media position right',
43+
props: {
44+
mediaPosition: 'right',
45+
},
46+
},
47+
{
48+
label: 'media position dynamic',
49+
props: {
50+
mediaPosition: { xs: 'top', sm: 'bottom', md: 'left', lg: 'right' },
51+
},
52+
},
53+
{
54+
label: 'media bottom',
55+
props: {
56+
mediaPosition: 'bottom',
57+
},
58+
},
59+
{
60+
label: 'media top, align center',
61+
props: {
62+
mediaPosition: 'top',
63+
align: 'center',
64+
},
65+
},
66+
{
67+
label: 'media position dynamic, media height dynamic, media width dynamic',
68+
props: {
69+
mediaWidth: { xs: '100%', sm: '100%', md: '50%', lg: '75%' },
70+
mediaHeight: { xs: '100px', sm: '200px', md: 'auto', lg: 'auto' },
71+
mediaPosition: { xs: 'top', sm: 'top', md: 'left', lg: 'left' },
72+
mediaCover: true,
73+
},
74+
},
75+
{
76+
label: 'overlay, row align, column align, content configured independently to 50% width',
77+
props: {
78+
overlay: true,
79+
overlayColumnAlign: 'end',
80+
overlayRowAlign: 'center',
81+
},
82+
},
83+
{
84+
label: 'pass down props to Layout and Surface',
85+
props: {
86+
background: 'brand-spruce',
87+
as: CdrSurfaceRaw,
88+
},
89+
},
90+
];
91+
92+
describe('CdrMediaObject', () => {
93+
describe('snapshot tests', () => {
94+
examples.forEach(({ label, props }) => {
95+
it(label, () => {
96+
const wrapper = mount(CdrMediaObject, {
97+
props,
98+
slots: { content: 'Some content', media: 'Some media' },
99+
});
100+
expect(wrapper.element).toMatchSnapshot();
101+
});
102+
});
103+
});
104+
});

0 commit comments

Comments
 (0)