diff --git a/src/components/examples.js b/src/components/examples.js index 02a8dbf39..1709228a2 100644 --- a/src/components/examples.js +++ b/src/components/examples.js @@ -22,6 +22,7 @@ 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 modal from 'componentsdir/modal/examples/Modal.vue'; +import objectOverlay from 'componentsdir/objectOverlay/examples/ObjectOverlay.vue'; import pagination from 'componentsdir/pagination/examples/Pagination.vue'; import picture from 'componentsdir/picture/examples/Picture.vue'; import popover from 'componentsdir/popover/examples/Popover.vue'; @@ -70,6 +71,7 @@ export default { list, mediaObject, modal, + objectOverlay, pagination, picture, popover, diff --git a/src/components/objectOverlay/CdrObjectOverlay.vue b/src/components/objectOverlay/CdrObjectOverlay.vue new file mode 100644 index 000000000..06954793d --- /dev/null +++ b/src/components/objectOverlay/CdrObjectOverlay.vue @@ -0,0 +1,227 @@ + + + + + \ No newline at end of file diff --git a/src/components/objectOverlay/styles/CdrObjectOverlay.module.scss b/src/components/objectOverlay/styles/CdrObjectOverlay.module.scss new file mode 100644 index 000000000..7ae7ec0e5 --- /dev/null +++ b/src/components/objectOverlay/styles/CdrObjectOverlay.module.scss @@ -0,0 +1,133 @@ +@use 'sass:map'; +@import '../../../styles/settings/index'; + +$breakpoints: ( + '': '', + '-sm': $cdr-breakpoint-sm, + '-md': $cdr-breakpoint-md, + '-lg': $cdr-breakpoint-lg +); + +$positions: ( + 'left-top': (top: 0, left: 0, transform: none), + 'center-top': (top: 0, left: 50%, transform: translateX(-50%)), + 'right-top': (top: 0, right: 0, transform: none), + 'left-center': (top: 50%, left: 0, transform: translateY(-50%)), + 'center-center': (top: 50%, left: 50%, transform: translate(-50%, -50%)), + 'right-center': (top: 50%, right: 0, transform: translateY(-50%)), + 'left-bottom': (bottom: 0, left: 0, transform: none), + 'center-bottom': (bottom: 0, left: 50%, transform: translateX(-50%)), + 'right-bottom': (bottom: 0, right: 0, transform: none) +); + +// Dark theme gradients (default) +$dark-gradients: ( + 'to-top': linear-gradient(to top, black 0%, rgba(0, 0, 0, 0.5) 20%, transparent 40%), + 'to-bottom': linear-gradient(to bottom, black 0%, rgba(0, 0, 0, 0.5) 20%, transparent 40%), + 'to-left': linear-gradient(to left, black 0%, rgba(0, 0, 0, 0.5) 20%, transparent 40%), + 'to-right': linear-gradient(to right, black 0%, rgba(0, 0, 0, 0.5) 20%, transparent 40%) +); + +// Light theme gradients +$light-gradients: ( + 'to-top': linear-gradient(to top, white 0%, rgba($cdr-color-background-primary, 0.5) 20%, transparent 40%), + 'to-bottom': linear-gradient(to bottom, white 0%, rgba($cdr-color-background-primary, 0.5) 20%, transparent 40%), + 'to-left': linear-gradient(to left, white 0%, rgba($cdr-color-background-primary, 0.5) 20%, transparent 40%), + 'to-right': linear-gradient(to right, white 0%, rgba($cdr-color-background-primary, 0.5) 20%, transparent 40%) +); + +.cdr-object-overlay { + overflow: hidden; + position: relative; + width: 100%; + height: 100%; + + &__container { + width: 100%; + height: 100%; + overflow: hidden; + } + + &__content { + position: absolute; + margin: var(--margin); + padding: var(--padding); + z-index: 2; + + @each $breakpoint-suffix, $min-width in $breakpoints { + @if $breakpoint-suffix != '' { + @media (min-width: $min-width) { + margin: var(--margin#{$breakpoint-suffix}, var(--margin)); + padding: var(--padding#{$breakpoint-suffix}, var(--padding)); + } + } + } + } + + // Create a mixin for breakpoints + @mixin at-breakpoint($breakpoint) { + @if $breakpoint == '' { + @content; + } @else { + @media (min-width: map.get($breakpoints, $breakpoint)) { + @content; + } + } + } + + // Generate positions for all breakpoints + @each $breakpoint-suffix, $min-width in $breakpoints { + @include at-breakpoint($breakpoint-suffix) { + // Position + @each $position, $properties in $positions { + $selector: if($breakpoint-suffix == '', + '[data-position="#{$position}"]', + '[data-position#{$breakpoint-suffix}="#{$position}"]'); + + &#{$selector} &__content { + @each $prop, $value in $properties { + #{$prop}: $value; + } + // Set unused positions to auto + @each $side in top, right, bottom, left { + @if not map-has-key($properties, $side) { + #{$side}: auto; + } + } + } + } + + // Dark theme gradients (default) + @each $gradient, $direction in $dark-gradients { + $selector: if($breakpoint-suffix == '', + '[data-gradient="#{$gradient}"]', + '[data-gradient#{$breakpoint-suffix}="#{$gradient}"]'); + + &#{$selector}:not([data-gradient-theme="light"])::before { + background-image: $direction; + border-radius: inherit; + display: block; + content: ''; + position: absolute; + inset: 0; + } + } + + // Light theme gradients + @each $gradient, $direction in $light-gradients { + $selector: if($breakpoint-suffix == '', + '[data-gradient="#{$gradient}"]', + '[data-gradient#{$breakpoint-suffix}="#{$gradient}"]'); + + &#{$selector}[data-gradient-theme="light"]::before { + background-image: $direction; + border-radius: inherit; + display: block; + content: ''; + position: absolute; + inset: 0; + } + } + } + } +} diff --git a/src/dev/router.js b/src/dev/router.js index 8bf672da5..bc7ed085e 100644 --- a/src/dev/router.js +++ b/src/dev/router.js @@ -42,6 +42,7 @@ const routes = [ { path: '/links', name: 'Links', component: Examples.links }, { path: '/lists', name: 'Lists', component: Examples.list }, { path: '/mediaObject', name: 'Media Object', component: Examples.mediaObject }, + { path: '/object-overlay', name: 'Object Overlay', component: Examples.objectOverlay }, { path: '/modals', name: 'Modals', component: Examples.modal }, { path: '/pagination', name: 'Pagination', component: Examples.pagination }, { path: '/picture', name: 'Picture', component: Examples.picture }, diff --git a/src/lib.ts b/src/lib.ts index 4ad55daed..6cace409a 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -28,6 +28,7 @@ export { default as CdrLink } from './components/link/CdrLink.vue'; export { default as CdrList } from './components/list/CdrList.vue'; export { default as CdrMediaObject } from './components/mediaObject/CdrMediaObject.vue'; export { default as CdrModal } from './components/modal/CdrModal.vue'; +export { default as CdrObjectOverlay } from './components/objectOverlay/CdrObjectOverlay.vue'; export { default as CdrPagination } from './components/pagination/CdrPagination.vue'; export { default as CdrPicture } from './components/picture/CdrPicture.vue'; export { default as CdrPopover } from './components/popover/CdrPopover.vue'; diff --git a/src/types/interfaces.ts b/src/types/interfaces.ts index d7a1e1658..6500804e2 100644 --- a/src/types/interfaces.ts +++ b/src/types/interfaces.ts @@ -3,7 +3,6 @@ import type { Tag, Space, SpaceFixed, - SpaceOption, Shadow, Radius, BorderColor, @@ -17,7 +16,7 @@ import type { Position, Alignment, AlignmentValue, - MediaMeasurement, + MediaMeasurement } from './other'; /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -361,7 +360,55 @@ export interface MediaObject extends Layout { /** * The spacing token to use for padding around the content. This can be an object with values for each Cedar breakpoint (xs, sm, md, lg). * @demoSelectMultiple false - * @values zero, one-x, two-x, scale-4, scale-3--5 + * @values zero, one-x, two-x */ - contentPadding?: SpaceOption; + contentPadding?: SpaceFixed; } + +export type ObjectPosition = + | 'left-top' + | 'center-top' + | 'right-top' + | 'left-center' + | 'center-center' + | 'right-center' + | 'left-bottom' + | 'center-bottom' + | 'right-bottom'; + +export type ResponsivePosition = { + xs?: ObjectPosition; + sm?: ObjectPosition; + md?: ObjectPosition; + lg?: ObjectPosition; +}; + +export type SpaceTuple = + | [SpaceFixed] + | [SpaceFixed, SpaceFixed] + | [SpaceFixed, SpaceFixed, SpaceFixed] + | [SpaceFixed, SpaceFixed, SpaceFixed, SpaceFixed]; + +export type Spacing = SpaceFixed | SpaceTuple; + +export type ResponsiveSpace = { + xs?: Spacing; + sm?: Spacing; + md?: Spacing; + lg?: Spacing; +}; + +export interface ObjectOverlayProps { + /** Determines if the container will have a gradient based on position */ + withGradient?: boolean; + /** Theme for the gradient (dark or light) */ + gradientTheme?: 'dark' | 'light'; + /** Position of the content relative to the container */ + position?: ResponsivePosition | ObjectPosition; + /** Margin space around the positioned content */ + margin?: ResponsiveSpace | Spacing; + /** Padding space around the positioned content */ + padding?: ResponsiveSpace | Spacing; + /** Sets the HTML tag for the container element */ + tag?: string; +} \ No newline at end of file