枫上雾棋的 storybook
  • CORE
  • COMPONENTS
  • CASES
  • CODE
ds
hooks
style
utils

colorPalette

export const colorPalette = {
  black: "#000",
  white: "#fff",

  gray: {
    100: "#f7fafc",
    200: "#edf2f7",
    300: "#e2e8f0",
    400: "#cbd5e0",
    500: "#a0aec0",
    600: "#718096",
    700: "#4a5568",
    800: "#2d3748",
    900: "#1a202c",
  },
};
White
Black
Gray
Red
Orange
Yellow
Green
Teal
Blue
Indigo
Purple
Pink
Source

reset

import { Global } from "@emotion/core";
import { normalize } from "polished";

export const DSReset = () => {
  const ds = useDesignSystem();

  return (
    <>
      <Global styles={{ ...normalize() }} />
      <Global
        styles={{
          a: {
            color: `${ds.color.primary}`,
            textDecoration: "none",
          },
        }}
      />
    </>
  );
};
Source

defaultTheme

const color = {
  primary: colorPalette.blue[500],
  secondary: rgba(colorPalette.blue[500], 0.85),
};

export const defaultTheme = {  color,
  colorPalette,
};
PRIMARY
SECONDARY
SUCCESS
WARNING
ERROR
INFO
TEXT
TEXTLIGHT
BG
BGLIGHT
Source

useDesignSystem

// useDesignSystem.ts
import { ThemeContext } from "@emotion/core";
import { ITheme } from "./defaultTheme";

export const useDesignSystem = () => {
  return useContext(ThemeContext as Context<ITheme>);};

// app.tsx
const App = () => {
  const ds = useDesignSystem();
  return <div css={{ background: ds.color.primary }} />;
};
Source

useDebounce

count: 0, debounceCount: 0
export const useDebounce = <T>(value: T, delay: number) => {
  const [debouncedValue, setDebouncedValue] = useState < T > value;

  useEffect(() => {
    const timeoutID = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(timeoutID);
    };
  }, [value, delay]);

  return debouncedValue;
};
Source

useLocalStorage

count:
export const useLocalStorage = <T>(
  key: string,
  initialValue?: T,
): [T, (value: any) => void] => {
  const [storedValue, setStoredValue] =
    useState <
    T >
    (() => {
      try {
        const item = globalThis.localStorage.getItem(key);
        return item ? JSON.parse(item) : initialValue;
      } catch (err) {
        console.error(err);
        return initialValue;
      }
    });

  const updateStoredValue = (value: any) => {
    try {
      const newStoredValue = isFunction(value) ? value(storedValue) : value;
      setStoredValue(newStoredValue);
      globalThis.localStorage.setItem(key, JSON.stringify(newStoredValue));
    } catch (err) {
      console.error(err);
    }
  };

  return [storedValue, updateStoredValue];
};
Source

useHover

hover
export const useHover = <T = any>(): [RefObject<T>, boolean] => {
  const [isHovered, setIsHovered] = useState(false);
  const hoverRef = useRef(null);

  useEffect(() => {
    const node = hoverRef.current;

    if (!node) {
      return;
    }

    const mouseover$ = fromEvent(node, "mouseover").subscribe(() =>
      setIsHovered(true),
    );
    const mouseout$ = fromEvent(node, "mouseout").subscribe(() =>
      setIsHovered(false),
    );

    return () => {
      mouseover$.unsubscribe();
      mouseout$.unsubscribe();
    };
  }, [hoverRef.current]);

  return [hoverRef, isHovered];
};
Source

useObservable

0
export const useObservable = <T>(ob$: Observable<T>, defaultValue: T): T => {
  const [value, setValue] = useState < T > defaultValue;

  useEffect(() => {
    const sub = ob$.subscribe(setValue);

    return () => {
      sub.unsubscribe();
    };
  }, [ob$]);

  return value;
};
Source

useRect

width: 0
export const getRect = (targetElm: Element, relatedElm: Element): IRect => {
  const targetRect = targetElm.getBoundingClientRect();
  const relatedRect = relatedElm.getBoundingClientRect();

  return {
    top: (targetRect.top || 0) - relatedRect.top,
    left: (targetRect.left || 0) - relatedRect.left,
    width: targetRect.width || 0,
    height: targetRect.height || 0,
  };
};

export const useRect = (
  elmRef: RefObject<Element | null>,
): [IRect, () => void] => {
  const [rect, setRect] = useState({ left: 0, top: 0, width: 0, height: 0 });

  const refreshRect = useCallback(() => {
    if (elmRef.current) {
      setRect(getRect(elmRef.current, document.body));
    }
  }, []);

  useEffect(() => {
    refreshRect();
  }, []);

  useLayoutEffect(() => {
    const resize$ = fromEvent(globalThis, "resize");
    const orientationchange$ = fromEvent(globalThis, "orientationchange");

    const sub = observableMerge(resize$, orientationchange$)
      .pipe(observeOn(animationFrameScheduler), debounceTime(200))
      .subscribe(refreshRect);

    return () => {
      sub.unsubscribe();
    };
  }, []);

  return [rect, refreshRect];
};
Source

