
Security News
Opengrep Adds Apex Support and New Rule Controls in Latest Updates
The latest Opengrep releases add Apex scanning, precision rule tuning, and performance gains for open source static code analysis.
@fluentui-react-native/use-styling
Advanced tools
This package provides a framework for building a hook function which produces opinionated styles, for both simple and higher order components. This is built with the following principles in mind:
An extremely simple example might look like:
// create the styling hook which can be used in the component, note that this gets created once and
// should not be done inside the component itself
const useSyling = buildUseStyling(myComponentOptions, themeHelper);
// component implementation, consuming the styling hook
const MyComponent = (props) => {
// get some default/styled props for the container and content child components
const { container, content } = useStyling(props);
// render as normal but mixin those values to pass the props to the sub-components
const { textValue, ...rest } = props;
const merged = { ...container, ...rest };
return (
<View {...merged}>
<Text {...content}>{textValue}</Text>
</View>
);
};
Now this example isn't particularly useful but demonstrates the basic role that the styling hook can play in a HOC.
At the core of this system is the idea that: Tokens + Theme ==> Styles
. This can be thought of as:
slotProps
tokensThatAreAlsoProps
The overall signature of the method is as follows:
const useStyling = buildUseStyling(
/* options that define the component */
{
/* Step 1: Build up a single Tokens object with constants for the component */
tokens?: [
/* object */
{ },
/* theme function */
(theme: Theme) => ({ /* created object */ }),
/* name to lookup */
'NameToLookup'
],
/* Step 2: Potentially apply states (if applicable) to the Tokens */
states?: ['hovered', 'pressed', 'disabled'],
/* Step 3: Copy some/none/all props to Tokens */
tokensThatAreAlsoProps?: 'all' /* all props are tokens */
| 'none' | undefined /* no props are tokens */
| ['token1', 'token2'] /* list of tokens which also appear in props */,
/* Step 4: Create props for child components from Tokens and Theme */
slotProps?: {
/* props as a constant object */
slot1: { },
/* props as a function of tokens and theme, with smart caching/filtering */
slot2: buildProps((tokens: Tokens, theme: Theme) => {
// function to generate props
return { style: { color: tokens.token1 }}
}, ['token1'])
/* props as a custom function of tokens, theme, cache */
slot3: (tokens: Tokens, theme: Theme, cache: GetMemoValue) => {
// function to generate props
}
}
},
/* theme helper plugin, global per theming system */
{
/* get the active theme, usually from the context */
useTheme: () => { return useContext(ThemeProvider) || getDefaultTheme() };
/* lookup a component in the theme to see if token overrides are available */
getComponentInfo: (theme: Theme, name: string) => lookupComponentInTheme(theme, name);)
}
);
A token is simply a setting that informs the styling of the component. These can be thought of as an additional set of component props that are automatically built-up by the framework. Typically these are set at component creation time or overriden by a theme but can can come from a variety of sources:
/** Tokens interface for this component */
interface Tokens {
backgroundColor?: ColorValue;
borderWidth?: number;
borderRadius?: number;
}
/** Build the styling hook */
const useStyling = buildUseStyling({
tokens: [
/* fixed object */
{
borderWidth: 1,
borderRadius: 2,
},
/* theme function */
(theme: Theme) => {
backgroundColor: theme.palette.bodyBackground || 'white';
},
/* name lookup */
'MyComponentName',
],
...otherSettings,
});
These tokens will be deeply merged in the order they appear in the array.
const mergedTokens = merge(
...options.tokens.map(entry => {
// string gets converted to object or function (or empty object)
if (typeof entry === 'string') {
entry = themeHelper.getComponentInfo(entry) || {};
}
// return either the function call (which will produce an object) or the object
return typeof entry === 'function' ? entry(theme) : entry;
});
);
The only variable input to this entire process is the theme. As a result the result of all of this will be cached based on:
WeakMap
As a result this calculation will run one time per theme, per component type.
Some components may have additional states or modes that can be applied to them. As an example a Button component might have a state called hovered or pressed. In this case the Tokens interface should be as follows:
interface ButtonTokens {
// base values
backgroundColor?: ColorValue;
color?: ColorValue;
borderColor?: ColorValue;
// states
hovered?: ButtonTokens;
pressed?: ButtonTokens;
}
In this case the states
option should be provided as follows:
const useStyling = buildUseStyling({
tokens: [
/* token settings */
],
states: ['hovered', 'pressed'],
...otherSettings,
});
For each entry in the states
array, in the order they appear, the following will happen:
The generated useStyling
hook has an optional second parameter which is a function of the form:
type HasLayer = (state: string) => boolean;
This allows states to be set based on states in addition to props. An example might look like:
interface ButtonState {
hovered?: boolean;
pressed?: boolean;
}
const Button = (props: ButtonProps) => {
// get some state value that figures out whether the button is pressed and/or hovered
const buttonState: ButtonState = useButtonState(props);
// get styling values based on props and the current state
const { container, icon, label } = useStyling(props, (stateName) => buttonState[stateName]);
const buttonProps = useButtonMagic(props, container);
return (
<View {...buttonProps}>
<Image {...icon} source={props.iconSrc} />
<Text {...label}>{props.text}</Text>
</View>
);
};
The ideal scenario from a caching perspective, is for tokens and props to be completely separate. This keeps variability very low and reduces the need for frequent style recalculations. From the perspective of someone using the components this sharply limits flexibility. As a result it may be useful for some tokens to be surfaced as props on the component.
Because props are not provided to the final stage of the useStyling
workflow, if any props are also tokens, they need to be declared so that they can override the token values coming from the theme or constants. This is done via the tokensThatAreAlsoProps
property. This has three behaviors:
Value | Behavior |
---|---|
'none' or undefined | No properties are tokens and tokens will be left as is. |
'all' | All properties should be considered tokens. This equates to: tokens = { ...tokens, ...props } |
(keyof Tokens)[] | Provides a discrete list of values to copy from props to tokens (if present) |
Part of the challenge of building a higher order component, particularly one that is customizeable, is figuring out how to map a single set of props into a multiple internal components. While it is standard to expose a style
on the props
interface of a component, this can only map to one of the internal components.
Customizing HOCs by directly targeting the styles of the sub-components is problematic in that it leaks the implementation details. As a result, it becomes challenging to maintain or update the component long term without breaking customers.
The solution used by the useStyling
module is to allow focus customization on the various Token values, then have a defined mapping of how those Tokens translate to props for each child component. In the parlance of this library, each child component is referred to as a slot.
Each slot returns its own props object, either directly or via a function.
The simplest way to provide props for a slot is by providing an object directly. While limited in applicability, it looks as follows:
const useStyling = buildUseStyling({
...precedingOptions,
slotProps: {
slot1: { style: { display: 'flex' } },
},
});
The functions are assumed to have the following signature:
function propBuilder(tokens: Tokens, theme: Theme, cache: GetMemoValue<Props>): Props;
While tokens and theme have been discussed at length above, the cache will be a cache instance keyed on:
If there are no tokens which are also props, i.e. if tokensProps
is falsy, this is sufficient and the function could be written as:
const slotFn = (tokens: Tokens, theme: Theme, cache: GetMemoValue<Props>) =>
cache(
() => {
return {
style: {
backgroundColor: tokens.backgroundColor,
// etc.
},
};
},
[
/* no keys, just direct cache the function result*/
],
)[0];
If some all props are tokens then those keys should be considered in the caching. This would then turn into:
const slotFn = (tokens: Tokens, theme: Theme, cache: GetMemoValue<Props>) => {
const { backgroundColor, color, borderRadius } = tokens;
return cache(
() => ({
style: { backgroundColor, color, borderRadius },
}),
[backgroundColor, color, borderRadius],
)[0];
};
The library provides a helper called buildProps to make this more ergonomic. It provides an implementation for the full caching function above and would be used as follows:
const slotFn = buildProps(
(tokens: Tokens, theme: Theme) => ({
style: {
backgroundColor: tokens.backgroundColor,
color: tokens.color,
borderRadius: tokens.borderRadius,
},
}),
['backgroundColor', 'color', 'borderRadius'],
);
The primary benefit this function provides is that buildUseStyling
knows how to refine these lists when constructing the hooks. As a result the caching will adjust automatically based on what is specified in tokenProps
.
FAQs
Opinionated use styling hook implementation
The npm package @fluentui-react-native/use-styling receives a total of 663 weekly downloads. As such, @fluentui-react-native/use-styling popularity was classified as not popular.
We found that @fluentui-react-native/use-styling demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 6 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
The latest Opengrep releases add Apex scanning, precision rule tuning, and performance gains for open source static code analysis.
Security News
npm now supports Trusted Publishing with OIDC, enabling secure package publishing directly from CI/CD workflows without relying on long-lived tokens.
Research
/Security News
A RubyGems malware campaign used 60 malicious packages posing as automation tools to steal credentials from social media and marketing tool users.