mirror of
https://github.com/zoriya/yoshiki.git
synced 2026-06-01 10:06:52 +00:00
Add fover support (either hover or focus)
This commit is contained in:
@@ -54,6 +54,11 @@ const BoxWithoutProps = (props: Stylable) => {
|
||||
color: "green",
|
||||
},
|
||||
},
|
||||
fover: {
|
||||
text: {
|
||||
textDecorationLine: "underline",
|
||||
}
|
||||
}
|
||||
},
|
||||
md({
|
||||
shadowOpacity: 0.5,
|
||||
|
||||
@@ -100,13 +100,16 @@ export default function Home(props: Stylable) {
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
tabIndex={0}
|
||||
{...css({
|
||||
hover: {
|
||||
self: { background: { xs: "red", sm: "blue", md: "green" } },
|
||||
fover: {
|
||||
link: {
|
||||
color: { xs: "blue", md: "black" },
|
||||
},
|
||||
},
|
||||
})}
|
||||
>
|
||||
<a href="https://github.com/vercel/next.js/tree/canary/examples">
|
||||
<a href="https://github.com/vercel/next.js/tree/canary/examples" {...css("link")}>
|
||||
<h2>Examples →</h2>
|
||||
<p>Discover and deploy boilerplate example Next.js projects.</p>
|
||||
</a>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "yoshiki",
|
||||
"version": "1.1.4",
|
||||
"version": "1.2.0",
|
||||
"author": "Zoe Roux <zoe.roux@sdg.moe> (https://github.com/AnonymusRaccoon)",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -86,12 +86,13 @@ export const useYoshiki = (_?: string) => {
|
||||
};
|
||||
|
||||
if (hasState<State>(css)) {
|
||||
const { hover, focus, press, ...inline } = css;
|
||||
const { hover, focus, fover, press, ...inline } = css;
|
||||
const { onPressIn, onPressOut, onHoverIn, onHoverOut, onFocus, onBlur } =
|
||||
leftOvers as PressableProps;
|
||||
const ret: StyleFunc<unknown> = ({ hovered, focused, pressed }) => {
|
||||
childStyles.current = {};
|
||||
assignChilds(childStyles.current, child);
|
||||
if (focused || hovered) assignChilds(childStyles.current, fover);
|
||||
if (hovered) assignChilds(childStyles.current, hover);
|
||||
if (focused) assignChilds(childStyles.current, focus);
|
||||
if (pressed) assignChilds(childStyles.current, press);
|
||||
@@ -99,6 +100,7 @@ export const useYoshiki = (_?: string) => {
|
||||
return [
|
||||
processStyle(inline),
|
||||
processStyle(child?.self ?? {}),
|
||||
(focused || hovered) && processStyle(fover?.self ?? {}),
|
||||
hovered && processStyle(hover?.self ?? {}),
|
||||
focused && processStyle(focus?.self ?? {}),
|
||||
pressed && processStyle(press?.self ?? {}),
|
||||
|
||||
@@ -17,6 +17,8 @@ export type Breakpoints<Property> = {
|
||||
export type WithState<Style> = {
|
||||
hover: { self?: Style; [key: string]: Style | undefined };
|
||||
focus: { self?: Style; [key: string]: Style | undefined };
|
||||
// A mix of hover and focus
|
||||
fover: { self?: Style; [key: string]: Style | undefined };
|
||||
press: { self?: Style; [key: string]: Style | undefined };
|
||||
};
|
||||
export type WithChild<Style> = {
|
||||
@@ -27,7 +29,7 @@ export const hasState = <Style = Record<string, unknown>>(
|
||||
obj: unknown,
|
||||
): obj is WithState<Style> => {
|
||||
if (!obj || typeof obj !== "object") return false;
|
||||
return "hover" in obj || "focus" in obj || "press" in obj;
|
||||
return "hover" in obj || "focus" in obj || "press" in obj || "fover" in obj;
|
||||
};
|
||||
|
||||
const isReadonlyArray = (array: unknown): array is ReadonlyArray<unknown> => Array.isArray(array);
|
||||
|
||||
@@ -35,16 +35,26 @@ export const md = (value: ForcedBreakpointStyle) => forceBreakpoint(value, "md")
|
||||
export const lg = (value: ForcedBreakpointStyle) => forceBreakpoint(value, "lg");
|
||||
export const xl = (value: ForcedBreakpointStyle) => forceBreakpoint(value, "xl");
|
||||
|
||||
const appendChild = (cn: string, childSelect?: string) =>
|
||||
childSelect ? `${cn} .${childSelect}` : cn;
|
||||
const stateMapper: {
|
||||
[key in keyof (WithState<undefined> & { normal: undefined })]: (cn: string) => string;
|
||||
[key in keyof (WithState<undefined> & { normal: undefined })]: (
|
||||
cn: string,
|
||||
childSelect?: string,
|
||||
) => string;
|
||||
} = {
|
||||
normal: (cn) => `.${cn}`,
|
||||
press: (cn) => `.${cn}:active`,
|
||||
normal: (cn, child) => appendChild(`.${cn}`, child),
|
||||
press: (cn, child) => appendChild(`.${cn}:active`, child),
|
||||
// :focus-visible is a pseudo-selector that only enables the focus ring when using the keyboard.
|
||||
focus: (cn) => `.${cn}:focus-visible`,
|
||||
focus: (cn, child) => appendChild(`.${cn}:focus-visible`, child),
|
||||
// The body.noHover will be set when the users uses a touch screen instead of a mouse. This is used to only enable hover with the mouse.
|
||||
// The where is used to decrease the rule specificity (make it the same as juste .cn:hover)
|
||||
hover: (cn) => `:where(body:not(.noHover)) .${cn}:hover`,
|
||||
hover: (cn, child) => appendChild(`:where(body:not(.noHover)) .${cn}:hover`, child),
|
||||
fover: (cn, child) =>
|
||||
`${appendChild(stateMapper["hover"](cn), child)}, ${appendChild(
|
||||
stateMapper["focus"](cn),
|
||||
child,
|
||||
)}`,
|
||||
};
|
||||
|
||||
export const sanitize = (value: unknown): string => {
|
||||
@@ -65,7 +75,7 @@ const generateClassBlock = (
|
||||
): string | undefined => {
|
||||
preprocessBlock ??= (id) => id;
|
||||
const block = Object.entries(prefix(preprocessBlock(style)))
|
||||
.filter(([_, value]) => value !== undefined)
|
||||
.filter(([, value]) => value !== undefined)
|
||||
.flatMap(([nKey, nValue]) => {
|
||||
const cssKey = nKey.replace(/[A-Z]/g, "-$&").toLowerCase();
|
||||
return Array.isArray(nValue)
|
||||
@@ -181,7 +191,7 @@ export const yoshikiCssToClassNames = (
|
||||
preprocessBlock?: PreprocessBlockFunction;
|
||||
},
|
||||
) => {
|
||||
const { child, hover, focus, press, ...inline } = css;
|
||||
const { child, hover, focus, fover, press, ...inline } = css;
|
||||
|
||||
const processStyles = (
|
||||
inlineStyle?: Record<string, unknown>,
|
||||
@@ -202,10 +212,11 @@ export const yoshikiCssToClassNames = (
|
||||
return dedupProperties(
|
||||
processStyles(inline),
|
||||
processStyles(child?.self),
|
||||
processStyles(fover?.self, "fover"),
|
||||
processStyles(hover?.self, "hover"),
|
||||
processStyles(focus?.self, "focus"),
|
||||
processStyles(press?.self, "press"),
|
||||
Object.keys({ ...hover, ...focus, ...press })
|
||||
Object.keys({ ...hover, ...focus, ...press, ...fover })
|
||||
.filter((x) => x !== "self")
|
||||
.map((x) => parentPrefix + x),
|
||||
classNames,
|
||||
@@ -214,7 +225,7 @@ export const yoshikiCssToClassNames = (
|
||||
|
||||
// TODO: This is extremly hacky and an ID should be unique to a component, not an instance.
|
||||
export const useClassId = (prefixKey?: string) => {
|
||||
const id = prefixKey ?? useId().replaceAll(":", "-");
|
||||
const id = prefixKey ? `-${prefixKey}-` : useId().replaceAll(":", "-");
|
||||
return ["ysp" + id, "ysc" + id] as const;
|
||||
};
|
||||
|
||||
@@ -224,6 +235,7 @@ export const generateChildCss = (
|
||||
focus,
|
||||
press,
|
||||
child,
|
||||
fover,
|
||||
}: Partial<WithState<Record<string, unknown>> & WithChild<Record<string, unknown>>>,
|
||||
{
|
||||
parentPrefix,
|
||||
@@ -285,7 +297,7 @@ export const generateChildCss = (
|
||||
for (const [breakpoint, breakedStyle] of Object.entries(splitStyle)) {
|
||||
const block = generateClassBlock(breakedStyle, preprocessBlock);
|
||||
if (!block) continue;
|
||||
const cssClass = `${stateMapper[state](parentName)} .${className} ${block}`;
|
||||
const cssClass = `${stateMapper[state](parentName, className)} ${block}`;
|
||||
registry.addRule(
|
||||
{ type: "general", key: className, breakpoint: breakpoint as BreakpointKey, state },
|
||||
addBreakpointBlock(breakpoint as BreakpointKey, cssClass),
|
||||
@@ -298,6 +310,7 @@ export const generateChildCss = (
|
||||
processStyles(hover, "hover");
|
||||
processStyles(focus, "focus");
|
||||
processStyles(press, "press");
|
||||
processStyles(fover, "fover");
|
||||
};
|
||||
|
||||
export const useYoshiki = (prefixKey?: string) => {
|
||||
@@ -311,7 +324,7 @@ export const useYoshiki = (prefixKey?: string) => {
|
||||
|
||||
return {
|
||||
css: <Leftover>(
|
||||
cssList: StyleList<CssObject | string> & Partial<WithState<CssObject>> & WithChild<CssObject>,
|
||||
cssList: StyleList<CssObject | string> & Partial<WithState<CssObject> & WithChild<CssObject>>,
|
||||
leftOverProps?: Leftover & { className?: string },
|
||||
): { className: string } & Omit<Leftover, "className"> => {
|
||||
const [css, parentKeys] = processStyleListWithoutChild(cssList);
|
||||
|
||||
@@ -27,7 +27,7 @@ export class StyleRegistry {
|
||||
StyleKey["state"],
|
||||
Record<StyleKey["breakpoint"], Record<StyleKey["type"], Record<string, string>>>
|
||||
> = Object.fromEntries(
|
||||
["normal", "hover", "focus", "press"].map((x) => [
|
||||
["normal", "fover", "hover", "focus", "press"].map((x) => [
|
||||
x,
|
||||
Object.fromEntries(
|
||||
Object.keys({ default: 0, ...breakpoints }).map((bp) => [
|
||||
|
||||
Reference in New Issue
Block a user