<div id="root"></div>
$shaft-color: #1d252b;
$door-color: #bbb;
$status-color: #e4b16c;
body {
background-color: black;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace;
}
#root {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
}
.elevator {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr;
gap: 0px 0px;
margin-bottom: var(--size-31);
width: 800px;
display: flex;
align-items: center;
justify-content: center;
}
.elevator-example {
display: inline-flex;
margin-bottom: var(--size-31);
&__status {
background-color: var(--color-black);
border-radius: 5px;
color: $status-color;
font-weight: 800;
margin: 0.5rem 0;
padding: 1rem;
}
&__button {
&--item {
appearance: none;
background-color: white;
border: 3px solid black;
border-radius: 50%;
cursor: pointer;
font-size: 1.2rem;
font-weight: 800;
height: 3rem;
margin: 1rem;
outline: none;
width: 3rem;
transition: all 200ms ease-out;
&:hover {
opacity: .8;
}
@media screen and (min-width: 800px) {
border: 5px solid black;
font-size: 1.8rem;
height: 4rem;
width: 4rem;
}
}
}
&__building {
background: black;
height: 500px;
margin: 0 auto;
overflow: hidden;
width: 300px;
}
&__shaft {
background: $shaft-color;
height: 500px;
margin: 0 auto;
position: relative;
width: 33.33%;
}
&__elevator {
bottom: 0;
height: 500px;
position: absolute;
width: 100%;
&--cab {
background-size: 100% 100%;
height: 20%;
overflow: hidden;
position: absolute;
width: 100%;
}
&--door {
background: $door-color;
height: 100%;
position: absolute;
top: 0;
transition: all 300ms ease-in;
width: 49%;
&.left {
left: 0;
}
&.right {
background: $door-color;
right: 0;
}
&.active-right {
right: -50%;
}
&.active-left {
left: -50%;
}
}
}
}
xxxxxxxxxx
import React, { Component, ReactElement, useState } from "https://esm.sh/react@18.0.0";
import ReactDOM from "https://esm.sh/react-dom@18.0.0";
type ElevatorProps = Record<string, unknown>
type ElevatorState = {
currentFloor: number
status: string
requestedFloor: string | number
previousStatus: string | null
requestedFloors: number[]
}
function timeout(ms: number): Promise<() => unknown> {
return new Promise((resolve) => setTimeout(resolve, ms))
}
async function sleep() {
await timeout(2000)
}
export const Status = {
IDLE: 'IDLE',
IDLE_PENDING: 'IDLE_PENDING',
MOVING_UP: 'MOVING_UP',
MOVING_DOWN: 'MOVING_DOWN',
}
export enum Direction {
UP = 'UP',
DOWN = 'DOWN',
}
class Elevator extends Component<ElevatorProps, ElevatorState> {
private readonly maxFloors: number
constructor(props: ElevatorProps) {
super(props)
this.state = {
currentFloor: 0,
status: Status.IDLE,
requestedFloor: 0,
previousStatus: Status.IDLE,
requestedFloors: [],
}
this.maxFloors = 2
this.setCurrentFloor = this.setCurrentFloor.bind(this)
this.moveElevator = this.moveElevator.bind(this)
}
setCurrentFloor(requestedFloor: number): void {
if (!this) return
const currentFloor =
requestedFloor > this?.state?.currentFloor
? this?.state?.currentFloor + 1
: this?.state?.currentFloor - 1
this.setState(
{
currentFloor,
status:
this.state.currentFloor === requestedFloor
? this.state.status
: Status.IDLE,
requestedFloor:
this.state.currentFloor === requestedFloor ? requestedFloor : '',
},
() => {
// If this isn't the floor that was requested then we'll move to the next one.
if (this.state.currentFloor !== requestedFloor) {
this.moveElevator(requestedFloor)
}
},
)
}
async moveElevator(requestedFloor: number): Promise<void> {
if (
this.state.status === Status.IDLE &&
this.state.currentFloor !== requestedFloor
) {
this.setState({
requestedFloor,
status:
requestedFloor !== this.state!.currentFloor &&
requestedFloor > this.state.currentFloor
? Status.MOVING_UP
: Status.MOVING_DOWN,
})
await sleep()
this.setCurrentFloor(requestedFloor)
}
}
render(): ReactElement {
return (
<div className="elevator row items-center">
<div className="elevator-controls">
<div
className="flex justify-center"
style={{ flexDirection: 'column' }}
>
<div className="elevator-example__status">
Current Floor:{' '}
{this.state.currentFloor === 0 ? 'G' : this.state.currentFloor}
</div>
<div className="elevator-example__status">
Current Status: {this.state.status}
</div>
<div className="elevator-example__status">
Requested Floor(s): {this.state.requestedFloor}
</div>
<div
className="elevator-example__button flex"
style={{ flexWrap: 'wrap' }}
>
<button
className="elevator-example__button--item flex items-center justify-center text-black disabled:text-gray-400"
onClick={() => this.moveElevator(0)}
disabled={this.state.status !== 'IDLE'}
>
G
</button>
<button
className="elevator-example__button--item flex items-center justify-center text-black disabled:text-gray-400"
onClick={() => this.moveElevator(1)}
disabled={this.state.status !== 'IDLE'}
>
1
</button>
<button
className="elevator-example__button--item flex items-center justify-center text-black disabled:text-gray-400"
onClick={() => this.moveElevator(2)}
disabled={this.state.status !== 'IDLE'}
>
2
</button>
</div>
</div>
</div>
<div className="elevator-example">
<div className="elevator-example__building" id="building">
<div className="elevator-example__shaft">
<div className="elevator-example__elevator">
<div
className="elevator-example__elevator--cab"
style={{
backgroundImage: `url("https://i.imgur.com/qU2zQAS.png")`,
bottom: `${
this.state.currentFloor * (this.maxFloors * 10) * 2
}%`,
}}
>
<div
className={`elevator-example__elevator--door left ${
this.state.status === 'IDLE' ||
this.state.status === 'IDLE_PENDING'
? 'active-left'
: ''
}`}
/>
<div
className={`elevator-example__elevator--door right ${
this.state.status === 'IDLE' ||
this.state.status === 'IDLE_PENDING'
? 'active-right'
: ''
}`}
/>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
}
ReactDOM.render(<Elevator />, document.getElementById('root'))
This Pen doesn't use any external CSS resources.