@reusable-ui/button
Version:
A button component for initiating an action.
221 lines (220 loc) • 8.16 kB
JavaScript
// 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, 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';
const [condBorderVars] = cssVars(); // 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'),
borderVars.borderColor,
]],
}),
// toggling functions:
...vars({
[condBorderVars.condBorderWidthTg]: [[
outlineableVars.outlinedSw,
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',
...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})`,
marginBlock: `calc(0px - ${paddingVars.paddingBlock})`,
// 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,
gradientableVars.gradientSw,
gradientableVars.backgGrad, // the gradient definition
]],
}),
});
};
const usesButtonGhostVariant = () => {
return style({
// layouts:
...style({
// borders:
boxShadow: ['none', '!important'],
// 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)),
]),
});
};
// styles:
export const onButtonStylesChange = watchChanges(onActionControlStylesChange, cssButtonConfig.onChange);
export const usesButtonLayout = memoizeStyle((options) => {
// options:
const orientationableStuff = usesOrientationable(options, defaultOrientationableOptions);
const { ifOrientationInline, ifOrientationBlock } = orientationableStuff;
return style({
// layouts:
...usesActionControlLayout(),
...style({
// layouts:
display: 'inline-flex',
...ifOrientationInline({
flexDirection: 'row', // items are stacked horizontally
}),
...ifOrientationBlock({
flexDirection: 'column', // items are stacked vertically
}),
justifyContent: 'center',
alignItems: 'center',
flexWrap: 'wrap',
// positions:
verticalAlign: 'baseline',
// 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);