Skip to content
Open
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
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "Tailor - Image Content Element",
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm",
"image": "mcr.microsoft.com/devcontainers/typescript-node:4-24-trixie",
"features": {
"ghcr.io/devcontainers-extra/features/pnpm:2": {},
"ghcr.io/devcontainers/features/github-cli:1": {}
Expand Down
33 changes: 21 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,33 @@
# ce-image
# Image

Tailor image content element component.
Image content element with upload and alt text support.

## Development
**Type:** `IMAGE`

Dev server:
## Data

```sh
pnpm dev
```
| Field | Type | Description |
|-------|------|-------------|
| `url` | `string \| null` | Public image URL |
| `alt` | `string?` | Alt text |

Lint
## Edit

```sh
pnpm lint
```
- Image preview with progressive loading
- Upload button in top toolbar (JPG, JPEG, PNG, GIF, WEBP, AVIF) with URL source support
- Alt text field in side toolbar

## Display

Test
- Renders the image with alt text
- Click image to open a fullscreen viewer dialog

## Development

```sh
pnpm dev # Preview :8080 | Edit :8010 | Display :8020 | Server :8030
pnpm build
pnpm lint
pnpm test
```

Expand Down
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@
},
"devDependencies": {
"@changesets/cli": "^2.29.5",
"@playwright/test": "1.53.2",
"@tailor-cms/cek-e2e": "^1.3.2",
"@tailor-cms/eslint-config": "1.1.2",
"@tailor-cms/tce-boot": "1.3.2",
"@playwright/test": "^1.58.2",
"@tailor-cms/cek-e2e": "^2.0.0-beta.10",
"@tailor-cms/eslint-config": "^2.0.0-beta.10",
"@tailor-cms/tce-boot": "^2.0.0-beta.10",
"@types/node": "^24.0.12",
"dotenv": "^17.1.0",
"eslint": "^9.30.1",
"eslint": "^9.39.3",
"prettier": "^3.6.2",
"typescript": "^5.8.3"
"typescript": "^6.0.2"
},
"packageManager": "pnpm@10.12.3"
}
16 changes: 9 additions & 7 deletions packages/display/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"description": "Tailor CMS image element end-user component",
"author": "Studion <info@gostudion.com> (https://github.com/tailor-cms)",
"type": "module",
"version": "1.1.0",
"version": "1.1.1-beta.0",
"exports": {
".": {
"import": "./dist/index.js",
Expand All @@ -25,15 +25,17 @@
"prepublish": "pnpm build"
},
"peerDependencies": {
"vue": "^3.5.13"
"vue": "^3.5.29"
},
"devDependencies": {
"@tailor-cms/ce-image-manifest": "workspace:*",
"@tailor-cms/eslint-config": "1.1.2",
"@vitejs/plugin-vue": "^6.0.0",
"typescript": "^5.8.3",
"vite": "^7.0.3",
"vue-tsc": "^3.0.1"
"@tailor-cms/eslint-config": "^2.0.0-beta.10",
"@vitejs/plugin-vue": "^6.0.5",
"typescript": "^6.0.2",
"vite": "^8.0.0",
"vite-plugin-lib-inject-css": "^2.2.2",
"vue-tsc": "^3.2.5",
"vuetify": "^4.0.0"
},
"publishConfig": {
"access": "public"
Expand Down
87 changes: 66 additions & 21 deletions packages/display/src/components/Display.vue
Original file line number Diff line number Diff line change
@@ -1,35 +1,80 @@
<template>
<div class="tce-image-root">
<VImg
v-if="element.data.url"
:alt="element.data.alt"
:src="element.data.url"
class="mx-auto"
<VDialog
aria-label="Image viewer"
content-class="d-flex align-center justify-center h-100 w-100"
transition="fade-transition"
@after-enter="closeBtn?.$el?.focus()"
>
<template #placeholder>
<div class="d-flex align-center justify-center fill-height">
<VProgressCircular color="blue-grey-darken-3" indeterminate />
</div>
<template #activator="{ props: overlayProps }">
<button
v-bind="overlayProps"
:aria-label="`Open image viewer: ${element.data.alt || 'image'}`"
class="image-viewer-activator"
>
<VImg :alt="element.data.alt" :src="url" class="mx-auto" rounded="lg">
<template #placeholder>
<div class="d-flex align-center justify-center fill-height">
<VProgressCircular
aria-label="Loading image"
color="blue-grey-darken-3"
indeterminate
/>
</div>
</template>
</VImg>
</button>
</template>
</VImg>
<VSheet
v-else
class="d-flex justify-center align-center my-2 text-h6"
height="15.5rem"
>
<VIcon class="mr-2">mdi-image-outline</VIcon>
Image placeholder
</VSheet>
<template #default="{ isActive }">
<VBtn
ref="closeBtn"
aria-label="Close image viewer"
class="position-absolute top-0 right-0"
color="white"
icon="mdi-close"
variant="tonal"
@click="isActive.value = false"
/>
<img :alt="element.data.alt" :src="url" class="viewer-image" />
</template>
</VDialog>
</div>
</template>

<script setup lang="ts">
import { Element } from '@tailor-cms/ce-image-manifest';
import { computed, useTemplateRef } from 'vue';
import type { Element } from '@tailor-cms/ce-image-manifest';
import type { VBtn } from 'vuetify/components';

const props = defineProps<{ element: Element }>();

defineProps<{ element: Element; userState: any }>();
const closeBtn = useTemplateRef<InstanceType<typeof VBtn>>('closeBtn');

const url = computed(() => props.element.data.url!);
</script>

<style scoped>
.tce-image-root {
.image-viewer-activator {
display: block;
width: 100%;
border: none;
padding: 0;
background: transparent;
border-radius: 8px;

&:focus-visible {
outline: 3px solid highlight;
outline-offset: 2px;
}
}

.viewer-image {
max-width: 90vw;
max-height: 90vh;
border-radius: 8px;
}

.v-overlay--active {
backdrop-filter: blur(18px);
}
</style>
1 change: 1 addition & 0 deletions packages/display/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/// <reference types="vite/client" />
/// <reference types="vuetify" />
1 change: 1 addition & 0 deletions packages/display/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"types": ["vite/client"],
"rootDir": "./src",
"outDir": "dist"
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
Expand Down
3 changes: 1 addition & 2 deletions packages/display/tsconfig.node.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"moduleResolution": "bundler"
},
"include": ["vite.config.ts"]
}
12 changes: 4 additions & 8 deletions packages/display/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { defineConfig } from 'vite';
import { libInjectCss } from 'vite-plugin-lib-inject-css';
import vue from '@vitejs/plugin-vue';

import { resolve } from 'node:path';

// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
plugins: [vue(), libInjectCss()],
build: {
// In order to avoid display runtime issues
// due to package missing (if dist is deleted for short time)
Expand All @@ -18,17 +19,12 @@ export default defineConfig({
fileName: 'index',
formats: ['es', 'cjs'],
},
rollupOptions: {
rolldownOptions: {
// make sure to externalize deps that shouldn't be bundled
// into your library
external: ['vue'],
output: {
intro: 'import "./index.css";',
// Provide global variables to use in the UMD build
// for externalized deps
globals: {
vue: 'Vue',
},
exports: 'named',
},
},
},
Expand Down
21 changes: 10 additions & 11 deletions packages/edit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"description": "Tailor CMS image element authoring component",
"author": "Studion <info@gostudion.com> (https://github.com/tailor-cms)",
"type": "module",
"version": "1.1.0",
"version": "1.1.1-beta.0",
"exports": {
".": {
"import": "./dist/index.js",
Expand All @@ -25,21 +25,20 @@
"prepublish": "pnpm build"
},
"peerDependencies": {
"vue": "^3.5.13"
"vue": "^3.5.29"
},
"devDependencies": {
"@tailor-cms/ce-image-manifest": "workspace:*",
"@tailor-cms/eslint-config": "1.1.2",
"@types/lodash-es": "^4.17.12",
"@vitejs/plugin-vue": "^6.0.0",
"typescript": "^5.8.3",
"vite": "^7.0.3",
"vue-tsc": "^3.0.1"
"@tailor-cms/eslint-config": "^2.0.0-beta.10",
"@vitejs/plugin-vue": "^6.0.5",
"typescript": "^6.0.2",
"vite": "^8.0.0",
"vite-plugin-lib-inject-css": "^2.2.2",
"vue-tsc": "^3.2.5",
"vuetify": "^4.0.0"
},
"dependencies": {
"@tailor-cms/cek-common": "^1.3.2",
"@tailor-cms/core-components": "^1.2.0",
"lodash-es": "^4.17.21"
"@tailor-cms/cek-common": "^2.0.0-beta.10"
},
"publishConfig": {
"access": "public"
Expand Down
4 changes: 1 addition & 3 deletions packages/edit/src/components/Edit.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<div class="tce-image">
<ElementPlaceholder
<TailorElementPlaceholder
v-if="!element.data.url"
:is-disabled="isReadonly"
:is-focused="isFocused"
Expand All @@ -23,7 +23,6 @@

<script lang="ts" setup>
import type { Element } from '@tailor-cms/ce-image-manifest';
import { ElementPlaceholder } from '@tailor-cms/core-components';
import manifest from '@tailor-cms/ce-image-manifest';

defineProps<{
Expand All @@ -32,7 +31,6 @@ defineProps<{
isFocused: boolean;
isReadonly: boolean;
}>();
defineEmits(['save']);
</script>

<style lang="scss" scoped>
Expand Down
4 changes: 2 additions & 2 deletions packages/edit/src/components/SideToolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
</template>

<script setup lang="ts">
import type { Element } from '@tailor-cms/ce-image-manifest';
import type { Element, ElementData } from '@tailor-cms/ce-image-manifest';
import { ref } from 'vue';

const props = defineProps<{ element: Element }>();
const emit = defineEmits(['save']);
const emit = defineEmits<{ save: [data: ElementData] }>();

const altText = ref(props.element.data.alt || '');

Expand Down
38 changes: 22 additions & 16 deletions packages/edit/src/components/TopToolbar.vue
Original file line number Diff line number Diff line change
@@ -1,31 +1,37 @@
<template>
<div class="d-flex align-center justify-center">
<AssetInput
:extensions="['.png', '.jpg', '.jpeg']"
:public-url="element.data.url"
:url="element.data.assets?.url"
<TailorFileInput
:allowed-extensions="EXTENSIONS"
:file-key="element.data.assets?.url"
class="mx-auto"
upload-label="Upload image"
@input="save"
allow-url-source
@delete="onDelete"
@input="onInput"
@upload="onUpload"
/>
</div>
</template>

<script setup lang="ts">
import { AssetInput } from '@tailor-cms/core-components';
import { cloneDeep } from 'lodash-es';
import type { Element } from '@tailor-cms/ce-image-manifest';
import type { Element, ElementData } from '@tailor-cms/ce-image-manifest';

const EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.avif'];

const props = defineProps<{ element: Element }>();
const emit = defineEmits(['save']);
const emit = defineEmits<{ save: [data: ElementData] }>();

const save = ({ url, publicUrl }: { url: string; publicUrl: string }) => {
const onUpload = ({ url, publicUrl }: Record<string, any>) => {
const assets = { url };
const elementData = Object.assign(cloneDeep(props.element.data), {
url: publicUrl,
assets,
});
emit('save', elementData);
emit('save', { ...props.element.data, url: publicUrl, assets });
};

const onInput = (payload: Record<string, any> | null) => {
if (!payload) return;
emit('save', { ...props.element.data, url: payload.publicUrl });
};

const onDelete = () => {
emit('save', { ...props.element.data, url: null, assets: {} });
};
</script>

Expand Down
Loading
Loading