Skip to content

Commit

Permalink
Merge pull request #27 from fleetbase/feature-drawer-component
Browse files Browse the repository at this point in the history
completed `<Drawer />` component feature and additional updates
  • Loading branch information
roncodes authored Nov 9, 2023
2 parents 83949a4 + 011974d commit df08d3c
Show file tree
Hide file tree
Showing 11 changed files with 431 additions and 5 deletions.
18 changes: 18 additions & 0 deletions addon/components/drawer.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<div role="dialog" aria-modal="true" class="next-drawer {{if this.isMinimized 'drawer-is-minimized'}} {{if this.isResizing 'drawer-is-resizing'}} {{if this.isOpen 'drawer-is-open'}} {{if this.noBackdrop 'drawer-no-backdrop'}} {{if @fullHeight 'drawer-full-height'}} {{@overlayClass}}" {{did-insert this.setupComponent}}>
{{#if this._rendered}}
<div class="next-drawer-panel-container {{@containerClass}}" {{did-insert (fn this.setupNode "drawerContainer")}}>
{{#if this.isResizable}}
<div class="gutter" {{did-insert (fn this.setupNode "gutter")}} {{on "mousedown" this.startResize}}>
{{#if @notchEnabled}}
<div class="notch" {{on "dblclick" this.toggleMinimize}}>
<span class="bar"></span>
<span class="bar"></span>
<span class="bar"></span>
</div>
{{/if}}
</div>
{{/if}}
<div class="next-drawer-panel" {{did-insert (fn this.setupNode "drawerPanel")}} {{set-height this.height}} ...attributes>{{yield this.context}}</div>
</div>
{{/if}}
</div>
176 changes: 176 additions & 0 deletions addon/components/drawer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { later } from '@ember/runloop';
import getWithDefault from '@fleetbase/ember-core/utils/get-with-default';

export default class DrawerComponent extends Component {
@tracked drawerNode;
@tracked drawerContainerNode;
@tracked drawerPanelNode;
@tracked gutterNode;
@tracked noBackdrop = true;
@tracked isOpen = true;
@tracked isResizable = true;
@tracked isResizing = false;
@tracked isMinimized = false;
@tracked mouseX = 0;
@tracked mouseY = 0;
@tracked height = 300;
@tracked _rendered = false;

context = {
toggle: this.toggle,
open: this.open,
close: this.close,
toggleMinimize: this.toggleMinimize,
minimize: this.minimize,
maximize: this.maximize,
isOpen: this.isOpen,
};

@action setupComponent(element) {
this.drawerNode = element;
this.height = getWithDefault(this.args, 'height', this.height);
this.isMinimized = getWithDefault(this.args, 'isMinimized', this.isMinimized);

later(
this,
() => {
this.isOpen = getWithDefault(this.args, 'isOpen', this.isOpen);
this.isResizable = getWithDefault(this.args, 'isResizable', this.isResizable);
this.noBackdrop = getWithDefault(this.args, 'noBackdrop', this.noBackdrop);

if (typeof this.args.onLoad === 'function') {
this.args.onLoad(this.context);
}

this._rendered = true;
},
300
);
}

@action setupNode(property, node) {
this[`${property}Node`] = node;
}

@action toggle() {
this.isOpen = !this.isOpen;
}

@action open() {
this.isOpen = true;
}

@action close() {
this.isOpen = false;
}

@action toggleMinimize() {
this.isMinimized = !this.isMinimized;
}

@action minimize() {
this.isMinimized = true;
}

@action maximize() {
this.isMinimized = false;
}

@action startResize(event) {
const disableResize = getWithDefault(this.args, 'disableResize', false);
const onResizeStart = getWithDefault(this.args, 'onResizeStart', null);
const { drawerPanelNode, isResizable } = this;

if (disableResize === true || !isResizable || !drawerPanelNode) {
return;
}

// if minimized undo
if (this.isMinimized) {
return this.maximize();
}

const bounds = drawerPanelNode.getBoundingClientRect();

// Set the overlay width/height
this.overlayWidth = bounds.width;
this.overlayHeight = bounds.height;

// Start resizing
this.isResizing = true;

// Get the current mouse position
this.mouseX = event.clientX;
this.mouseY = event.clientY;

// Attach the listeners
document.addEventListener('mousemove', this.resize);
document.addEventListener('mouseup', this.stopResize);

// Send up event
if (typeof onResizeStart === 'function') {
onResizeStart({ event, drawerPanelNode });
}
}

@action resize(event) {
const disableResize = getWithDefault(this.args, 'disableResize', false);
const onResize = getWithDefault(this.args, 'onResize', null);
const { drawerPanelNode, isResizable } = this;

if (disableResize === true || !isResizable || !drawerPanelNode) {
return;
}

const dy = event.clientY - this.mouseY;
const multiplier = -1;
const height = dy * multiplier + this.overlayHeight;
const minResizeHeight = getWithDefault(this.args, 'minResizeHeight', 0);
const maxResizeHeight = getWithDefault(this.args, 'maxResizeHeight', 600);

// Min resize height
if (height <= minResizeHeight) {
drawerPanelNode.style.height = `${minResizeHeight}px`;
return;
}

// Max resize height
if (height >= maxResizeHeight) {
drawerPanelNode.style.height = `${maxResizeHeight}px`;
return;
}

// Style changes
drawerPanelNode.style.userSelect = 'none';
drawerPanelNode.style.height = `${height}px`;
document.body.style.cursor = 'row-resize';

// Send callback
if (typeof onResize === 'function') {
onResize({ event, drawerPanelNode });
}
}

@action stopResize(event) {
const onResizeEnd = getWithDefault(this.args, 'onResizeEnd', null);
const { drawerPanelNode } = this;

// End resizing
this.isResizing = false;

// Remove style changes
document.body.style.removeProperty('cursor');
drawerPanelNode.style.userSelect = 'auto';

// Remove the handlers of `mousemove` and `mouseup`
document.removeEventListener('mousemove', this.resize);
document.removeEventListener('mouseup', this.stopResize);

if (typeof onResizeEnd === 'function') {
onResizeEnd({ event, drawerPanelNode });
}
}
}
5 changes: 5 additions & 0 deletions addon/components/table/cell/point.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{{#if this.isClickable}}
<a href="javascript:;" class={{@column.className}} {{on "click" this.onClick}}>{{this.display}}</a>
{{else}}
<span class={{@column.className}}>{{this.display}}</span>
{{/if}}
54 changes: 54 additions & 0 deletions addon/components/table/cell/point.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { isArray } from '@ember/array';
import { get, action } from '@ember/object';
import { later } from '@ember/runloop';

const isPoint = (point) => {
return point && typeof point === 'object' && isArray(point.coordinates);
};

export default class TableCellPointComponent extends Component {
@tracked display = '';
@tracked isClickable = false;

constructor(owner, { row, column }) {
super(...arguments);
this.isClickable = typeof column === 'object' && (typeof column.onClick === 'function' || typeof column.action === 'function');
this.displayPointFromRow(row, column);
}

displayPointFromRow(row, column) {
later(
this,
() => {
const pointColumn = column.valuePath;

if (pointColumn) {
const point = get(row, pointColumn);

if (isPoint(point)) {
this.display = `${point.coordinates[1]} ${point.coordinates[0]}`;
}
}
},
50
);
}

@action onClick() {
const column = this.args.column;

if (column) {
const { onClick, action } = column;

if (typeof onClick === 'function') {
onClick(this.args.row, ...arguments);
}

if (typeof action === 'function') {
action(this.args.row);
}
}
}
}
1 change: 1 addition & 0 deletions addon/styles/addon.css
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
@import 'components/kanban.css';
@import 'components/notification-tray.css';
@import 'components/fleet-listing.css';
@import 'components/drawer.css';
@import 'components/full-calendar.css';

/** Third party */
Expand Down
121 changes: 121 additions & 0 deletions addon/styles/components/drawer.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
.next-drawer {
@apply absolute inset-0 opacity-0 transition duration-300 ease-in-out pointer-events-auto bg-gray-900 bg-opacity-50;
width: 100%;
height: 100%;
z-index: 800;
}

.next-drawer.drawer-no-backdrop {
@apply bg-transparent pointer-events-none;
}

.next-drawer.drawer-no-backdrop > * {
@apply pointer-events-auto;
}

.next-drawer.drawer-is-open {
@apply opacity-100;
}

.next-drawer.drawer-is-open > .next-drawer-panel-container {
transform: translateY(0);
}

.next-drawer.drawer-is-minimized {
@apply bg-transparent pointer-events-none;
}

.next-drawer.drawer-is-minimized > * {
@apply pointer-events-auto;
}

.next-drawer.drawer-is-minimized > .next-drawer-panel-container > .next-drawer-panel {
height: 0px !important;
}

.next-drawer.drawer-is-minimized > .next-drawer-panel-container {
@apply pointer-events-auto;
}

.next-drawer > .next-drawer-panel-container {
@apply left-0 right-0 bottom-0;
transform: translateY(100%);
}

.next-drawer > .next-drawer-panel-container {
@apply absolute transform transition ease-in-out duration-500 pointer-events-auto;
}

.next-drawer > .next-drawer-panel-container > .next-drawer-panel {
@apply transform transition ease-in-out duration-500 pointer-events-auto bg-white shadow-next-nav;
}

.next-drawer.drawer-is-open .next-drawer-panel-container .next-drawer-panel {
@apply shadow-none;
}

body[data-theme='dark'] .next-drawer > .next-drawer-panel-container > .next-drawer-panel {
@apply bg-gray-900;
}

.next-drawer > .next-drawer-panel-container > .gutter {
height: auto;
width: 100%;
background-image: none;
background-color: inherit;
cursor: row-resize;
background-position: unset;
background-repeat: unset;
position: relative;
z-index: 9999;
@apply border-2 border-gray-200 border-solid;
}

body[data-theme="dark"] .next-drawer > .next-drawer-panel-container > .gutter {
@apply border-gray-500;
}

.next-drawer > .next-drawer-panel-container > .gutter:hover,
.next-drawer.drawer-is-resizing > .next-drawer-panel-container > .gutter,
body[data-theme="dark"] .next-drawer > .next-drawer-panel-container > .gutter:hover,
body[data-theme="dark"] .next-drawer.drawer-is-resizing > .next-drawer-panel-container > .gutter {
@apply border-sky-500 bg-opacity-75;
}

.next-drawer > .next-drawer-panel-container > .gutter > .notch {
position: absolute;
top: 0;
left: 0;
right: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 3.5rem;
height: 1.25rem;
margin-left: auto;
margin-right: auto;
margin-top: -11px;
z-index: 9999999;
padding: 0 0.75rem;
cursor: grab !important;
@apply bg-gray-200 border-2 border-white shadow-md drop-shadow-md rounded-lg;
}

body[data-theme="dark"] .next-drawer > .next-drawer-panel-container > .gutter > .notch {
@apply bg-gray-600 border-gray-600;
}

.next-drawer > .next-drawer-panel-container > .gutter > .notch > .bar {
display: block;
height: 0.0955555555rem;
width: 100%;
margin: 0.09rem 0;
border-radius: 2px;
cursor: grab !important;
@apply bg-gray-400;
}

body[data-theme='dark'] .next-drawer > .next-drawer-panel-container > .gutter > .notch > .bar {
@apply bg-gray-400;
}
Loading

0 comments on commit df08d3c

Please sign in to comment.