diff --git a/addon/components/drawer.hbs b/addon/components/drawer.hbs new file mode 100644 index 0000000..23c0ce0 --- /dev/null +++ b/addon/components/drawer.hbs @@ -0,0 +1,18 @@ +
\ No newline at end of file diff --git a/addon/components/drawer.js b/addon/components/drawer.js new file mode 100644 index 0000000..12076e2 --- /dev/null +++ b/addon/components/drawer.js @@ -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 }); + } + } +} diff --git a/addon/components/table/cell/point.hbs b/addon/components/table/cell/point.hbs new file mode 100644 index 0000000..029b3d2 --- /dev/null +++ b/addon/components/table/cell/point.hbs @@ -0,0 +1,5 @@ +{{#if this.isClickable}} + {{this.display}} +{{else}} + {{this.display}} +{{/if}} \ No newline at end of file diff --git a/addon/components/table/cell/point.js b/addon/components/table/cell/point.js new file mode 100644 index 0000000..9921d8e --- /dev/null +++ b/addon/components/table/cell/point.js @@ -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); + } + } + } +} diff --git a/addon/styles/addon.css b/addon/styles/addon.css index 75b2304..4faa8dc 100644 --- a/addon/styles/addon.css +++ b/addon/styles/addon.css @@ -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 */ diff --git a/addon/styles/components/drawer.css b/addon/styles/components/drawer.css new file mode 100644 index 0000000..b361717 --- /dev/null +++ b/addon/styles/components/drawer.css @@ -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; +} diff --git a/addon/styles/layout/next.css b/addon/styles/layout/next.css index eeea887..096f7e5 100644 --- a/addon/styles/layout/next.css +++ b/addon/styles/layout/next.css @@ -857,8 +857,9 @@ body[data-theme='dark'] .next-map-container-table-container { @apply outline-none; } +body[data-theme='dark'] .next-map-container-view-switch > button.active, .next-map-container-view-switch > button.active { - @apply bg-blue-500 text-white font-semibold; + @apply bg-sky-500 text-white font-semibold; } body[data-theme='dark'] .next-map-container-view-switch > button { @@ -871,10 +872,6 @@ body[data-theme='dark'] .next-map-container-view-switch > button:active { @apply outline-none; } -body[data-theme='dark'] .next-map-container-view-switch > button.active { - @apply bg-blue-500 text-white font-semibold; -} - .next-view-container { display: flex; flex-shrink: initial; diff --git a/app/components/drawer.js b/app/components/drawer.js new file mode 100644 index 0000000..a78f6ee --- /dev/null +++ b/app/components/drawer.js @@ -0,0 +1 @@ +export { default } from '@fleetbase/ember-ui/components/drawer'; diff --git a/app/components/table/cell/point.js b/app/components/table/cell/point.js new file mode 100644 index 0000000..62c696e --- /dev/null +++ b/app/components/table/cell/point.js @@ -0,0 +1 @@ +export { default } from '@fleetbase/ember-ui/components/table/cell/point'; diff --git a/tests/integration/components/drawer-test.js b/tests/integration/components/drawer-test.js new file mode 100644 index 0000000..b8100be --- /dev/null +++ b/tests/integration/components/drawer-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'dummy/tests/helpers'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Component | drawer', function (hooks) { + setupRenderingTest(hooks); + + test('it renders', async function (assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs`