UNPKG

@reusable-ui/hamburger-menu-button

Version:

A toggleable hamburger button for showing/hiding menu in Navbar.

193 lines (192 loc) 8.3 kB
// cssfn: import { // writes css in javascript: rule, states, fallback, style, vars, cssVars, } from '@cssfn/core'; // writes css in javascript // reusable-ui features: import { // hooks: useAnimatingState, // animation stuff of UI: usesAnimation, } from '@reusable-ui/core'; // a set of reusable-ui packages which are responsible for building any component const [hamburgerableVars] = cssVars(); // no need to have SSR support because the variables are not shared externally (outside <HamburgerMenuButton>) // .crossed will be added after crossing-animation done: const selectorIfCrossed = '.crossed'; // .crossing = styled crossing: const selectorIfCrossing = '.crossing'; // .hamburgering will be added after loosing cross(ing|ed) and will be removed after hamburgering-animation done: const selectorIfHamburgering = '.hamburgering'; // if all above are not set => hamburgered: const selectorIfHamburgered = ':not(:is(.crossed, .crossing, .hamburgering))'; export const ifCrossed = (styles) => rule(selectorIfCrossed, styles); export const ifCrossing = (styles) => rule(selectorIfCrossing, styles); export const ifHamburgering = (styles) => rule(selectorIfHamburgering, styles); export const ifHamburgered = (styles) => rule(selectorIfHamburgered, styles); export const ifCross = (styles) => rule([selectorIfCrossing, selectorIfCrossed], styles); export const ifHamburger = (styles) => rule([selectorIfHamburgering, selectorIfHamburgered], styles); export const ifCrossHamburgering = (styles) => rule([selectorIfCrossing, selectorIfCrossed, selectorIfHamburgering], styles); /** * Uses hamburger animation. * @param config A configuration of `hamburgerableRule`. * @returns A `HamburgerableStuff` represents a hamburgerable state. */ export const usesHamburgerable = (config) => { // dependencies: // features: const { animationRule, animationVars } = usesAnimation(); // css vars: const transformNoneVars = () => vars({ [hamburgerableVars.topTransformIn]: animationVars.transformNone, [hamburgerableVars.midTransformIn]: animationVars.transformNone, [hamburgerableVars.btmTransformIn]: animationVars.transformNone, [hamburgerableVars.topTransformOut]: animationVars.transformNone, [hamburgerableVars.midTransformOut]: animationVars.transformNone, [hamburgerableVars.btmTransformOut]: animationVars.transformNone, }); const transformInVars = () => vars({ [hamburgerableVars.topTransformIn]: config?.hamburgerTopTransformIn, [hamburgerableVars.midTransformIn]: config?.hamburgerMidTransformIn, [hamburgerableVars.btmTransformIn]: config?.hamburgerBtmTransformIn, }); const transformOutVars = () => vars({ [hamburgerableVars.topTransformOut]: config?.hamburgerTopTransformOut, [hamburgerableVars.midTransformOut]: config?.hamburgerMidTransformOut, [hamburgerableVars.btmTransformOut]: config?.hamburgerBtmTransformOut, }); const animNoneVars = () => vars({ [hamburgerableVars.topAnim]: animationVars.animNone, [hamburgerableVars.midAnim]: animationVars.animNone, [hamburgerableVars.btmAnim]: animationVars.animNone, }); const animInVars = () => vars({ [hamburgerableVars.topAnim]: config?.hamburgerTopAnimIn, [hamburgerableVars.midAnim]: config?.hamburgerMidAnimIn, [hamburgerableVars.btmAnim]: config?.hamburgerBtmAnimIn, }); const animOutVars = () => vars({ [hamburgerableVars.topAnim]: config?.hamburgerTopAnimOut, [hamburgerableVars.midAnim]: config?.hamburgerMidAnimOut, [hamburgerableVars.btmAnim]: config?.hamburgerBtmAnimOut, }); return { hamburgerableRule: () => style({ // features: ...animationRule(), // reset functions: // declare default values at lowest specificity: ...fallback({ ...transformNoneVars(), ...animNoneVars(), }), // animation states: ...states([ ifCrossed({ ...transformInVars(), }), ifCrossing({ ...transformInVars(), ...transformOutVars(), ...animInVars(), }), ifHamburgering({ ...transformInVars(), ...transformOutVars(), ...animOutVars(), }), ifHamburgered({ ...transformOutVars(), }), ]), // compositions: ...vars({ [hamburgerableVars.topTransform]: [[ // combining: transform1 * transform2 * transform3 ... // back-to-front order, the last is processed first, the first is processed last hamburgerableVars.topTransformIn, hamburgerableVars.topTransformOut, ]], [hamburgerableVars.midTransform]: [[ // combining: transform1 * transform2 * transform3 ... // back-to-front order, the last is processed first, the first is processed last hamburgerableVars.midTransformIn, hamburgerableVars.midTransformOut, ]], [hamburgerableVars.btmTransform]: [[ // combining: transform1 * transform2 * transform3 ... // back-to-front order, the last is processed first, the first is processed last hamburgerableVars.btmTransformIn, hamburgerableVars.btmTransformOut, ]], }), }), hamburgerableVars, }; }; export const useHamburgerable = (isActive) => { // fn states: /* * state is hamburgered/crossed based on [controllable crossed = isActive] * [uncontrollable crossed] is not supported */ const crossedFn = isActive /*controllable*/; // states: const [crossed, setCrossed, animation, { handleAnimationStart, handleAnimationEnd, handleAnimationCancel }] = useAnimatingState({ initialState: crossedFn, animationName: /((^|[^a-z])(hamburgerout|hamburgerin)|([a-z])(Hamburgerout|Hamburgerin))(?![a-z])/, }); // update state: if (crossed !== crossedFn) { // change detected => apply the change & start animating setCrossed(crossedFn); // remember the last change } // if // fn props: const state = (() => { // crossing: if (animation === true) return 2 /* HamburgerableState.Crossing */; // hamburgering: if (animation === false) return 1 /* HamburgerableState.Hamburgering */; // fully crossed: if (crossed) return 3 /* HamburgerableState.Crossed */; // fully hamburgered: return 0 /* HamburgerableState.Hamburgered */; })(); const stateClass = (() => { switch (state) { // crossing: case 2 /* HamburgerableState.Crossing */: { return 'crossing'; } ; // hamburgering: case 1 /* HamburgerableState.Hamburgering */: { return 'hamburgering'; } ; // fully crossed: case 3 /* HamburgerableState.Crossed */: { return 'crossed'; } ; // fully hamburgered: case 0 /* HamburgerableState.Hamburgered */: { return null; // discard all classes above } ; } // switch })(); // api: return { active: crossed, state: state, class: stateClass, handleAnimationStart, handleAnimationEnd, handleAnimationCancel, }; }; //#endregion hamburgerable