root-container

PreviousNext
Docs
limeplayui

Preview

Loading preview…
registry/default/ui/root-container.tsx
"use client"

import { composeRefs } from "@radix-ui/react-compose-refs"
import React from "react"

import { cn } from "@/lib/utils"
import { useMediaStore } from "@/registry/default/ui/media-provider"

export interface RootContainerProps
  extends React.ComponentPropsWithoutRef<"div"> {
  /**
   * Height in pixels for aspect ratio calculation
   */
  height?: number
  /**
   * Width in pixels for aspect ratio calculation
   */
  width?: number
}

export type RootContainerPropsDocs = Pick<
  RootContainerProps,
  "height" | "width"
>

export const RootContainer = React.forwardRef<
  HTMLDivElement,
  RootContainerProps
>((props, forwardedRef) => {
  const { children, className, height, width, ...etc } = props
  const idle = useMediaStore((state) => state.idle)
  const forceIdle = useMediaStore((state) => state.forceIdle)
  const setIdle = useMediaStore((state) => state.setIdle)
  const status = useMediaStore((state) => state.status)
  const debug = useMediaStore((state) => state.debug)

  const setPlayerContainerRef = useMediaStore(
    (state) => state.setPlayerContainerRef
  )
  const aspectRatio = React.useMemo(
    () => calculateAspectRatio(width, height),
    [height, width]
  )
    ?.split(":")
    .join("/")

  return (
    <div
      aria-label="Media player"
      className={cn(
        className,
        `
          group/root aspect-(--aspect-ratio)
          focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary/50
        `
      )}
      data-idle={debug || forceIdle ? "false" : idle}
      data-layout-type="root-container"
      data-status={status}
      onBlur={() => {
        if (!forceIdle) {
          setIdle(true)
        }
      }}
      onFocus={() => {
        if (!forceIdle) {
          setIdle(false)
        }
      }}
      onPointerEnter={() => {
        if (!forceIdle) {
          setIdle(false)
        }
      }}
      onPointerLeave={() => {
        if (!forceIdle) {
          setIdle(true)
        }
      }}
      onPointerMove={() => {
        if (!forceIdle) {
          setIdle(false)
        }
      }}
      // Show controls like tabbing over
      // onKeyDown={}
      // onPointerMove show controls again
      // onPointerUp IDK yet, but probably
      // onPointerLeave hide controls now, maybe with timeout?
      // onFocus show controls and set focus true so that keyboard shortcuts are triggered
      // onBlur hide controls and set focus to false
      onPointerUp={() => {
        if (!forceIdle) {
          setIdle(false)
        }
      }}
      ref={composeRefs(forwardedRef, setPlayerContainerRef)}
      role="region"
      style={{
        ["--aspect-ratio" as string]: aspectRatio,
        ["--height" as string]: height,
        ["--width" as string]: width,
      }}
      {...etc}
    >
      {children}
    </div>
  )
})

RootContainer.displayName = "RootContainer"

function calculateAspectRatio(width?: number, height?: number) {
  if (width && height) {
    const gcd = (a: number, b: number): number => {
      return b === 0 ? a : gcd(b, a % b)
    }
    const divisor = gcd(width, height)
    const aspectWidth = width / divisor
    const aspectHeight = height / divisor
    return `${aspectWidth}:${aspectHeight}`
  }
}

Installation

npx shadcn@latest add @limeplay/root-container

Usage

import { RootContainer } from "@/components/ui/root-container"
<RootContainer />