JSPM

@qyu/reactcmp-dropdown

2.1.1
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 13
  • Score
    100M100P100Q76579F
  • License MIT

React Components for Dropdown Positioning

Package Exports

  • @qyu/reactcmp-dropdown
  • @qyu/reactcmp-dropdown/style/index.css
  • @qyu/reactcmp-dropdown/style/index.css.d.ts

Readme

@qyu/reactcmp-dropdown

React Component Library for solving dropdown positioning problems

Purpose

Library is intended as minimalistic solution for dropdown rendering

It supportas:

  • Nested Dropdpowns
  • List is rendered in Portal (no issues with overflow)
  • Basic focus management (for complex cases you would need external library)
  • Basic animations with css transitions (for complex animations you would need external library)

First include styles

// import styles with your bundler or copy them by hand and reference in your html
import "@qyu/reactcmp-dropdown/style/index"
// or import module and insert them directly to the component
import clmap from "@qyu/reactcmp-dropdown/stlye/index"

Basic usage

import * as ddn from "qyu/reactcmp-dropdown"

// will render dropdown
function DDN(props: { children: r.Children }) {
    // virtual container will not render any dom element, only create necessary contexts
    // there also a Component that renders a real one
    return <ddn.CmpContainerVirtual>
        <ddn.CmpButton>
            Press
        </ddn.CmpButton>

        <ddn.CmpListPortal
            gap={5}
            // find by id, you can pass HTLMElement directly alternatively
            portal={"ddnportal"}

            // optional
            direction={"ver"}
            // optional
            align={"start"}
            // optional
            justify={"end"}
            // optional
            stretch={"min"}
        >
            {/* Used to measure content. Required for any list */}
            <ddn.Content>
                {props.children}
            </ddn.Content>
        </ddn.CmpListPortal>
    </ddn.CmpContainer>
}

// nested dropdowns
function App() {
    // if you want to render nested dropdown 
    // - it will need its own context with <ddn.CmpContainerVirtual>
    // therefore - this works
    return <DDN>
        <DDN>
            List Content
        </DDN>
    </DDN>
}

Change Transition time and z-index

:root {
    --qyuddn-z-index: 3;
    --qyuddn-trtime: 0.2s;
}

Making your custom button

const CustomButton = function () {
    const ref = r.useRef<HTMLButtonElement | null>(null)
    const ctxstate_open = ddn.useCtxStateOpen()

    // this will give you calculated value of wether your dropdown is open or not
    const open = ddn.useOpenInfer({})

    // this wrapper is required, it registers necessary events and stuff
    return <ddn.ButtonVirtual target={ref}>
        <button 
            ref={ref} 
            onClick={() => ctxstate_open.open_set(o => !o)}
        >
            MyButton
        </button>
    </ddn.ButtonVirtual>
}

Usage with all properties

import * as ddn from "qyu/reactcmp-dropdown"

function App() {
    // it is generally recomended to use local state instead of declaring it here
    const [open, open_set] = r.useState(false)

    return <ddn.CmpContainer 
        // optional, will be added to other styles
        className={""}
        // here you can insert styles in modular form
        clmap={clmap}

        // optional, will use local state if not provided
        open={open}
        open_set={open_set}

        // on which events should close the dropdown {CloseEvt_ConfigFull}
        // optional, all true by default
        closeevents={{ 
            // focus going outside the container
            blur: true,
            // click anywhere outside
            click: true,
            // scroll of any parent
            scroll: true,
            // page resize
            resize: true,
            // press escape
            escape: true,
        }}

        // focus controls {Focus_Config}
        // optional, default values below
        focus={{
            // should capture focus
            capture: true,
            // focus options
            capture_options: { preventScroll: true },
            // should restore focus
            restore: true,
            // focus options
            restore_options: { preventScroll: true },
        }}

        // customize rendered element
        render_view={props => <div {...props} />}
    >
        <ddn.CmpButton 
            className={""}
            disabled={false}
            render_view={props => <button {...props} />}

            // if it should be the element list position is based around
            // if unset - applies automatically depending on the context (true for virtual container, false otherwise)
            isroot={false}
        >
            Press
        </ddn.CmpButton>

        {/* ListPortal renders inside a portal with position: fixed */}
        {/* There also ListAbs and ListFix. They share most of the properties. */}
        <ddn.CmpListPortal
            className={""}
            clmap={clmap}
            clmap_content={clmap}
            render_view={props => <div {...props} />}

            // specify the portal
            portal={"domid"}
            portal={document.getElementById("domid")}

            // disable types of transitions, default is false
            transition_nopos={false}
            transition_nosize={false}

            // do not render children when hidden, default is true
            lazy={true}

            // add gap between button and list, in pixels
            gap={5}

            // inverse direction default if "ver"
            direction
            // set direction directly
            direction={"hor" || "ver"}

            // inverse align default is "start"
            align
            // set align directly
            align={"start" || "end" || "center"}

            // inverse justify default is "end"
            justify
            // set justify directly
            justify={"end" || "start"}

            // stretch to min default is "none"
            stretch
            // set stretch directly
            // "min" stretches it to the size of the root if list is smaller
            // "strict" sets the size of the root to the list
            stretch={"none" | "min" | "strict"}

            // recalculate position on, optional
            // default values shown below
            rearrange={{
                // {any[] | undefined}
                deps: undefined,
                // rearrange when list is opened
                open: true,
                // rearrange on scroll
                scroll: true,
                // rearrange on window resize
                resize: true,
                // rearrange every animation frame
                // if you need to use it - you are probably doing something wrong
                polling: false,
            }}

            // when visibility changes
            event_visibility_chage={(visible: boolean) => {}}
            // when transition status changes
            event_transition_change={(property: string, active: true) => {}}
        >
            {/* Used to measure content. Required for any list */}
            <ddn.Content
                clmap={clmap}
                className={""}
                render_view={props => <div {...props} />}

                // dont render focusguards, false by default
                focus_noguards={false}
            >
                Your Content Here
            </ddn.Content>

            {() => {
                return <ddn.Content>
                    Your Content Here
                </ddn.Content>
            }}
        </ddn.CmpListPortal>
    </ddn.CmpContainer>
}