diff --git a/packages/chusho/src/components/CPicture/CPicture.spec.js b/packages/chusho/src/components/CPicture/CPicture.spec.js new file mode 100644 index 00000000..115fe915 --- /dev/null +++ b/packages/chusho/src/components/CPicture/CPicture.spec.js @@ -0,0 +1,78 @@ +import { mount } from '@vue/test-utils'; +import CPicture from './CPicture'; + +describe('CPicture', () => { + it('renders with the right attributes', () => { + const wrapper = mount(CPicture, { + props: { + src: '/image.jpg', + alt: 'alt', + }, + }); + + expect(wrapper.html()).toBe( + 'alt' + ); + }); + + it('renders an empty alt by default', () => { + const wrapper = mount(CPicture, { + props: { + src: '/image.jpg', + }, + }); + + expect(wrapper.html()).toBe( + '' + ); + }); + + it('apply config class on img tag', () => { + const wrapper = mount(CPicture, { + global: { + provide: { + $chusho: { + options: { + components: { + picture: { + class: 'picture', + }, + }, + }, + }, + }, + }, + props: { + src: '/image.jpg', + class: 'special-picture', + }, + }); + + expect(wrapper.find('img').classes()).toEqual([ + 'special-picture', + 'picture', + ]); + }); + + it('renders given sources', () => { + const wrapper = mount(CPicture, { + props: { + src: '/image.jpg', + sources: [ + { + srcset: 'image@2x.webp 2x, image.webp', + type: 'image/webp', + }, + { + srcset: 'image@2x.jpg 2x, image.jpg', + type: 'image/jpeg', + }, + ], + }, + }); + + expect(wrapper.html()).toBe( + '' + ); + }); +}); diff --git a/packages/chusho/src/components/CPicture/CPicture.ts b/packages/chusho/src/components/CPicture/CPicture.ts new file mode 100644 index 00000000..7647dc12 --- /dev/null +++ b/packages/chusho/src/components/CPicture/CPicture.ts @@ -0,0 +1,57 @@ +import { + defineComponent, + h, + inject, + mergeProps, + PropType, + SourceHTMLAttributes, +} from 'vue'; + +import { DollarChusho } from '../../types'; +import { generateConfigClass } from '../../utils/components'; +import componentMixin from '../mixins/componentMixin'; + +export default defineComponent({ + name: 'CPicture', + + mixins: [componentMixin], + + inheritAttrs: false, + + props: { + /** + * Default/fallback image URL used in the `src` attribute. + */ + src: { + type: String, + required: true, + }, + /** + * Alternative text description; leave empty if the image is not a key part of the content, otherwise describe what can be seen. + */ + alt: { + type: String, + default: '', + }, + /** + * Generate multiple `source` elements with the given attributes. + */ + sources: { + type: Array as PropType, + default: () => [], + }, + }, + + render() { + const pictureConfig = inject('$chusho', null)?.options + ?.components?.picture; + const elementProps: Record = mergeProps(this.$attrs, { + src: this.$props.src, + alt: this.$props.alt, + ...generateConfigClass(pictureConfig?.class, this.$props), + }); + const sources = this.$props.sources.map((source) => h('source', source)); + + return h('picture', null, [...sources, h('img', elementProps)]); + }, +}); diff --git a/packages/chusho/src/components/CPicture/index.ts b/packages/chusho/src/components/CPicture/index.ts new file mode 100644 index 00000000..7fc2f8d1 --- /dev/null +++ b/packages/chusho/src/components/CPicture/index.ts @@ -0,0 +1,3 @@ +import CPicture from './CPicture'; + +export { CPicture }; diff --git a/packages/chusho/src/components/index.ts b/packages/chusho/src/components/index.ts index 9059ac70..5d500548 100644 --- a/packages/chusho/src/components/index.ts +++ b/packages/chusho/src/components/index.ts @@ -4,3 +4,4 @@ export * from './CCollapse'; export * from './CTabs'; export * from './CDialog'; export * from './CAlert'; +export * from './CPicture'; diff --git a/packages/chusho/src/types/index.ts b/packages/chusho/src/types/index.ts index d1fad563..27acd423 100644 --- a/packages/chusho/src/types/index.ts +++ b/packages/chusho/src/types/index.ts @@ -57,6 +57,9 @@ interface ComponentsOptions { collapseContent?: ComponentCommonOptions & { transition?: BaseTransitionProps; }; + picture?: { + class?: VueClassBinding | ClassGenerator; + }; } export interface ChushoOptions { diff --git a/packages/docs/.vuepress/config.js b/packages/docs/.vuepress/config.js index 9bffe587..f03fc3db 100644 --- a/packages/docs/.vuepress/config.js +++ b/packages/docs/.vuepress/config.js @@ -41,6 +41,7 @@ module.exports = { 'components/button.md', 'components/dialog.md', 'components/icon.md', + 'components/picture.md', 'components/tabs.md', 'components/collapse.md', ], diff --git a/packages/docs/guide/components/picture.md b/packages/docs/guide/components/picture.md new file mode 100644 index 00000000..cf384e70 --- /dev/null +++ b/packages/docs/guide/components/picture.md @@ -0,0 +1,56 @@ +# Picture + +Easily generate responsive images. + +## Config + +### class + +Classes applied to the component `img` element, except when the prop `bare` is set to `true`. See [styling components](/guide/styling-components/). + +- **type:** `Array | Object | String | (props: Object) => {}` +- **default:** `null` + +#### Example + +```js +class: 'img-responsive' +``` + +## API + + + +## Examples + +### Simplest + +```vue + +``` + +### With sources + +```vue + +``` + +### With additional attributes + +Attributes are not applied to the `picture` element but to the `img` element. + +```vue + +``` diff --git a/packages/playground/chusho.config.js b/packages/playground/chusho.config.js index 3e9f42ff..222a936f 100644 --- a/packages/playground/chusho.config.js +++ b/packages/playground/chusho.config.js @@ -33,6 +33,9 @@ export default { height: 48, class: 'inline-block align-middle pointer-events-none fill-current', }, + picture: { + class: 'picture', + }, tabs: { class: 'tabs', }, diff --git a/packages/playground/src/assets/images/building.jpg b/packages/playground/src/assets/images/building.jpg new file mode 100644 index 00000000..3b2c9355 Binary files /dev/null and b/packages/playground/src/assets/images/building.jpg differ diff --git a/packages/playground/src/assets/images/building.webp b/packages/playground/src/assets/images/building.webp new file mode 100644 index 00000000..bc61a4ec Binary files /dev/null and b/packages/playground/src/assets/images/building.webp differ diff --git a/packages/playground/src/assets/images/building@2x.jpg b/packages/playground/src/assets/images/building@2x.jpg new file mode 100644 index 00000000..6b7de319 Binary files /dev/null and b/packages/playground/src/assets/images/building@2x.jpg differ diff --git a/packages/playground/src/assets/images/building@2x.webp b/packages/playground/src/assets/images/building@2x.webp new file mode 100644 index 00000000..2ebe9385 Binary files /dev/null and b/packages/playground/src/assets/images/building@2x.webp differ diff --git a/packages/playground/src/components/examples/components/picture/Default.vue b/packages/playground/src/components/examples/components/picture/Default.vue new file mode 100644 index 00000000..afa1fd9f --- /dev/null +++ b/packages/playground/src/components/examples/components/picture/Default.vue @@ -0,0 +1,15 @@ + + + diff --git a/packages/playground/src/components/examples/components/picture/WithSources.vue b/packages/playground/src/components/examples/components/picture/WithSources.vue new file mode 100644 index 00000000..32af3c65 --- /dev/null +++ b/packages/playground/src/components/examples/components/picture/WithSources.vue @@ -0,0 +1,35 @@ + + + diff --git a/packages/playground/src/components/examples/routes.json b/packages/playground/src/components/examples/routes.json index 83d76e8a..79e41b15 100644 --- a/packages/playground/src/components/examples/routes.json +++ b/packages/playground/src/components/examples/routes.json @@ -131,6 +131,19 @@ "component": "Controlled" } ] + }, + "picture": { + "label": "Picture", + "variants": [ + { + "label": "Default", + "component": "Default" + }, + { + "label": "With Sources", + "component": "WithSources" + } + ] } } },