useToggle

off
export const useToggle = (defaultValue?: boolean): [boolean, () => void] => {
  const [value, setValue] = useState(!!defaultValue);

  const toggle = () => {
    return setValue(!value);
  };

  return [value, toggle];
};
Source

useVisibilitySensor

1
2
3
4
5
export const useVisibilitySensor = (
  ref: RefObject<Element>,
  options: IntersectionObserverInit,
) => {
  const [isIntersecting, setIntersecting] = useState(false);

  useEffect(() => {
    const io = new IntersectionObserver(([entry]) => {
      setIntersecting(entry.isIntersecting);
    }, options);

    if (ref.current) {
      io.observe(ref.current);
    }

    return () => {
      io.disconnect();
    };
  }, []);

  return isIntersecting;
};
Source

flex

CSS FlexBox
export const flex = (flexOpts: CSSObject): CSSObject => ({
  display: "flex",
  ...flexOpts,
});

export const inlineFlex = (flexOpts: CSSObject): CSSObject => ({
  display: "inline-flex",
  ...flexOpts,
});

styled.div({
  ...flex({    justifyContent: "center",
    alignItems: "center",
  }),
});
Source

fluidType

Fluid type is cool.

export const fluidType = (
  minVw: breakpointKey,
  maxVw: breakpointKey,
  minFontSize: number,
  maxFontSize: number,
): CSSObject => ({
  fontSize: minFontSize,
  [`@media (min-width: ${breakpoint[minVw]}px)`]: {
    fontSize: `calc(${minFontSize}px + (${
      maxFontSize - minFontSize
    }) * (100vw - ${breakpoint[minVw]}px) / (${
      breakpoint[maxVw] - breakpoint[minVw]
    }))`,
  },
  [`@media (min-width: ${breakpoint[maxVw]}px)`]: {
    fontSize: maxFontSize,
  },
});

styled.div({
  ...fluidType("md", "xl", 12, 16),});
Source

grid

1
2
3
4
5
export const grid = (gridOpts: CSSObject): CSSObject => ({
  display: "grid",
  ...gridOpts,
});

styled.div({
  ...grid({    gridTemplateColumns: "repeat(3, 1fr)",
    gridTemplateRows: "repeat(3, 1fr)",
    gridAutoFlow: "row dense",
    gridGap: `${ds.spacing[5]}`,
  }),
});
Source

mq

Media Queries.

import facepaint from "facepaint";

export enum breakpoint {
  sm = 640,
  md = 768,
  lg = 1024,
  xl = 1280,
}
export type breakpointKey = keyof typeof breakpoint;

export const mq = (breakpoints: breakpointKey[], style: CSSObject) => {
  const selectors = breakpoints.map(
    bp => `@media (min-width: ${breakpoint[bp]}px)`,
  );
  const mq = facepaint(selectors);

  const dynamicStyle = mq(style);

  return dynamicStyle;
};

styled.div(mq(["lg"], {  color: [ds.color.secondary, ds.color.text],
}))
Source

canUseDOM

export const canUseDOM = !!(
  typeof window !== "undefined" &&
  window.document &&
  window.document.createElement
);
Source

parseSearchString

import { parse } from "querystring";

export const parseSearchString = (search: string) => {
  if (search.startsWith("?")) {
    return parse(search.slice(1));
  }

  return parse(search);
};
Source

pickElmAttrs

// pickElmAttrs.ts
import isPropValid from "@emotion/is-prop-valid";

export const pickElmAttrs = (props: Record<string, any>) => {
  const p: Record<string, any> = {};

  Object.keys(props).forEach(key => {
    if (isPropValid(key)) {
      p[key] = props[key];
    }
  });

  return p;
};

// app.tsx
const App = ({ ...otherprops }) => {
  return <div {...pickElmAttrs(otherProps)} />;};
Source

sleep

export const sleep = async (interval: number) => {
  return new Promise(resolve => {
    setTimeout(resolve, interval);
  });
};
Source

toSearchString

import { stringify } from "querystring";

export type SearchQuery =
  | Record<string, string | number | boolean | string[] | number[] | boolean[]>
  | undefined;

export const toSearchString = (query: SearchQuery) => {
  const str = stringify(query);
  return str ? `?${str}` : "";
};
Source

withoutBubble

Link
export const withoutBubble = (cb: () => void) => (e: React.SyntheticEvent) => {
  if (e) {
    e.preventDefault();
    e.stopPropagation();
  }
  cb();
};
Source