Skip to content

Commit

Permalink
Media Object (#192)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sikhote authored Dec 12, 2024
1 parent eeb1f87 commit bf23abd
Show file tree
Hide file tree
Showing 27 changed files with 3,302 additions and 1,623 deletions.
3,541 changes: 1,939 additions & 1,602 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,14 @@
"stylelint-config-standard-scss": "^13.1.0",
"stylelint-scss": "^6.8.1",
"ts-node": "^10.9.1",
"typescript": "^5.1.6",
"typescript": "5.6.3",
"vite": "^5.0.1",
"vite-plugin-dts": "^3.3.1",
"vitest": "^1.1.1",
"vue-docgen-cli": "^4.79.0",
"vue-eslint-parser": "^9.3.0",
"vue-router": "^4.0.10",
"vue-tsc": "^2.0.29"
"vue-tsc": "2.1.10"
},
"engines": {
"node": ">= 20.0.0",
Expand Down
4 changes: 2 additions & 2 deletions src/components/examples.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import landingLead from 'componentsdir/landingLead/examples/LandingLead.vue';
import layout from 'componentsdir/layout/examples/Layout.vue';
import links from 'componentsdir/link/examples/Links.vue';
import list from 'componentsdir/list/examples/Lists.vue';
// import mediaObject from 'componentsdir/mediaObject/examples/MediaObject.vue';
import mediaObject from 'componentsdir/mediaObject/examples/MediaObject.vue';
import modal from 'componentsdir/modal/examples/Modal.vue';
import pagination from 'componentsdir/pagination/examples/Pagination.vue';
import picture from 'componentsdir/picture/examples/Picture.vue';
Expand Down Expand Up @@ -67,7 +67,7 @@ export default {
layout,
links,
list,
// mediaObject,
mediaObject,
modal,
pagination,
picture,
Expand Down
6 changes: 4 additions & 2 deletions src/components/fulfillmentTile/CdrFulfillmentTileContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ const rootProps = computed(
(): bodyTextProps => ({
tag: 'div',
scale: props.scale,
class: [style[baseClass]],
}),
);
</script>

<template>
<CdrBody v-bind="rootProps">
<CdrBody
v-bind="rootProps"
:class="[style[baseClass]]"
>
<!-- @slot Where all default content should be placed. -->
<slot />
</CdrBody>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ const toggleCheckbox2 = (value: number) => {
class="example__search-tile-other-header-button"
:checked="checkbox2.includes(1)"
role="checkbox"
:layout="{ flow: 'rows' }"
:layout="{ flow: 'row' }"
@click="toggleCheckbox2(1)"
>
<CdrFulfillmentTileHeader>
Expand Down
2 changes: 1 addition & 1 deletion src/components/layout/CdrLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const rootProps = computed(() => {

<template>
<component
:is="as"
:is="props.as"
v-bind="rootProps"
>
<!-- @slot Where all default content should be placed. -->
Expand Down
4 changes: 2 additions & 2 deletions src/components/layout/examples/Layout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
import CdrLayout from '../CdrLayout.vue';
import CdrSurface from '../../surface/CdrSurface.vue';
import CdrText from '../../text/CdrText.vue';
import type { Layout } from '../../../types/interfaces';
import type { Layout, HtmlAttributes } from '../../../types/interfaces';
defineOptions({ name: 'Layout' });
interface LayoutExample {
props: Layout;
props: Layout | HtmlAttributes;
children: number;
label: string;
isNarrow?: boolean;
Expand Down
120 changes: 120 additions & 0 deletions src/components/mediaObject/CdrMediaObject.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<script setup lang="ts">
import { useCssModule, computed } from 'vue';
import mapClasses from '../../utils/mapClasses';
import { MediaObject, NameValuePair, HtmlAttributes } from '../../types/interfaces';
import type { Breakpoint } from '../../types/other';
import { modifyClassName } from '../../utils/buildClass';
import { getLayoutStyling } from '../../utils/mediaObject';
import CdrLayout from '../layout/CdrLayout.vue';
import { breakpoints, spacing } from '../../utils/other';
/** Component for buttons that have a checked state. */
defineOptions({ name: 'CdrMediaObject' });
const props = withDefaults(defineProps<MediaObject>(), {
align: 'start',
mediaPosition: 'left',
mediaWidth: '1fr',
mediaHeight: 'auto',
mediaCover: false,
overlay: false,
overlayRowAlign: 'start',
overlayColumnAlign: 'start',
contentPadding: 'zero',
});
const style = useCssModule();
const rootProps = computed(() => {
const baseClass = 'cdr-media-object';
const classes = [baseClass];
const inlineStyles: NameValuePair = {};
const {
align,
mediaPosition,
mediaWidth,
mediaHeight,
mediaCover,
overlay,
overlayRowAlign,
overlayColumnAlign,
contentPadding,
...otherProps
} = props;
const additionalProps: HtmlAttributes = { ...otherProps };
if (contentPadding !== 'zero') {
if (typeof contentPadding === 'string') {
inlineStyles['--cdr-media-object-content-padding'] = spacing[contentPadding];
} else {
classes.push(modifyClassName(baseClass, 'content-padding-cq'));
breakpoints.forEach((breakpoint: Breakpoint) => {
// Add in padding styles for various breakpoints
inlineStyles[`--cdr-media-object-content-padding-${breakpoint}`] =
spacing[contentPadding[breakpoint]];
});
}
}
// Enter overlay mode, which does not use these props, because they are not relevant:
// mediaPosition, mediaWidth, mediaHeight, mediaCover, align
if (overlay) {
classes.push(modifyClassName(baseClass, 'overlay'));
Object.assign(additionalProps, { rows: 'auto', columns: 'auto' });
inlineStyles['--cdr-media-object-row-align'] = overlayRowAlign;
inlineStyles['--cdr-media-object-column-align'] = overlayColumnAlign;
} else {
// Get layout related props and inline styles based on media measurements and
// content position, both of which can be dynamic
const layoutStyling = getLayoutStyling(mediaPosition, mediaWidth, mediaHeight);
Object.assign(inlineStyles, layoutStyling.inlineStyles);
Object.assign(additionalProps, layoutStyling.props);
// Add in class for allowing dynamic content positioning
if (typeof mediaPosition !== 'string') {
classes.push(modifyClassName(baseClass, 'media-position-cq'));
}
// Add align class and inline styles for allowing dynamic align values
// or set the static value
if (typeof align !== 'string') {
classes.push(modifyClassName(baseClass, 'align-cq'));
breakpoints.forEach((breakpoint: Breakpoint) => {
// Add in media position styles for various breakpoints
inlineStyles[`--cdr-media-object-align-${breakpoint}`] = align[breakpoint];
});
} else {
inlineStyles['--cdr-media-object-align'] = align;
}
// Add cover class
if (mediaCover) {
classes.push(modifyClassName(baseClass, 'cover'));
}
}
return {
...additionalProps,
class: mapClasses(style, ...classes) || undefined,
style: inlineStyles,
};
});
</script>

<template>
<CdrLayout v-bind="rootProps">
<div :class="style['cdr-media-object__media']">
<!-- @slot Where the media should be placed. Should be a single node. -->
<slot name="media" />
</div>
<div :class="style['cdr-media-object__content']">
<!-- @slot Where all content should be placed. Can be multiple nodes. -->
<slot name="content" />
</div>
</CdrLayout>
</template>

<style lang="scss" module src="./styles/CdrMediaObject.module.scss"></style>
104 changes: 104 additions & 0 deletions src/components/mediaObject/__tests__/CdrMediaObject.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { markRaw } from 'vue';
import { mount } from '../../../../test/vue-jest-style-workaround.js';
import CdrMediaObject from '../CdrMediaObject.vue';
import CdrSurface from '../../surface/CdrSurface.vue';

const CdrSurfaceRaw = markRaw(CdrSurface);

export const examples = [
{
label: 'default: media position left, media width 1fr, media height auto, align start',
props: {},
},
{
label: 'content padding',
props: { contentPadding: 'two-x' },
},
{
label: 'content padding dynamic',
props: { contentPadding: { xs: 'one-x', sm: 'one-x', md: 'three-x', lg: 'three-x' } },
},
{
label: 'media width 100px',
props: { mediaWidth: '100px' },
},
{
label: 'media width dynamic',
props: { mediaWidth: { xs: '100px', sm: '100px', md: '300px', lg: '300px' } },
},
{
label: 'align center',
props: { mediaWidth: 'auto', align: 'center' },
},
{
label: 'dynamic align',
props: { mediaWidth: 'auto', align: { xs: 'center', sm: 'center', md: 'end', lg: 'start' } },
},
{
label: 'cover',
props: { mediaWidth: '125px', mediaCover: true },
},
{
label: 'media position right',
props: {
mediaPosition: 'right',
},
},
{
label: 'media position dynamic',
props: {
mediaPosition: { xs: 'top', sm: 'bottom', md: 'left', lg: 'right' },
},
},
{
label: 'media bottom',
props: {
mediaPosition: 'bottom',
},
},
{
label: 'media top, align center',
props: {
mediaPosition: 'top',
align: 'center',
},
},
{
label: 'media position dynamic, media height dynamic, media width dynamic',
props: {
mediaWidth: { xs: '100%', sm: '100%', md: '50%', lg: '75%' },
mediaHeight: { xs: '100px', sm: '200px', md: 'auto', lg: 'auto' },
mediaPosition: { xs: 'top', sm: 'top', md: 'left', lg: 'left' },
mediaCover: true,
},
},
{
label: 'overlay, row align, column align, content configured independently to 50% width',
props: {
overlay: true,
overlayColumnAlign: 'end',
overlayRowAlign: 'center',
},
},
{
label: 'pass down props to Layout and Surface',
props: {
background: 'brand-spruce',
as: CdrSurfaceRaw,
},
},
];

describe('CdrMediaObject', () => {
describe('snapshot tests', () => {
examples.forEach(({ label, props }) => {
it(label, () => {
const wrapper = mount(CdrMediaObject, {
props,
slots: { content: 'Some content', media: 'Some media' },
});
expect(wrapper.element).toMatchSnapshot();
});
});
});
});
Loading

0 comments on commit bf23abd

Please sign in to comment.