UNPKG

@reusable-ui/button

Version:

A button component for initiating an action.

368 lines (250 loc) 10.6 kB
// cssfn: import { // writes css in javascript: rule, variants, states, fallback, style, vars, // strongly typed of css variables: cssVars, switchOf, // reads/writes css variables configuration: usesCssProps, usesPrefixedProps, // writes complex stylesheets in simpler way: watchChanges, memoizeStyle, } from '@cssfn/core' // writes css in javascript // reusable-ui core: import { // a border (stroke) management system: borderRadiuses, // a spacer (gap) management system: spacers, // a typography management system: typos, // border (stroke) stuff of UI: usesBorder, // padding (inner spacing) stuff of UI: usesPadding, // a capability of UI to rotate its layout: OrientationableOptions, usesOrientationable, // size options of UI: usesResizable, // gradient variant of UI: usesGradientable, setGradient, // outlined (background-less) variant of UI: usesOutlineable, // adds an interactive feel to a UI: ifArrive, ifLeave, // shows the UI as clicked when activated: usesActiveAsClick, } from '@reusable-ui/core' // a set of reusable-ui packages which are responsible for building any component // reusable-ui components: import { // configs: basics, } from '@reusable-ui/basic' // a base component import { // styles: onActionControlStylesChange, usesActionControlLayout, usesActionControlVariants, usesActionControlStates, } from '@reusable-ui/action-control' // a base component // internals: import { // defaults: defaultOrientationableOptions, } from '../defaults.js' import { // configs: buttons, cssButtonConfig, } from './config.js' // variables: interface CondBorderVars { condBorderWidthTg : any } const [condBorderVars] = cssVars<CondBorderVars>(); // no need to have SSR support because the variables are not shared externally (outside <ProgressBar>) // utility styles: const usesAppearAsOutlined = () => { // dependencies: // features: const {borderVars } = usesBorder(); // variants: const {outlineableVars} = usesOutlineable(); return style({ // compositions: ...vars({ [borderVars.border ] : [[ borderVars.borderStyle, switchOf( condBorderVars.condBorderWidthTg, // border is supported '0px', // border not supported ), borderVars.borderColor, ]], }), // toggling functions: ...vars({ [condBorderVars.condBorderWidthTg] : [[ outlineableVars.outlinedSw, // border is supported only if `.outlined` borderVars.borderWidth, ]], }), // toggling functions: ...vars({ [outlineableVars.noBackgTg] : [[ 'transparent', // the no background color definition ]], [outlineableVars.backgTg] : [[ outlineableVars.backgFn, // the outlined background color definition ]], [outlineableVars.foregTg] : [[ outlineableVars.foregFn, // the outlined foreground color definition ]], [outlineableVars.altBackgTg] : [[ outlineableVars.altBackgFn, // the outlined alternate background color definition ]], [outlineableVars.altForegTg] : [[ outlineableVars.altForegFn, // the outlined alternate foreground color definition ]], }), }); }; const usesButtonLinkVariant = () => { // dependencies: // features: const {borderVars } = usesBorder(); const {paddingVars } = usesPadding(); // variants: const {gradientableVars} = usesGradientable(); const {outlineableVars } = usesOutlineable(); return style({ // layouts: ...style({ // accessibilities: userSelect : 'contain', // a link should be selectable ...fallback({ userSelect : 'text', // a link should be selectable }), // borders: // small rounded corners on top: [borderVars.borderStartStartRadius] : borderRadiuses.sm, [borderVars.borderStartEndRadius ] : borderRadiuses.sm, // small rounded corners on bottom: [borderVars.borderEndStartRadius ] : borderRadiuses.sm, [borderVars.borderEndEndRadius ] : borderRadiuses.sm, // spacings: [paddingVars.paddingInline] : spacers.xs, [paddingVars.paddingBlock ] : `max(0px, ${spacers.xs} - (0.5em * (${switchOf(basics.lineHeight, typos.lineHeight)} - 1)))`, marginInline : `calc(0px - ${paddingVars.paddingInline})`, // cancels out the padding with negative margin marginBlock : `calc(0px - ${paddingVars.paddingBlock })`, // cancels out the padding with negative margin // typos: textDecoration : 'underline', lineHeight : switchOf(basics.lineHeight, typos.lineHeight), // customize: ...usesCssProps(usesPrefixedProps(buttons, 'link')), // apply config's cssProps starting with link*** }), // toggling functions: ...vars({ [gradientableVars.backgGradTg] : [[ outlineableVars.outlinedSw, // gradient is supported only if `.outlined` gradientableVars.gradientSw, // the gradient switching function gradientableVars.backgGrad, // the gradient definition ]], }), }); }; const usesButtonGhostVariant = () => { return style({ // layouts: ...style({ // borders: boxShadow : ['none', '!important'], // no shadow & no focus animation // customize: ...usesCssProps(usesPrefixedProps(buttons, 'ghost')), // apply config's cssProps starting with ghost*** }), // states: ...states([ ifArrive({ // appearances: opacity: buttons.ghostOpacityArrive, // increase the opacity to increase visibility }), ifLeave( // backgrounds: setGradient(false), // hide the gradient to increase invisibility ), ]), }); }; // styles: export const onButtonStylesChange = watchChanges(onActionControlStylesChange, cssButtonConfig.onChange); export const usesButtonLayout = memoizeStyle((options?: OrientationableOptions) => { // options: const orientationableStuff = usesOrientationable(options, defaultOrientationableOptions); const {ifOrientationInline, ifOrientationBlock} = orientationableStuff; return style({ // layouts: ...usesActionControlLayout(), ...style({ // layouts: display : 'inline-flex', // use inline flexbox, so it takes the width & height as we set ...ifOrientationInline({ // inline flexDirection : 'row', // items are stacked horizontally }), ...ifOrientationBlock({ // block flexDirection : 'column', // items are stacked vertically }), justifyContent : 'center', // center items (text, icon, etc) horizontally alignItems : 'center', // center items (text, icon, etc) vertically flexWrap : 'wrap', // allows the items (text, icon, etc) to wrap to the next row if no sufficient width available // positions: verticalAlign : 'baseline', // <Button>'s text should be aligned with sibling text, so the <Button> behave like <span> wrapper // typos: textAlign : 'center', // customize: ...usesCssProps(buttons), // apply config's cssProps }), }); }, onButtonStylesChange); export const usesButtonVariants = memoizeStyle(() => { // dependencies: // variants: const {resizableRule} = usesResizable(buttons); return style({ // variants: /* write specific buttonStyle first, so it can be overriden by `.nude`, `.mild`, `.outlined`, etc */ ...variants([ rule(['.link', '.ghost'], usesAppearAsOutlined() ), rule( '.link' , usesButtonLinkVariant() ), rule( '.ghost' , usesButtonGhostVariant() ), ]), ...usesActionControlVariants(), ...resizableRule(), }); }, onButtonStylesChange); export const usesButtonStates = memoizeStyle(() => { // dependencies: // states: const {activeAsClickRule} = usesActiveAsClick(); return style({ // states: ...usesActionControlStates(), ...activeAsClickRule(), }); }, onButtonStylesChange); export default memoizeStyle(() => style({ // layouts: ...usesButtonLayout(), // variants: ...usesButtonVariants(), // states: ...usesButtonStates(), }), onButtonStylesChange);