Complete implementing popover system main features (#131)

* Make popover buttons open popover menus and add placeholder messages to all of them.
* Implement all directions for drawing, aligning, and edge-clamping popovers.
* Fix popovers so they are drawn outside their parent panel bounds and not clipped.
* Fix popover HTML to avoid nesting it inside a <button> element.
This commit is contained in:
Keavon Chambers 2021-05-21 12:22:30 -07:00 committed by GitHub
parent 4ed093bb4b
commit c8eea1e4b1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 204 additions and 66 deletions

View file

@ -21,6 +21,22 @@ button {
line-height: 1;
color: #ddd;
}
// For placeholder messages (remove eventually)
.popover {
h1,
h2,
h3,
h4,
h5,
h6,
p {
margin: 0;
}
p {
margin-top: 8px;
}
}
</style>
<script lang="ts">

View file

@ -12,13 +12,19 @@
<IconButton :size="24" title="Vertical Align Top"><AlignVerticalTop /></IconButton>
<IconButton :size="24" title="Vertical Align Center"><AlignVerticalCenter /></IconButton>
<IconButton :size="24" title="Vertical Align Bottom"><AlignVerticalBottom /></IconButton>
<DropdownButton />
<DropdownButton>
<h3>Align</h3>
<p>More alignment-related buttons will be here</p>
</DropdownButton>
<ItemDivider />
<IconButton :size="24" title="Flip Horizontal"><FlipHorizontal /></IconButton>
<IconButton :size="24" title="Flip Vertical"><FlipVertical /></IconButton>
<DropdownButton />
<DropdownButton>
<h3>Flip</h3>
<p>More flip-related buttons will be here</p>
</DropdownButton>
<ItemDivider />
@ -27,7 +33,10 @@
<IconButton :size="24" title="Boolean Subtract Back"><BooleanSubtractBack /></IconButton>
<IconButton :size="24" title="Boolean Intersect"><BooleanIntersect /></IconButton>
<IconButton :size="24" title="Boolean Difference"><BooleanDifference /></IconButton>
<DropdownButton />
<DropdownButton>
<h3>Boolean</h3>
<p>More boolean-related buttons will be here</p>
</DropdownButton>
</div>
<div class="spacer"></div>
<div class="right side">
@ -35,7 +44,10 @@
<IconButton :size="24" title="View Mode: Normal"><ViewModeNormal /></IconButton>
<IconButton :size="24" title="View Mode: Outline"><ViewModeOutline /></IconButton>
<IconButton :size="24" title="View Mode: Pixels"><ViewModePixels /></IconButton>
<DropdownButton />
<DropdownButton>
<h3>Display Mode</h3>
<p>More display mode options will be here</p>
</DropdownButton>
</RadioPicker>
<ItemDivider />
@ -147,6 +159,7 @@ import { ResponseType, registerResponseHandler, Response, UpdateCanvas, SetActiv
import LayoutRow from "../layout/LayoutRow.vue";
import LayoutCol from "../layout/LayoutCol.vue";
import WorkingColors from "../widgets/WorkingColors.vue";
import { PopoverDirection } from "../widgets/PopoverMount.vue";
import ShelfItem from "../widgets/ShelfItem.vue";
import ItemDivider from "../widgets/ItemDivider.vue";
import IconButton from "../widgets/IconButton.vue";
@ -304,6 +317,7 @@ export default defineComponent({
return {
viewportSvg: "",
activeTool: "Select",
PopoverDirection,
};
},
});

View file

@ -3,7 +3,10 @@
<LayoutRow :class="'options-bar'">
<NumberInput />
<NumberInput />
<DropdownButton />
<DropdownButton>
<h3>Compositing Options</h3>
<p>More blend and compositing options will be here</p>
</DropdownButton>
</LayoutRow>
<LayoutRow :class="'layer-tree'">
<LayoutCol :class="'list'">
@ -78,6 +81,7 @@ import LayoutRow from "../layout/LayoutRow.vue";
import LayoutCol from "../layout/LayoutCol.vue";
import NumberInput from "../widgets/NumberInput.vue";
import DropdownButton from "../widgets/DropdownButton.vue";
import { PopoverDirection } from "../widgets/PopoverMount.vue";
import IconButton from "../widgets/IconButton.vue";
import IconContainer from "../widgets/IconContainer.vue";
import EyeVisible from "../../../assets/svg/24x24-bounds-16x16-icon/visibility-eye-visible.svg";
@ -122,6 +126,7 @@ export default defineComponent({
},
data() {
return {
PopoverDirection,
layers: [] as Array<LayerPanelEntry>,
};
},

View file

@ -1,25 +1,41 @@
<template>
<button class="dropdown-button">
<component :is="icon" />
</button>
<div class="dropdown-button">
<button @click="clickButton">
<component :is="icon" />
</button>
<PopoverMount :direction="PopoverDirection.Bottom" ref="popover">
<slot></slot>
</PopoverMount>
</div>
</template>
<style lang="scss">
.dropdown-button {
display: inline-block;
position: relative;
width: 16px;
height: 24px;
margin: 2px 4px;
padding: 0;
outline: none;
border: none;
border-radius: 2px;
vertical-align: top;
background: #111;
fill: #ddd;
&:hover {
background: #666;
fill: #fff;
.popover-mount {
left: 50%;
}
button {
width: 100%;
height: 100%;
padding: 0;
outline: none;
border: none;
border-radius: 2px;
vertical-align: top;
background: #111;
fill: #ddd;
&:hover {
background: #666;
fill: #fff;
}
}
}
</style>
@ -28,6 +44,7 @@
import { defineComponent } from "vue";
import DropdownArrow from "../../../assets/svg/16x24-bounds-8x16-icon/dropdown-arrow.svg";
import VerticalEllipsis from "../../../assets/svg/16x24-bounds-8x16-icon/vertical-ellipsis.svg";
import PopoverMount, { PopoverDirection } from "./PopoverMount.vue";
export enum DropdownButtonIcon {
"DropdownArrow" = "DropdownArrow",
@ -38,9 +55,21 @@ export default defineComponent({
components: {
VerticalEllipsis,
DropdownArrow,
PopoverMount,
PopoverDirection,
},
props: {
icon: { type: String, default: DropdownButtonIcon.DropdownArrow },
},
methods: {
clickButton() {
(this.$refs.popover as typeof PopoverMount).setOpen();
},
},
data() {
return {
PopoverDirection,
};
},
});
</script>

View file

@ -1,7 +1,7 @@
<template>
<div class="popover-mount" v-if="open">
<div class="tail left"></div>
<div class="popover">
<div class="popover-mount" :class="direction.toLowerCase()" v-if="open">
<div class="tail"></div>
<div class="popover" ref="popover">
<div class="popover-content" ref="popoverContent">
<slot></slot>
</div>
@ -15,46 +15,75 @@
width: 0;
height: 0;
display: flex;
// Overlays begin at a z-index of 1000
z-index: 1000;
&.top,
&.bottom {
flex-direction: column;
}
}
.tail {
width: 0;
height: 0;
border-style: solid;
// Put the tail above the popover's shadow
z-index: 1;
// Draw over the application without being clipped by the containing panel's `overflow: hidden`
position: fixed;
&.top {
border-width: 0 6px 8px 6px;
border-color: transparent transparent #222222e6 transparent;
margin-left: -6px;
margin-top: 2px;
}
&.bottom {
.top > & {
border-width: 8px 6px 0 6px;
border-color: #222222e6 transparent transparent transparent;
margin-left: -6px;
margin-bottom: 2px;
}
&.left {
border-width: 6px 8px 6px 0;
border-color: transparent #222222e6 transparent transparent;
margin-top: -6px;
margin-left: 2px;
.bottom > & {
border-width: 0 6px 8px 6px;
border-color: transparent transparent #222222e6 transparent;
margin-left: -6px;
margin-top: 2px;
}
&.right {
.left > & {
border-width: 6px 0 6px 8px;
border-color: transparent transparent transparent #222222e6;
margin-top: -6px;
margin-right: 2px;
}
.right > & {
border-width: 6px 8px 6px 0;
border-color: transparent #222222e6 transparent transparent;
margin-top: -6px;
margin-left: 2px;
}
}
.popover {
display: flex;
align-items: center;
.top > & {
justify-content: center;
margin-bottom: 10px;
}
.bottom > & {
justify-content: center;
margin-top: 10px;
}
.left > & {
align-items: center;
margin-right: 10px;
}
.right > & {
align-items: center;
margin-left: 10px;
}
.popover-content {
background: #222222e6;
@ -65,8 +94,9 @@
padding: 8px;
z-index: 0;
display: flex;
// This `position: relative` is used to allow `top`/`right`/`bottom`/`left` properties to shift the content back from overflowing the workspace
position: relative;
flex-direction: column;
// Draw over the application without being clipped by the containing panel's `overflow: hidden`
position: fixed;
}
}
</style>
@ -74,24 +104,49 @@
<script lang="ts">
import { defineComponent } from "vue";
export enum PopoverDirection {
Top = "Top",
Bottom = "Bottom",
Left = "Left",
Right = "Right",
}
export default defineComponent({
components: {},
props: {
direction: { type: String, default: PopoverDirection.Bottom },
},
data() {
return {
open: false,
mouseStillDown: false,
PopoverDirection,
};
},
updated() {
const popoverContent = this.$refs.popoverContent as HTMLElement;
const popover = this.$refs.popover as HTMLElement;
const workspace = document.querySelector(".workspace");
if (popoverContent && workspace) {
const workspaceBounds = workspace.getBoundingClientRect();
const popoverBounds = popoverContent.getBoundingClientRect();
const bottomOffset = workspaceBounds.bottom - popoverBounds.bottom - 8;
if (bottomOffset < 0) popoverContent.style.top = `${bottomOffset}px`;
if (this.direction === PopoverDirection.Left || this.direction === PopoverDirection.Right) {
const topOffset = popoverBounds.top - workspaceBounds.top - 8;
if (topOffset < 0) popover.style.transform = `translate(0, ${-topOffset}px)`;
const bottomOffset = workspaceBounds.bottom - popoverBounds.bottom - 8;
if (bottomOffset < 0) popover.style.transform = `translate(0, ${bottomOffset}px)`;
}
if (this.direction === PopoverDirection.Top || this.direction === PopoverDirection.Bottom) {
const leftOffset = popoverBounds.left - workspaceBounds.left - 8;
if (leftOffset < 0) popover.style.transform = `translate(${-leftOffset}px, 0)`;
const rightOffset = workspaceBounds.right - popoverBounds.right - 8;
if (rightOffset < 0) popover.style.transform = `translate(${rightOffset}px, 0)`;
}
}
},
methods: {

View file

@ -6,7 +6,8 @@
<style lang="scss">
.radio-picker {
button {
button,
.dropdown-button {
fill: #ddd;
border-radius: 0;
margin: 0;

View file

@ -1,16 +1,18 @@
<template>
<div class="working-colors">
<div class="swatch-pair">
<button @click="clickSecondarySwatch" class="secondary swatch" style="background: white">
<PopoverMount ref="secondarySwatchPopover">
<div class="secondary swatch">
<button @click="clickSecondarySwatch" style="background: white"></button>
<PopoverMount :direction="PopoverDirection.Right" horizontal ref="secondarySwatchPopover">
<ColorPicker />
</PopoverMount>
</button>
<button @click="clickPrimarySwatch" class="primary swatch" style="background: black">
<PopoverMount ref="primarySwatchPopover">
</div>
<div class="primary swatch">
<button @click="clickPrimarySwatch" style="background: black"></button>
<PopoverMount :direction="PopoverDirection.Right" horizontal ref="primarySwatchPopover">
<ColorPicker />
</PopoverMount>
</button>
</div>
</div>
<div class="swap-and-reset">
<IconButton :size="16">
@ -32,24 +34,31 @@
}
.swatch {
width: 24px;
height: 24px;
border-radius: 50%;
border: 2px #888 solid;
box-shadow: 0 0 0 2px #333;
width: 28px;
height: 28px;
margin: 2px;
padding: 0;
box-sizing: unset;
outline: none;
position: relative;
.popover-mount {
right: -4px;
button {
width: 100%;
height: 100%;
border-radius: 50%;
border: 2px #888 solid;
box-shadow: 0 0 0 2px #333;
margin: 0;
padding: 0;
box-sizing: border-box;
outline: none;
}
}
.primary.swatch {
margin-bottom: -8px;
.popover-mount {
top: 50%;
right: -2px;
}
&.primary {
margin-bottom: -8px;
}
}
.swap-and-reset {
@ -60,7 +69,7 @@
<script lang="ts">
import { defineComponent } from "vue";
import PopoverMount from "./PopoverMount.vue";
import PopoverMount, { PopoverDirection } from "./PopoverMount.vue";
import ColorPicker from "../popovers/ColorPicker.vue";
import IconButton from "./IconButton.vue";
import SwapButton from "../../../assets/svg/16x16-bounds-12x12-icon/swap.svg";
@ -84,5 +93,10 @@ export default defineComponent({
(this.$refs.primarySwatchPopover as typeof PopoverMount).setClosed();
},
},
data() {
return {
PopoverDirection,
};
},
});
</script>

View file

@ -9,7 +9,10 @@
</IconButton>
</div>
</div>
<DropdownButton :icon="DropdownButtonIcon.VerticalEllipsis" />
<DropdownButton :icon="DropdownButtonIcon.VerticalEllipsis">
<h3>Panel Options</h3>
<p>More panel-related options will be here</p>
</DropdownButton>
</div>
<div class="panel-body">
<component :is="panelType" />
@ -54,17 +57,16 @@
border-radius: 8px 8px 0 0;
position: relative;
&::before,
&:not(:first-child)::before,
&::after {
content: "";
width: 16px;
height: 8px;
position: absolute;
bottom: 0;
box-shadow: #333;
}
&::before {
&:not(:first-child)::before {
left: -16px;
border-bottom-right-radius: 8px;
box-shadow: 8px 0 0 0 #333;
@ -135,6 +137,7 @@ import LayerTree from "../panels/LayerTree.vue";
import Minimap from "../panels/Minimap.vue";
import IconButton from "../widgets/IconButton.vue";
import DropdownButton, { DropdownButtonIcon } from "../widgets/DropdownButton.vue";
import { PopoverDirection } from "../widgets/PopoverMount.vue";
import VerticalEllipsis from "../../../assets/svg/16x24-bounds-8x16-icon/vertical-ellipsis.svg";
import CloseX from "../../../assets/svg/16x16-bounds-12x12-icon/close-x.svg";
@ -159,6 +162,7 @@ export default defineComponent({
data() {
return {
DropdownButtonIcon,
PopoverDirection,
};
},
});