Security News
GitHub Removes Malicious Pull Requests Targeting Open Source Repositories
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
@bodiless/fclasses
Advanced tools
The Bodiless FClasses Design API is designed to facilitate the implementation of a Design System in a React application. Before diving into the technical details below, it might make sense to read our high level overview of Design Systems in Bodiless to better understand the general patterns at work.
At a high level, this API expresses Design Tokens as React higher-order components, and provides utilities which allow you to apply them to both simple elements and compound components. In most cases, the design token HOC's leverage "atomic" or "functional" CSS, defining units of design as collections of utility classes.
A compound component using this API will expose a styling API (a design
prop) which
describes the UI elements of which it is composed. Consumers then supply a
list of higher-order components which should be applied to each element to modify
its appearence or behavior. The framework allows nested wrapping of components
to selectively extend or override individual elements. It also provides a tool
for adding and removing classes to/from individual elements.
Use of this API allows composed components to expose a styling API which remains consistent even when the internal markup of the component changes. Consumers of those components can then sustainably extend and re-extend their look and feel, with less danger of breakage when the underlying component changes.
This library was developed to support a styling paradigm known as "atomic" or "functional" CSS. There are many excellent web resources describing the goals and methodology of this pattern, but in its most basic form, it uses simple, single-purpose utility classes in lieu of complex CSS selectors. Thus, for example, instead of
<div class="my-wrapper">Foo</div>
.my-wrapper {
background-color: blue;
color: white;
}
the functional css paradigm favors
<div class="bg-blue text-white">Foo</div>
.bg-blue {
background-color: blue;
}
.text-white {
color: white;
}
Usually, a framework is used to generate the utility classes programmatically. Tachyons and Tailwind are two such frameworks. All the examples below use classes generated by Tailwind.
The FClasses
API in this library provides higher-order components which can be
used to add and remove classes from an element. They allow a single element
styled using functional utilty classes to be fully or partially restyled --
prserving some of its styles while adding or removing others. For example:
const Div = stylable<HTMLProps<HTMLDivElement>>('div');
const Callout = addClasses('bg-blue text-white p-2 border border-yellow')(Div);
const SpecialGreenCallout = flow(
addClasses('bg-green'),
removeClasses('bg-blue'),
)(Callout);
The higher order components are reusable, so for example:
const withRedCalloutBorder = flow(
addClasses('border-red'),
removeClasses('border-yellow),
);
const RedBorderedCallout = withRedCalloutBorder(Callout);
const ChristmasCallout = withRedCalloutBorder(SpecialGreenCallout);
and they can be composed using standard functional programming techniques:
const ChristmasCallout = flowRight(
withRedCalloutBorder,
asSpecialGreenCallout,
asCallout,
)('div');
stylable()
In order to use addClasses()
or removeClasses()
, the target component must
first be made stylable. That is:
const BlueDiv = addClasses('bg-blue')('div');
will not work (and will raise a type error if using Typescript). Instead, you must write:
const Div = stylable<HTMLProps<HTMLDivElement>>('div');
const BlueDiv = addClasses('bg-blue')(Div);
or, if you prefer:
const BlueDiv = flowRight(
addClasses('bg-blue'),
stylable,
)('div');
stylable()
when applied to intrinsic elements.When using typescript in the above examples, we must explicitly
specify the type of our stylable Div
because it cannot be inferred from the
intrinsic element 'div'
.
removeClasses()
can only remove classes which were originally added by
addClasses()
. Thus, for example:
const BlueDiv = ({ className, ...rest }) => <div className={`${classname} bg-blue`} {...rest} />;
const GreenDiv = removeClasses('bg-blue').addClasses('bg-green')(BlueDiv);
will not work, because the bg-blue
class is hidden inside BlueDiv
and not
accessible to the removeClasses()
HOC. Instead, use:
const BlueDiv = addClasses('bg-blue')(Stylable('div'));
const GreenDiv = removeClasses('bg-blue').addClasses('bg-green')(BlueDiv);
removeClasses()
with no arguments to remove all classesconst Button: FC<HTMLProps<HTMLButtonElement>> = props => <button onClick={specialClickHandler} type="button" {...props} />;
const StylableButton = stylable(Button);
const OceanButton = withClasses('text-green bg-blue italic')(StylableButton);
const DesertButton = withoutClasses().withClasses('text-yellow bg-red bold')(OceanButton);
This is usefule when you don't have access to the original, unstyled variant of the component.
The Design API provides a mechanism for applying higher order components (including those provided by the FClasses API) to individual elements within a compound component.
Consider the following component:
const Tout: FC<{}> = () => {
return (
<div className="wrapper">
<h2 className="title">This is the title</h2>
<div className="body">This is the body</h2>
<a href="http://foo.com" className="cta">This is the CTA</a>
</div>
);
)
With the Design API, rather than providing classes which a consumer can style using CSS, we provide a way for consumers to replace or modify the individual components of which the Tout is composed:
export type ToutComponents = {
Wrapper: ComponentType<StylableProps>,
ImageWrapper: ComponentType<StylableProps>,
ImageLink: ComponentType<StylableProps>,
Image: ComponentType<StylableProps>,
ContentWrapper: ComponentType<StylableProps>,
Title: ComponentType<StylableProps>,
Body: ComponentType<StylableProps>,
Link: ComponentType<StylableProps>,
};
const toutComponentStart:ToutComponents = {
Wrapper: Div,
ImageWrapper: Div,
ImageLink: A,
Image: Img,
ContentWrapper: Div,
Title: H2,
Body: Div,
Link: A,
};
type Props = DesignableComponentsProps<ToutComponents> & { };
const ToutBase: FC<Props> = ({ components }) => {
const {
Wrapper,
ImageWrapper,
Image,
ImageLink,
ContentWrapper,
Title,
Body,
Link,
} = components;
return (
<Wrapper>
<ImageWrapper>
<ImageLink>
<Image />
</ImageLink>
</ImageWrapper>
<ContentWrapper>
<Title />
<Body />
<Link />
</ContentWrapper>
</Wrapper>
);
};
Here we have defined a type of the components that we need, a starting point for those components and then we have create a componant that accepts those compoents. Next we will combine the Start point as well as the ToutBase to make a designable tout that can take a Design prop.
const ToutDesignable = designable(toutComponentStart)(ToutBase);
A consumer can now style our Tout by employing the withDesign()
API method to
pass a Design
object as a prop value. This is simply a set of higher-order
components which will be applied to each element. For example:
const asBasicTout = withDesign({
Wrapper: addClasses('font-sans'),
Title: addClasses('text-sm text-green'),
Body: addClasses('my-10'),
Cta: addClasses('block w-full bg-blue text-yellow py-1'),
});
const BasicTout = asBasicTout(Tout);
In ths example, we could simply have provided our design directly as a prop:
const BasicTout: FC = () => <Tout design={{
Wrapper: addClasses('font-sans'),
Title: addClasses('text-sm text-green'),
Body: addClasses('my-10'),
Cta: addClasses('block w-full bg-blue text-yellow py-1'),
}} />
However, by using withDesign()
instead, our component itself will expose its own
design prop, allowing other consumers to further extend it:
const asPinkTout = withDesign({
Cta: addClasses('bg-pink').removeClasses('bg-blue'),
});
const PinkTout = asPinkTout(BasicTout);
In these examples, we are extending the default components. If we wanted
instead to replace one, we could write our HOC to ignore its argument
(or use the provided shortcut HOC replaceWith()
):
const StylableH2 = stylable<JSX.IntrinsicElements['h2']>('h2');
const StandardH2 = addClasses('text-xl text-blue')(StylableH2);
const StandardTout = withDesign({
Title: replaceWith(StandardH2), // same as () => StandardH2
})(BasicTout);
We can also use the startWith()
HOC, instead of replacing the whole component, it will only replace the base component but still use any hoc that might have wrapped it.
As with FClasses, HOC's created via withDesign()
are themselves reusable, so
we can write:
const asStandardTout = withDesign({
Title: replaceWith(StandardH2), // same as () => StandardH2
});
const StandardTout = asStandardTout(Tout);
const StandardPinkTout = asStandardTout(PinkTout);
const StandardRedTout = asStandardTout(RedTout);
And, also as with FClasses, the HOC's can be composed:
const StandardPinkAndGreenTout = flowRight(
withGreenCtaText,
asStandardTout,
asPinkTout,
)(BasicTout);
It is sometimes useful to apply classes conditionally, based on props passed to a component and/or based on component state. The FClasses design API includes some helper methods which make this easier.
Imagine we have a button which has different variants depending on whether it
is active and/or whether it is the first in a list of buttons. We can use
the addClassesIf()
, removeClassesIf()
, withoutProps()
and hasProp()
helpers to accomplish this:
type VariantProps = {
isActive?: boolean,
isFirst?: boolean,
isEnabled?: boolean,
};
const Div = stylable<HTMLProps<HTMLDivElement>>('div');
const isActive = (props: any) => hasProp('isActive')(props);
const isFirst = (props: any) => hasProp('isFirst')(props);
const ContextMenuButton = flow(
withoutProps<VariantProps>(['isActive', 'isFirst'),
addClasses('cursor-pointer pl-2 text-grey'),
addClassesIf(isActive)('text-white'),
removeClassesIf(isActive)('text-grey'),
removeClassesIf(isFirst)('pl-2'),
)(Div);
Imagine we have a button which consume some state from a react context.
We can use addClassesIf
and removeClassesIf
helpers to add classes to the button conditionally:
const ToggleContext = React.createContext({
state: false,
});
const useStateContext = () => useContext(ToggleContext);
const isToggled = () => useStateContext().state;
const Div = stylable<HTMLProps<HTMLDivElement>>('div');
const Button = props => {
const { state } = useStateContext();
return <Div {...props}>{`Button state consumed from store is ${state}.`}</Div>;
};
const StyledButton = flow(
addClassesIf(isToggled)('bg-green-200'),
)(Button);
A few notes:
We use flow()
rather than flowRight()
because it is better at type inference.
Our innermost HOC is withoutProps()
. This guarantees that the props used to
control styling won't be passed to the div
element. We must explicitly type
the generic withoutProps()
. This ensures that the type of the resulting
component will include these props.
One of the most powerful features of the Design API is the ability to create multiple variants of a component by composing different tokens onto it.
FAQs
Allows for the injection of functional class into components.
The npm package @bodiless/fclasses receives a total of 1 weekly downloads. As such, @bodiless/fclasses popularity was classified as not popular.
We found that @bodiless/fclasses demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 5 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.