Skip to content

Commit 9dc08a6

Browse files
committed
update object overlay component
1 parent 7e3cdb3 commit 9dc08a6

5 files changed

Lines changed: 263 additions & 160 deletions

File tree

src/components/objectOverlay/CdrObjectOverlay.vue

Lines changed: 113 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { useCssModule, computed } from 'vue';
2+
import { useCssModule, computed, ref, onMounted, nextTick } from 'vue';
33
import type { ObjectOverlayProps } from '../../types/interfaces';
44
55
/** Component for positioning content in 9 different positions relative to a container */
@@ -10,9 +10,31 @@ const props = withDefaults(defineProps<ObjectOverlayProps>(), {
1010
position: 'center-center',
1111
margin: 'zero',
1212
tag: 'div',
13+
gradientTheme: 'dark'
1314
});
1415
15-
// Helper function to process component properties (position, gradient)
16+
// Refs for DOM elements
17+
const containerRef = ref<HTMLElement | null>(null);
18+
const overlayRef = ref<HTMLElement | null>(null);
19+
20+
// Helper function to determine gradient direction based on position
21+
const getGradientDirection = (position: string): string | null => {
22+
if (position === 'center-center') return null;
23+
24+
if (['left-top', 'center-top', 'right-top'].includes(position)) {
25+
return 'to-bottom';
26+
} else if (position === 'left-center') {
27+
return 'to-right';
28+
} else if (position === 'right-center') {
29+
return 'to-left';
30+
} else if (['left-bottom', 'center-bottom', 'right-bottom'].includes(position)) {
31+
return 'to-top';
32+
}
33+
34+
return null;
35+
};
36+
37+
// Helper function to process component properties (position, withGradient)
1638
const processComponentProperty = (property: string, prop: any): Record<string, string> => {
1739
const result: Record<string, string> = {};
1840
@@ -71,13 +93,38 @@ const attrs = computed(() => {
7193
const component: Record<string, string> = {};
7294
const content: Record<string, any> = { style: {} };
7395
74-
// Process component properties
75-
['position', 'gradient'].forEach((property) => {
76-
const prop = props[property as keyof typeof props];
77-
if (prop) {
78-
Object.assign(component, processComponentProperty(property, prop));
96+
// Process position property
97+
if (props.position) {
98+
Object.assign(component, processComponentProperty('position', props.position));
99+
}
100+
101+
// Process gradient based on position if withGradient is true
102+
if (props.withGradient) {
103+
// Set gradient theme
104+
if (props.gradientTheme) {
105+
component['data-gradient-theme'] = props.gradientTheme;
79106
}
80-
});
107+
108+
// For responsive positions, we need to determine gradient for each breakpoint
109+
if (typeof props.position === 'object') {
110+
Object.entries(props.position).forEach(([mq, pos]) => {
111+
const direction = getGradientDirection(pos as string);
112+
if (direction) {
113+
if (mq === 'xs') {
114+
component['data-gradient'] = direction;
115+
} else {
116+
component[`data-gradient-${mq}`] = direction;
117+
}
118+
}
119+
});
120+
} else {
121+
// For single position value
122+
const direction = getGradientDirection(props.position as string);
123+
if (direction) {
124+
component['data-gradient'] = direction;
125+
}
126+
}
127+
}
81128
82129
// Process content properties
83130
['margin', 'padding'].forEach((property) => {
@@ -97,6 +144,59 @@ const attrs = computed(() => {
97144
};
98145
});
99146
147+
// Function to inherit border radius from container
148+
const inheritBorderRadius = () => {
149+
if (!containerRef.value || !overlayRef.value) return;
150+
151+
// Get the first child of the container (the actual content element)
152+
const containerContent = containerRef.value.firstElementChild as HTMLElement;
153+
if (!containerContent) return;
154+
155+
requestAnimationFrame(() => {
156+
// Get computed style of the container content
157+
const computedStyle = window.getComputedStyle(containerContent);
158+
const borderRadius = computedStyle.borderRadius;
159+
160+
if (!overlayRef.value) return;
161+
162+
// Apply the same border radius to the overlay component
163+
if (borderRadius && borderRadius !== '0px') {
164+
overlayRef.value.style.borderRadius = borderRadius;
165+
} else {
166+
// Try to get border-radius from individual properties if the shorthand is not set
167+
const topLeft = computedStyle.borderTopLeftRadius;
168+
const topRight = computedStyle.borderTopRightRadius;
169+
const bottomLeft = computedStyle.borderBottomLeftRadius;
170+
const bottomRight = computedStyle.borderBottomRightRadius;
171+
172+
if (
173+
topLeft !== '0px' || topRight !== '0px' || bottomLeft !== '0px' || bottomRight !== '0px'
174+
) {
175+
overlayRef.value.style.borderTopLeftRadius = topLeft;
176+
overlayRef.value.style.borderTopRightRadius = topRight;
177+
overlayRef.value.style.borderBottomLeftRadius = bottomLeft;
178+
overlayRef.value.style.borderBottomRightRadius = bottomRight;
179+
}
180+
}
181+
}); // Small delay to ensure styles are applied
182+
};
183+
184+
// Apply border radius after component is mounted and when it updates
185+
onMounted(() => {
186+
nextTick(inheritBorderRadius);
187+
188+
// Set up MutationObserver to detect when children are added/changed
189+
if (containerRef.value) {
190+
const observer = new MutationObserver(inheritBorderRadius);
191+
observer.observe(containerRef.value, {
192+
childList: true,
193+
subtree: true,
194+
attributes: true,
195+
attributeFilter: ['style', 'class']
196+
});
197+
}
198+
});
199+
100200
const styles = useCssModule();
101201
</script>
102202

@@ -105,8 +205,12 @@ const styles = useCssModule();
105205
:is="tag"
106206
:class="styles['cdr-object-overlay']"
107207
v-bind="attrs.component"
208+
ref="overlayRef"
108209
>
109-
<div :class="styles['cdr-object-overlay__container']">
210+
<div
211+
:class="styles['cdr-object-overlay__container']"
212+
ref="containerRef"
213+
>
110214
<!-- @slot Container content that the overlay will be positioned relative to -->
111215
<slot name="container" />
112216
</div>

src/components/objectOverlay/__tests__/CdrObjectOverlay.spec.js

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -98,18 +98,6 @@ describe('CdrObjectOverlay.vue', () => {
9898
wrapper.unmount();
9999
});
100100

101-
it('applies gradient through data attribute', () => {
102-
const wrapper = mount(CdrObjectOverlay, {
103-
props: { gradient: 'to-top' },
104-
slots: {
105-
container: '<div>Container</div>',
106-
content: '<div>Content</div>',
107-
},
108-
});
109-
expect(wrapper.attributes('data-gradient')).toBe('to-top');
110-
wrapper.unmount();
111-
});
112-
113101
it('handles responsive position values', () => {
114102
const wrapper = mount(CdrObjectOverlay, {
115103
props: {
@@ -158,5 +146,84 @@ describe('CdrObjectOverlay.vue', () => {
158146
expect(wrapper.element.tagName).toBe('SECTION');
159147
wrapper.unmount();
160148
});
149+
150+
// New tests for gradientTheme
151+
it('applies dark gradient theme by default when withGradient is true', () => {
152+
const wrapper = mount(CdrObjectOverlay, {
153+
props: {
154+
withGradient: true,
155+
position: 'left-top'
156+
},
157+
slots: {
158+
container: '<div>Container</div>',
159+
content: '<div>Content</div>',
160+
},
161+
});
162+
expect(wrapper.attributes('data-gradient')).toBe('to-bottom');
163+
expect(wrapper.attributes('data-gradient-theme')).toBe('dark');
164+
wrapper.unmount();
165+
});
166+
167+
it('applies light gradient theme when specified', () => {
168+
const wrapper = mount(CdrObjectOverlay, {
169+
props: {
170+
withGradient: true,
171+
gradientTheme: 'light',
172+
position: 'left-top'
173+
},
174+
slots: {
175+
container: '<div>Container</div>',
176+
content: '<div>Content</div>',
177+
},
178+
});
179+
expect(wrapper.attributes('data-gradient')).toBe('to-bottom');
180+
expect(wrapper.attributes('data-gradient-theme')).toBe('light');
181+
wrapper.unmount();
182+
});
183+
184+
it('applies correct gradient direction based on position with light theme', () => {
185+
const positionsAndDirections = [
186+
{ position: 'left-top', direction: 'to-bottom' },
187+
{ position: 'center-top', direction: 'to-bottom' },
188+
{ position: 'right-top', direction: 'to-bottom' },
189+
{ position: 'left-center', direction: 'to-right' },
190+
{ position: 'right-center', direction: 'to-left' },
191+
{ position: 'left-bottom', direction: 'to-top' },
192+
{ position: 'center-bottom', direction: 'to-top' },
193+
{ position: 'right-bottom', direction: 'to-top' },
194+
];
195+
196+
positionsAndDirections.forEach(({ position, direction }) => {
197+
const wrapper = mount(CdrObjectOverlay, {
198+
props: {
199+
withGradient: true,
200+
gradientTheme: 'light',
201+
position
202+
},
203+
slots: {
204+
container: '<div>Container</div>',
205+
content: '<div>Content</div>',
206+
},
207+
});
208+
expect(wrapper.attributes('data-gradient')).toBe(direction);
209+
expect(wrapper.attributes('data-gradient-theme')).toBe('light');
210+
wrapper.unmount();
211+
});
212+
});
213+
214+
it('does not apply gradient when position is center-center', () => {
215+
const wrapper = mount(CdrObjectOverlay, {
216+
props: {
217+
withGradient: true,
218+
position: 'center-center'
219+
},
220+
slots: {
221+
container: '<div>Container</div>',
222+
content: '<div>Content</div>',
223+
},
224+
});
225+
expect(wrapper.attributes('data-gradient')).toBeUndefined();
226+
wrapper.unmount();
227+
});
161228
});
162229
});

0 commit comments

Comments
 (0)