diff options
5 files changed, 167 insertions, 0 deletions
diff --git a/frontend/src/components/lobby/radial-list/RadialList.css b/frontend/src/components/lobby/radial-list/RadialList.css new file mode 100644 index 00000000..3b0f3a79 --- /dev/null +++ b/frontend/src/components/lobby/radial-list/RadialList.css @@ -0,0 +1,23 @@ +.radial-list-container { + margin: 0; + padding: 0; + position: relative; +} + +.radial-list { + margin: 0; + padding: 0; + transition: all 500ms ease-in-out; + z-index: 1; +} + +.radial-list-center { + z-index: 0; +} + +.absolute-center { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); +} diff --git a/frontend/src/components/lobby/radial-list/RadialList.jsx b/frontend/src/components/lobby/radial-list/RadialList.jsx new file mode 100644 index 00000000..95ed8d17 --- /dev/null +++ b/frontend/src/components/lobby/radial-list/RadialList.jsx @@ -0,0 +1,65 @@ +//@flow +import * as React from 'react'; +import type { CartesianCoords, RadialConfig } from './radial-math'; +import { cartesianOffsets, CLOCKWISE, COUNTERCLOCKWISE } from './radial-math'; +import './RadialList.css'; +import { RadialListItem } from './RadialListItem'; + +type RadialListProps = { + items: Array<React.Node>, + centerElement?: React.Node, + diameter?: number, // 240px by default + offsetDegrees?: number, // defaults to 0 = 12 o'clock + arc?: number, // defaults to 360 (full circle) + clockwise?: boolean, // defaults to 360 (full circle) + itemWidth?: number, + itemHeight?: number, +}; + +export const RadialList = ({items, centerElement, diameter = 240, offsetDegrees = 0, arc = 360, clockwise = true, itemWidth = 20, itemHeight = 20}: RadialListProps) => { + const containerStyle = { + width: diameter + itemWidth, + height: diameter + itemHeight, + }; + const radius = diameter / 2; + const direction = clockwise ? CLOCKWISE : COUNTERCLOCKWISE; + const radialConfig: RadialConfig = {radius, arc, offsetDegrees, direction}; + + return <div className='radial-list-container' style={containerStyle}> + <RadialListItems items={items} radialConfig={radialConfig}/> + <RadialListCenter centerElement={centerElement}/> + </div>; +}; + +type RadialListItemsProps = { + items: Array<React.Node>, + radialConfig: RadialConfig, +}; + +const RadialListItems = ({items, radialConfig}: RadialListItemsProps) => { + const diameter = radialConfig.radius * 2; + const ulStyle = { + width: diameter, + height: diameter, + }; + const itemOffsets: Array<CartesianCoords> = cartesianOffsets(items.length, radialConfig); + + return <ul className='radial-list absolute-center' style={ulStyle}> + {items.map((item, i) => (<RadialListItem + key={i} + item={item} + offsets={itemOffsets[i]} + />))} + </ul>; +}; + +type RadialListCenterProps = { + centerElement?: React.Node, +}; + +const RadialListCenter = ({centerElement}: RadialListCenterProps) => { + if (!centerElement) { + return null; + } + return <div className='radial-list-center absolute-center'>{centerElement}</div>; +}; diff --git a/frontend/src/components/lobby/radial-list/RadialListItem.css b/frontend/src/components/lobby/radial-list/RadialListItem.css new file mode 100644 index 00000000..65bb9661 --- /dev/null +++ b/frontend/src/components/lobby/radial-list/RadialListItem.css @@ -0,0 +1,11 @@ +.radial-list-item { + display: block; + position: absolute; + top: 50%; + left: 50%; + margin: 0; + padding: 0; + list-style: unset; + transition: all 500ms ease-in-out; + z-index: 1; +} diff --git a/frontend/src/components/lobby/radial-list/RadialListItem.jsx b/frontend/src/components/lobby/radial-list/RadialListItem.jsx new file mode 100644 index 00000000..1d5df7f5 --- /dev/null +++ b/frontend/src/components/lobby/radial-list/RadialListItem.jsx @@ -0,0 +1,18 @@ +//@flow +import * as React from 'react'; +import type { CartesianCoords } from './radial-math'; +import './RadialListItem.css'; + +type RadialListItemProps = { + item: React.Node, + offsets: CartesianCoords, +}; + +export const RadialListItem = ({item, offsets}: RadialListItemProps) => { + // Y-axis points down, hence the minus sign + const liStyle = { + transform: `translate(${offsets.x}px, ${-offsets.y}px) translate(-50%, -50%)`, + }; + + return <li className='radial-list-item' style={liStyle}>{item}</li>; +}; diff --git a/frontend/src/components/lobby/radial-list/radial-math.js b/frontend/src/components/lobby/radial-list/radial-math.js new file mode 100644 index 00000000..4091a270 --- /dev/null +++ b/frontend/src/components/lobby/radial-list/radial-math.js @@ -0,0 +1,50 @@ +//@flow + +export type CartesianCoords = { + x: number, + y: number, +} +type CylindricalCoords = { + radius: number, + thetaDegrees: number, +} + +const toRad = (deg) => deg * (Math.PI / 180); +const roundedProjection = (radius, theta, trigFn) => Math.round(radius * trigFn(theta)); +const xProjection = (radius, theta) => roundedProjection(radius, theta, Math.cos); +const yProjection = (radius, theta) => roundedProjection(radius, theta, Math.sin); + +const toCartesian = ({radius, thetaDegrees}: CylindricalCoords): CartesianCoords => ({ + x: xProjection(radius, toRad(thetaDegrees)), + y: yProjection(radius, toRad(thetaDegrees)), +}); + +export type Direction = -1 | 1; +export const CLOCKWISE: Direction = -1; +export const COUNTERCLOCKWISE: Direction = 1; + +export type RadialConfig = {| + radius: number, + arc: number, + offsetDegrees: number, + direction: Direction, +|} +const DEFAULT_CONFIG: RadialConfig = { + radius: 120, + arc: 360, + offsetDegrees: 0, + direction: CLOCKWISE, +}; + +const DEFAULT_START = 90; // Up + +export function cartesianOffsets(nbItems: number, radialConfig: RadialConfig = DEFAULT_CONFIG): Array<CartesianCoords> { + return Array.from({length: nbItems}, (v, i) => itemCartesianOffsets(i, nbItems, radialConfig)); +} + +function itemCartesianOffsets(index: number, nbItems: number, {radius, arc, direction, offsetDegrees}: RadialConfig): CartesianCoords { + const startAngle = DEFAULT_START + direction * offsetDegrees; + const angleStep = arc / nbItems; + const itemAngle = startAngle + direction * angleStep * index; + return toCartesian({radius, thetaDegrees: itemAngle}); +} |