(false);
7 |
8 | const open = React.useCallback(() => setIsOpen(true), []);
9 | const close = React.useCallback(() => setIsOpen(false), []);
10 |
11 | return [isOpen, open, close];
12 | };
13 |
--------------------------------------------------------------------------------
/packages/kitchn/src/hooks/use-checkbox.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export type UseCheckboxValue = boolean;
4 | export type UseCheckboxResult = [boolean, () => void];
5 |
6 | export const useCheckbox = (
7 | defaultValue: UseCheckboxValue = false,
8 | ): UseCheckboxResult => {
9 | const [checked, setChecked] = React.useState(defaultValue);
10 | const toggle = () => setChecked((prev) => !prev);
11 | return [checked, toggle];
12 | };
13 |
--------------------------------------------------------------------------------
/packages/kitchn/src/hooks/use-resize.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const useResize = (
4 | callback: () => unknown,
5 | immediatelyInvoke: boolean = true,
6 | ): void => {
7 | React.useEffect(() => {
8 | const fn = () => callback();
9 | if (immediatelyInvoke) {
10 | fn();
11 | }
12 | window.addEventListener("resize", fn);
13 | return () => window.removeEventListener("resize", fn);
14 | }, []);
15 | };
16 |
--------------------------------------------------------------------------------
/packages/kitchn/src/native/types/index.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
2 | export * from "./theme";
3 |
4 | export type KitchnComponent = {
5 | /**
6 | * The root element.
7 | */
8 | as?: React.ElementType;
9 | /**
10 | * The content, duh.
11 | */
12 | children?: React.ReactNode;
13 | } & P &
14 | Omit;
15 |
16 | export type NormalSizes = "small" | "normal" | "large";
17 |
--------------------------------------------------------------------------------
/docs/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Kitchn",
3 | "short_name": "Kitchn",
4 | "icons": [
5 | {
6 | "src": "/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#051527",
17 | "background_color": "#051527",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Bug
2 |
3 | - [ ] Make sure the linting passes by running `pnpm lint && pnpm build`
4 | - [ ] Related issues linked using `fixes #number`
5 |
6 | ## Feature
7 |
8 | - [ ] Make sure the linting passes by running `pnpm lint && pnpm build`
9 | - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
10 | - [ ] Related issues linked using `fixes #number`
11 |
--------------------------------------------------------------------------------
/packages/kitchn/src/hooks/use-navigation-menu.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { NavigationMenuContext } from "../contexts/navigation-menu";
4 |
5 | const useNavigationMenu = () => {
6 | const context = React.useContext(NavigationMenuContext);
7 | if (!context) {
8 | throw new Error(
9 | "Navigation menu components must be used within NavigationMenu.Container",
10 | );
11 | }
12 | return context;
13 | };
14 |
15 | export default useNavigationMenu;
16 |
--------------------------------------------------------------------------------
/docs/pages/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": {
3 | "type": "page",
4 | "title": "Kitchn",
5 | "display": "hidden",
6 | "theme": {
7 | "layout": "raw"
8 | }
9 | },
10 | "docs": {
11 | "type": "page",
12 | "title": "Documentation"
13 | },
14 | "blog": {
15 | "type": "page",
16 | "title": "Blog"
17 | },
18 | "showcase": {
19 | "type": "page",
20 | "title": "Showcase",
21 | "theme": {
22 | "layout": "raw"
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/kitchn/src/contexts/combobox.tsx:
--------------------------------------------------------------------------------
1 | import React, { MutableRefObject } from "react";
2 |
3 | export interface ComboboxConfig {
4 | value?: string;
5 | updateValue?: (val: string) => unknown;
6 | visible?: boolean;
7 | updateVisible?: (next: boolean) => unknown;
8 | ref?: MutableRefObject;
9 | }
10 |
11 | const defaultContext = {
12 | visible: false,
13 | };
14 |
15 | export const ComboboxContext =
16 | React.createContext(defaultContext);
17 |
--------------------------------------------------------------------------------
/packages/kitchn/src/hoc/with-box/index.ts:
--------------------------------------------------------------------------------
1 | import { useWarning } from "../../hooks";
2 | import { DecoratorProps, withDecorator } from "../with-decorator";
3 |
4 | export type BoxProps = DecoratorProps;
5 |
6 | export const withBox = (
7 | WrappedComponent: React.ComponentType,
8 | ) => {
9 | // eslint-disable-next-line react-hooks/rules-of-hooks
10 | useWarning("withBox is deprecated, please use withDecorator instead.");
11 | return withDecorator(WrappedComponent);
12 | };
13 |
--------------------------------------------------------------------------------
/packages/kitchn/src/hooks/use-navigation-menu-item.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { NavigationMenuItemContext } from "../contexts/navigation-menu-item";
4 |
5 | const useNavigationMenuItem = () => {
6 | const context = React.useContext(NavigationMenuItemContext);
7 | if (!context) {
8 | throw new Error(
9 | "Navigation menu item components must be used within NavigationMenu.Item",
10 | );
11 | }
12 | return context;
13 | };
14 |
15 | export default useNavigationMenuItem;
16 |
--------------------------------------------------------------------------------
/packages/kitchn/src/hooks/use-ssr.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { isBrowser } from "../utils/is-browser";
4 |
5 | export type SSRState = {
6 | isBrowser: boolean;
7 | isServer: boolean;
8 | };
9 |
10 | export const useSSR = (): SSRState => {
11 | const [browser, setBrowser] = React.useState(false);
12 | React.useEffect(() => {
13 | setBrowser(isBrowser());
14 | }, []);
15 |
16 | return {
17 | isBrowser: browser,
18 | isServer: !browser,
19 | };
20 | };
21 |
--------------------------------------------------------------------------------
/packages/kitchn/src/hoc/with-scale/index.ts:
--------------------------------------------------------------------------------
1 | import { useWarning } from "../../hooks";
2 | import { DecoratorProps, withDecorator } from "../with-decorator";
3 |
4 | export type ScaleProps = DecoratorProps;
5 |
6 | export const withScale = (
7 | WrappedComponent: React.ComponentType,
8 | ) => {
9 | // eslint-disable-next-line react-hooks/rules-of-hooks
10 | useWarning("withScale is deprecated, please use withDecorator instead.");
11 | return withDecorator(WrappedComponent);
12 | };
13 |
--------------------------------------------------------------------------------
/docs/screens/showcase/index.tsx:
--------------------------------------------------------------------------------
1 | import { Container } from "kitchn";
2 |
3 | import Featured from "@/components/showcase/featured";
4 | import Projects from "@/components/showcase/projects";
5 | import { sortedProjects } from "@/data/showcases";
6 |
7 | const Showcase: React.FC = () => {
8 | return (
9 |
10 |
11 |
12 |
13 | );
14 | };
15 |
16 | export default Showcase;
17 |
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
3 | "commit": false,
4 | "fixed": [["kitchn"]],
5 | "changelog": [
6 | "@changesets/changelog-github",
7 | { "repo": "tonightpass/kitchn" }
8 | ],
9 | "linked": [],
10 | "access": "public",
11 | "baseBranch": "master",
12 | "updateInternalDependencies": "patch",
13 | "ignore": [],
14 | "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
15 | "updateInternalDependents": "always"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.husky/scripts/validate-branch-name.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | branchName="$(git rev-parse --abbrev-ref HEAD)"
4 | regex="^(develop|master)$|^([a-z0-9-]+)\/([a-z-]+)$"
5 |
6 |
7 | echo "---------[ Branch validation ]--------"
8 | echo ""
9 | if [[ ! $branchName =~ $regex ]] ;
10 | then
11 | echo "Your branch name is not valid! Please check our contributing guidelines:"
12 | echo "https://docs.onruntime.com/contributing"
13 | else
14 | echo "Your branch name is valid!"
15 | fi
16 | echo ""
17 | [[ $branchName =~ $regex ]] || exit 1
--------------------------------------------------------------------------------
/packages/kitchn/src/native/index.ts:
--------------------------------------------------------------------------------
1 | import styled from "styled-components/native";
2 |
3 | // ./styled-components
4 | export * from "./styled-components";
5 |
6 | // ./types
7 | export * from "./types";
8 |
9 | // ./themes
10 | export * from "./themes";
11 |
12 | // ./components
13 | export * from "./components";
14 |
15 | // ./hooks
16 | export * from "./hooks";
17 |
18 | // ./hoc
19 | export * from "./hoc";
20 |
21 | // ./utils
22 | export * from "../utils/_collections";
23 |
24 | const kitchn = styled;
25 | export default kitchn;
26 |
--------------------------------------------------------------------------------
/examples/next-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-app",
3 | "private": true,
4 | "scripts": {
5 | "dev": "next -p 3005",
6 | "build": "next build",
7 | "start": "next start -p 3005"
8 | },
9 | "dependencies": {
10 | "kitchn": "latest",
11 | "next": "^14.2.5",
12 | "react": "^18.2.0",
13 | "react-dom": "^18.2.0"
14 | },
15 | "devDependencies": {
16 | "@types/node": "^18.11.9",
17 | "@types/react": "^18.2.67",
18 | "@types/react-dom": "^18.2.22",
19 | "typescript": "^5.0.4"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/workshop/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "workshop",
3 | "private": true,
4 | "scripts": {
5 | "dev": "next -p 3000",
6 | "build": "next build",
7 | "start": "next start -p 3000"
8 | },
9 | "dependencies": {
10 | "kitchn": "workspace:*",
11 | "next": "^14.0.0",
12 | "react": "^18.2.0",
13 | "react-dom": "^18.2.0",
14 | "react-hook-form": "^7.45.4",
15 | "react-icons": "^5.0.0"
16 | },
17 | "devDependencies": {
18 | "@types/react": "^18.2.67",
19 | "@types/react-dom": "^18.2.22"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/workshop/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import { KitchnProvider, createTheme, tonightpassTheme } from "kitchn";
2 | import { AppProps } from "next/app";
3 |
4 | import "kitchn/fonts.css";
5 |
6 | const App = ({ Component, pageProps }: AppProps) => {
7 | return (
8 |
14 |
15 |
16 | );
17 | };
18 |
19 | export default App;
20 |
--------------------------------------------------------------------------------
/examples/next-pages/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-pages",
3 | "private": true,
4 | "scripts": {
5 | "dev": "next -p 3004",
6 | "build": "next build",
7 | "start": "next start -p 3004"
8 | },
9 | "dependencies": {
10 | "kitchn": "latest",
11 | "next": "^13.0.2",
12 | "react": "^18.2.0",
13 | "react-dom": "^18.2.0"
14 | },
15 | "devDependencies": {
16 | "@types/node": "^18.11.9",
17 | "@types/react": "^18.2.67",
18 | "@types/react-dom": "^18.2.22",
19 | "typescript": "^5.0.4"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/workshop/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
--------------------------------------------------------------------------------
/examples/next-app/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
--------------------------------------------------------------------------------
/examples/next-pages/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
--------------------------------------------------------------------------------
/.husky/post-receive:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | #
5 | # This hook is triggered after the remote branch is updated.
6 | #
7 |
8 | branchName="$(git rev-parse --abbrev-ref HEAD)"
9 |
10 | if [ "$branchName" = "master" ]; then
11 | packageDir="$(dirname $(readlink -f $0))/.."
12 | packageVersion=$(cat $packageDir/package.json \
13 | | grep version \
14 | | head -1 \
15 | | awk -F: '{ print $2 }' \
16 | | sed 's/[",]//g')
17 |
18 | git tag $packageVersion
19 | git push --tags
20 |
21 | pnpm release
22 | fi
23 |
--------------------------------------------------------------------------------
/packages/kitchn/src/index.ts:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | // ./styled-components
4 | export * from "./styled-components";
5 |
6 | // ./types
7 | export * from "./types";
8 |
9 | // ./themes
10 | export * from "./themes";
11 |
12 | // ./components
13 | export * from "./components";
14 |
15 | // ./contexts
16 | export * from "./contexts";
17 |
18 | // ./hooks
19 | export * from "./hooks";
20 |
21 | // .hoc
22 | export * from "./hoc";
23 |
24 | // ./utils
25 | export * from "./utils";
26 |
27 | const kitchn = styled;
28 | export default kitchn;
29 |
--------------------------------------------------------------------------------
/docs/types/showcase.ts:
--------------------------------------------------------------------------------
1 | export type Project = {
2 | height: number;
3 | width: number;
4 | internalUrl?: string;
5 | url: string;
6 | repo?: string;
7 | src: string;
8 | title: string;
9 | };
10 |
11 | export type Company = {
12 | url: string;
13 | logo: React.ComponentType;
14 | name: string;
15 | projects: Record;
16 | similarWebGlobalRank: number;
17 | style?: React.HTMLAttributes["style"];
18 | };
19 |
20 | export type SortedProject = Project & {
21 | slug: string;
22 | owner: Omit;
23 | };
24 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "tasks": {
4 | "build": {
5 | "dependsOn": [
6 | "^build"
7 | ],
8 | "outputs": [
9 | "dist/**/*.d.ts",
10 | "dist/**/*.mjs",
11 | "dist/**/*.js",
12 | "dist/**/*.css",
13 | ".next/**",
14 | "index.js",
15 | "ssg.js",
16 | "loader.js"
17 | ]
18 | },
19 | "dev": {
20 | "persistent": true,
21 | "cache": false
22 | },
23 | "start": {
24 | "cache": false
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/kitchn/src/hooks/use-warning.ts:
--------------------------------------------------------------------------------
1 | import { NAME } from "../constants";
2 |
3 | const warningStack: { [key: string]: boolean } = {};
4 |
5 | export const useWarning = (message: string, component?: string) => {
6 | const tag = component ? ` [${component}]` : " ";
7 | const log = `[${NAME}]${tag}: ${message}`;
8 |
9 | if (typeof console === "undefined") return;
10 | if (warningStack[log]) return;
11 | warningStack[log] = true;
12 |
13 | if (process.env.NODE_ENV !== "production") {
14 | return console.error(log);
15 | }
16 |
17 | console.warn(log);
18 | };
19 |
--------------------------------------------------------------------------------
/docs/screens/landing/index.tsx:
--------------------------------------------------------------------------------
1 | import { Container } from "kitchn";
2 |
3 | import Examples from "@/components/landing/examples";
4 | import Featured from "@/components/landing/featured";
5 | import Features from "@/components/landing/features";
6 | import Showcase from "@/components/landing/showcase";
7 |
8 | const Landing: React.FC = () => {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 | };
18 |
19 | export default Landing;
20 |
--------------------------------------------------------------------------------
/examples/react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react",
3 | "private": true,
4 | "scripts": {
5 | "dev": "vite --port 3002",
6 | "build": "tsc && vite build",
7 | "preview": "vite preview --port 3002",
8 | "serve": "pnpm preview"
9 | },
10 | "dependencies": {
11 | "kitchn": "latest",
12 | "react": "^18.2.0",
13 | "react-dom": "^18.2.0"
14 | },
15 | "devDependencies": {
16 | "@types/react": "^18.2.67",
17 | "@types/react-dom": "^18.2.22",
18 | "@vitejs/plugin-react-swc": "^3.0.0",
19 | "typescript": "^4.9.3",
20 | "vite": "^4.2.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
36 | # public
37 | public/robots.txt
38 | public/sitemap*.xml
39 |
--------------------------------------------------------------------------------
/packages/kitchn/src/components/global-style/index.ts:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from "styled-components";
2 |
3 | import { mainCss } from "./main";
4 | import { resetCss } from "./reset";
5 | import { themeCss } from "./theme";
6 | import { Themes } from "../../types";
7 | import { KitchnProviderProps } from "../provider";
8 |
9 | export const GlobalStyle = createGlobalStyle<{
10 | staticThemes: Themes;
11 | attribute: KitchnProviderProps["attribute"];
12 | }>`
13 | ${resetCss}
14 | ${themeCss}
15 | ${mainCss}
16 | `;
17 |
18 | export { mainCss, resetCss, themeCss };
19 | export default GlobalStyle;
20 |
--------------------------------------------------------------------------------
/packages/kitchn/src/native/styled-components.ts:
--------------------------------------------------------------------------------
1 | import "styled-components/native";
2 |
3 | import { MainTheme, Theme } from "./types/theme";
4 |
5 | // custom types
6 | declare module "styled-components" {
7 | export interface DefaultTheme extends Theme, MainTheme {}
8 | }
9 |
10 | export type * from "styled-components/native/dist/types";
11 |
12 | // custom exports
13 | export {
14 | ThemeConsumer,
15 | ThemeContext as StyledThemeContext,
16 | ThemeProvider as StyledThemeProvider,
17 | css,
18 | isStyledComponent,
19 | useTheme as useStyledTheme,
20 | withTheme,
21 | } from "styled-components/native";
22 |
--------------------------------------------------------------------------------
/packages/kitchn/src/utils/get-active-mod-map.ts:
--------------------------------------------------------------------------------
1 | import { KeyMod } from "./codes";
2 |
3 | export const getActiveModMap = (
4 | bindings: number[],
5 | ): Record => {
6 | const modBindings = bindings.filter((item: number) => !!KeyMod[item]);
7 | const activeModMap: Record = {
8 | CtrlCmd: false,
9 | Shift: false,
10 | Alt: false,
11 | WinCtrl: false,
12 | };
13 | modBindings.forEach((code) => {
14 | const modKey = KeyMod[code] as keyof typeof KeyMod;
15 | activeModMap[modKey] = true;
16 | });
17 | return activeModMap;
18 | };
19 |
--------------------------------------------------------------------------------
/examples/next-pages/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": false,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "incremental": true,
11 | "esModuleInterop": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "jsx": "preserve"
17 | },
18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
19 | "exclude": ["node_modules"]
20 | }
21 |
--------------------------------------------------------------------------------
/examples/next-app/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { KitchnProvider } from "kitchn";
2 | import { KitchnRegistry } from "kitchn/next";
3 |
4 | import "kitchn/fonts.css";
5 |
6 | export default function RootLayout({ children }: React.PropsWithChildren) {
7 | return (
8 |
9 |
10 | {"Create Kitchn Next"}
11 |
12 |
13 |
14 |
15 | {children}
16 |
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/packages/kitchn/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./theme";
2 | export * from "./window";
3 |
4 | export type KitchnComponent> = {
5 | /**
6 | * The root element.
7 | */
8 | as?: React.ElementType;
9 | /**
10 | * The content, duh.
11 | */
12 | children?: React.ReactNode;
13 | forwardedAs?: React.ElementType;
14 | } & P &
15 | Omit;
16 |
17 | export type NormalSizes = "small" | "normal" | "large";
18 |
19 | export type TriggerTypes = "hover" | "click";
20 |
21 | export type DeepPartial = {
22 | [P in keyof T]?: T[P] extends object ? DeepPartial : T[P];
23 | };
24 |
--------------------------------------------------------------------------------
/examples/react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"],
20 | "references": [{ "path": "./tsconfig.node.json" }]
21 | }
22 |
--------------------------------------------------------------------------------
/docs/pages/docs/hooks.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Custom React hooks for enhanced functionality"
3 | description: "A collection of custom hooks that can be used to enhance the functionality of your React website or web application."
4 | ---
5 |
6 | import { Card, Cards } from 'nextra-theme-docs'
7 | import hooksMetadata from './hooks/_meta.json'
8 |
9 | # Hooks
10 |
11 | Kitchn provides a variety of custom hooks to simplify and enhance the functionality of your projects. Below is a list of all available hooks:
12 |
13 |
14 | {Object.entries(hooksMetadata).map(([key, value]) => (
15 |
16 | ))}
17 |
18 |
--------------------------------------------------------------------------------
/docs/pages/docs/components.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "React components for building websites and applications"
3 | description: "A collection of components that can be used to build a React website or web application."
4 | ---
5 |
6 | import { Card, Cards } from 'nextra-theme-docs'
7 | import componentsMetadata from './components/_meta.json'
8 |
9 | # Components
10 |
11 | Kitchn provides prebuild components to help you build your projects faster. Here is an overview of all the components that are available:
12 |
13 |
14 | {Object.entries(componentsMetadata).map(([key, value]) => (
15 |
16 | ))}
17 |
18 |
--------------------------------------------------------------------------------
/examples/gatsby/gatsby-config.ts:
--------------------------------------------------------------------------------
1 | import type { GatsbyConfig } from "gatsby";
2 |
3 | const config: GatsbyConfig = {
4 | siteMetadata: {
5 | title: "gatsby",
6 | siteUrl: "https://www.yourdomain.tld",
7 | },
8 | // More easily incorporate content into your pages through automatic TypeScript type generation and better GraphQL IntelliSense.
9 | // If you use VSCode you can also use the GraphQL plugin
10 | // Learn more at: https://gatsby.dev/graphql-typegen
11 | graphqlTypegen: true,
12 | plugins: [
13 | "gatsby-plugin-pnpm",
14 | "gatsby-plugin-styled-components",
15 | "babel-plugin-styled-components",
16 | ],
17 | };
18 |
19 | export default config;
20 |
--------------------------------------------------------------------------------
/packages/kitchn/src/native/themes/index.ts:
--------------------------------------------------------------------------------
1 | import { DefaultTheme } from "styled-components";
2 |
3 | import { mainTheme } from "./main";
4 | import { darkTheme } from "../../themes/dark";
5 | import { lightTheme } from "../../themes/light";
6 | import { Theme } from "../types/theme";
7 |
8 | export * from "./main";
9 | export * from "../../themes/dark";
10 | export * from "../../themes/light";
11 | export * from "../../themes/tonightpass";
12 |
13 | export const createTheme = (theme: Theme): DefaultTheme => {
14 | return { ...mainTheme, ...theme };
15 | };
16 |
17 | export const defaultThemes = {
18 | dark: createTheme(darkTheme),
19 | light: createTheme(lightTheme),
20 | };
21 |
--------------------------------------------------------------------------------
/docs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "target": "es5",
5 | "lib": ["dom", "dom.iterable", "esnext"],
6 | "allowJs": true,
7 | "skipLibCheck": true,
8 | "strict": false,
9 | "forceConsistentCasingInFileNames": true,
10 | "noEmit": true,
11 | "incremental": true,
12 | "esModuleInterop": true,
13 | "isolatedModules": true,
14 | "moduleResolution": "node",
15 | "jsx": "preserve",
16 | "module": "esnext",
17 | "resolveJsonModule": true,
18 | "paths": {
19 | "@/*": ["./*"]
20 | }
21 | },
22 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
23 | "exclude": ["node_modules"]
24 | }
25 |
--------------------------------------------------------------------------------
/packages/kitchn/fonts.css:
--------------------------------------------------------------------------------
1 | @import url("@fontsource/figtree");
2 | @import url("@fontsource/figtree/300.css");
3 | @import url("@fontsource/figtree/400.css");
4 | @import url("@fontsource/figtree/500.css");
5 | @import url("@fontsource/figtree/600.css");
6 | @import url("@fontsource/figtree/700.css");
7 | @import url("@fontsource/figtree/800.css");
8 | @import url("@fontsource/figtree/900.css");
9 |
10 | @import url("@fontsource/fira-code");
11 | @import url("@fontsource/fira-code/300.css");
12 | @import url("@fontsource/fira-code/400.css");
13 | @import url("@fontsource/fira-code/500.css");
14 | @import url("@fontsource/fira-code/600.css");
15 | @import url("@fontsource/fira-code/700.css");
16 |
--------------------------------------------------------------------------------
/packages/kitchn/src/utils/pick-child.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const pickChild = (
4 | children: React.ReactNode | undefined,
5 | targetChild: React.ElementType,
6 | ): [React.ReactNode | undefined, React.ReactNode | undefined] => {
7 | const target: React.ReactNode[] = [];
8 | const withoutTargetChildren = React.Children.map(children, (item) => {
9 | if (!React.isValidElement(item)) return item;
10 | if (item.type === targetChild) {
11 | target.push(item);
12 | return null;
13 | }
14 | return item;
15 | });
16 |
17 | const targetChildren = target.length >= 0 ? target : undefined;
18 |
19 | return [withoutTargetChildren, targetChildren];
20 | };
21 |
--------------------------------------------------------------------------------
/packages/kitchn/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./use-breakpoint";
2 | export * from "./use-checkbox";
3 | export * from "./use-click-anywhere";
4 | export * from "./use-click-away";
5 | export * from "./use-clipboard";
6 | export * from "./use-combobox";
7 | export * from "./use-current-state";
8 | export * from "./use-domobserver";
9 | export * from "./use-keyboard";
10 | export * from "./use-media-query";
11 | export * from "./use-modal";
12 | export * from "./use-portal";
13 | export * from "./use-previous";
14 | export * from "./use-rect";
15 | export * from "./use-resize";
16 | export * from "./use-ssr";
17 | export * from "./use-theme";
18 | export * from "./use-toasts";
19 | export * from "./use-warning";
20 |
--------------------------------------------------------------------------------
/docs/pages/docs/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": "Introduction",
3 | "installation": "Installation",
4 | "theming": "Theming",
5 | "styling": "Styling",
6 | "playground": "Playground",
7 | "brands": "Brands",
8 | "figma": "Figma",
9 | "frameworks": "Frameworks",
10 | "components": "Components",
11 | "hooks": "Hooks",
12 | "api-reference": {
13 | "title": "API Reference",
14 | "href": "https://jsdocs.io/package/kitchn",
15 | "newWindow": true
16 | },
17 | "roadmap": {
18 | "title": "Roadmap",
19 | "href": "https://github.com/orgs/tonightpass/projects/2",
20 | "newWindow": true
21 | },
22 | "contributing": "Contributing",
23 | "acknowledgements": "Acknowledgements"
24 | }
25 |
--------------------------------------------------------------------------------
/examples/expo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "expo",
3 | "private": true,
4 | "main": "index.js",
5 | "scripts": {
6 | "start": "expo start",
7 | "dev": "pnpm start",
8 | "android": "expo start --android",
9 | "ios": "expo start --ios",
10 | "web": "expo start --web"
11 | },
12 | "dependencies": {
13 | "kitchn": "workspace:*",
14 | "expo": "~47.0.5",
15 | "expo-status-bar": "~1.4.2",
16 | "react": "^18.1.0",
17 | "react-native": "^0.70.5"
18 | },
19 | "devDependencies": {
20 | "@babel/core": "^7.19.6",
21 | "@types/expo": "^33.0.1",
22 | "@types/react": "^18.2.67",
23 | "@types/react-dom": "^18.2.22",
24 | "@types/react-native": "^0.70.6"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/kitchn/src/components/list/item/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | import { withDecorator } from "../../../hoc/with-decorator";
5 | import { KitchnComponent } from "../../../types";
6 |
7 | export type ListItemProps = KitchnComponent<
8 | object,
9 | React.LiHTMLAttributes
10 | >;
11 |
12 | const ListItemComponent = styled((props: ListItemProps) => {
13 | return ;
14 | })`
15 | margin-top: 10px;
16 | &:first-child {
17 | margin-top: 0;
18 | }
19 | `;
20 |
21 | ListItemComponent.displayName = "KitchnListItem";
22 | export const ListItem = withDecorator(ListItemComponent);
23 | export default ListItem;
24 |
--------------------------------------------------------------------------------
/packages/kitchn/src/styled-components.ts:
--------------------------------------------------------------------------------
1 | import "styled-components";
2 |
3 | import { MainTheme, Theme } from "./types/theme";
4 |
5 | // custom types
6 | declare module "styled-components" {
7 | export interface DefaultTheme extends Theme, MainTheme {}
8 | }
9 |
10 | export type * from "styled-components/dist/types";
11 | // custom exports
12 | export {
13 | ThemeContext as StyledThemeContext,
14 | ThemeProvider as StyledThemeProvider,
15 | useTheme as useStyledTheme,
16 | ServerStyleSheet,
17 | StyleSheetConsumer,
18 | StyleSheetContext,
19 | StyleSheetManager,
20 | ThemeConsumer,
21 | createGlobalStyle,
22 | css,
23 | isStyledComponent,
24 | keyframes,
25 | withTheme,
26 | } from "styled-components";
27 |
--------------------------------------------------------------------------------
/docs/pages/docs/hooks/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "use-breakpoint": "useBreakpoint",
3 | "use-checkbox": "useCheckbox",
4 | "use-clickanywhere": "useClickAnyWhere",
5 | "use-clickaway": "useClickAway",
6 | "use-clipboard": "useClipboard",
7 | "use-combobox": "useCombobox",
8 | "use-currentstate": "useCurrentState",
9 | "use-domobserver": "useDOMObserver",
10 | "use-keyboard": "useKeyboard",
11 | "use-mediaquery": "useMediaQuery",
12 | "use-modal": "useModal",
13 | "use-portal": "usePortal",
14 | "use-previous": "usePrevious",
15 | "use-rect": "useRect",
16 | "use-resize": "useResize",
17 | "use-ssr": "useSSR",
18 | "use-theme": "useTheme",
19 | "use-toasts": "useToasts",
20 | "use-warning": "useWarning"
21 | }
22 |
--------------------------------------------------------------------------------
/docs/components/youtube-video/index.tsx:
--------------------------------------------------------------------------------
1 | import kitchn from "kitchn";
2 |
3 | const YoutubeVideo: React.FC = ({ ...props }) => {
4 | return (
5 |
13 | );
14 | };
15 |
16 | const Iframe = kitchn.iframe`
17 | aspect-ratio: 16 / 9;
18 | width: 100%;
19 | border: none;
20 | border: 1px solid ${({ theme }) => theme.colors.layout.dark};
21 | border-radius: ${({ theme }) => theme.radius.square};
22 | margin-top: 1rem;
23 | margin-bottom: 1rem;
24 | `;
25 |
26 | export default YoutubeVideo;
27 |
--------------------------------------------------------------------------------
/packages/kitchn/src/hooks/use-breakpoint.ts:
--------------------------------------------------------------------------------
1 | import { useTheme } from "styled-components";
2 |
3 | import { useMediaQuery } from "./use-media-query";
4 |
5 | export type UseBreakpointResult = {
6 | isMobile: boolean;
7 | isTablet: boolean;
8 | isLaptop: boolean;
9 | isDesktop: boolean;
10 | };
11 |
12 | export const useBreakpoint = (): UseBreakpointResult => {
13 | const theme = useTheme();
14 |
15 | return {
16 | isMobile: useMediaQuery(`(max-width: ${theme.breakpoint.mobile})`),
17 | isTablet: useMediaQuery(`(max-width: ${theme.breakpoint.tablet})`),
18 | isLaptop: useMediaQuery(`(max-width: ${theme.breakpoint.laptop})`),
19 | isDesktop: useMediaQuery(`(max-width: ${theme.breakpoint.desktop})`),
20 | };
21 | };
22 |
--------------------------------------------------------------------------------
/docs/pages/_app.mdx:
--------------------------------------------------------------------------------
1 | import { KitchnProvider } from "kitchn";
2 | import { useTheme as useNextraTheme } from "nextra-theme-docs";
3 |
4 | import GlobalStyle from "@/components/global-style";
5 | import Script from "@/components/script";
6 |
7 | import "kitchn/fonts.css";
8 |
9 | export default function App({ Component, pageProps }) {
10 | const { resolvedTheme } = useNextraTheme();
11 |
12 | return (
13 | // eslint-disable-next-line react/jsx-filename-extension
14 |
19 |
20 |
21 |
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/packages/kitchn/src/native/components/provider/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DefaultTheme } from "styled-components/native";
3 |
4 | import { ThemeProvider } from "../../contexts/theme";
5 | import { withScale } from "../../hoc";
6 |
7 | export type KitchnProviderProps = {
8 | children?: React.ReactNode;
9 | theme?: DefaultTheme;
10 | };
11 |
12 | const KitchnProviderComponent: React.FC = ({
13 | children,
14 | theme,
15 | }: KitchnProviderProps) => {
16 | return {children};
17 | };
18 |
19 | KitchnProviderComponent.displayName = "KitchnProvider";
20 | export const KitchnProvider = withScale(KitchnProviderComponent);
21 | export default KitchnProvider;
22 |
--------------------------------------------------------------------------------
/examples/expo/App.tsx:
--------------------------------------------------------------------------------
1 | import { StatusBar } from "expo-status-bar";
2 | import { KitchnProvider, Text as KitchnText } from "kitchn/native";
3 | import { StyleSheet, Text, View } from "react-native";
4 |
5 | export default function App() {
6 | return (
7 |
8 |
9 | {"Open up App.js to start working on your app! ddd"}
10 |
11 | {"hey"}
12 |
13 |
14 | );
15 | }
16 |
17 | const styles = StyleSheet.create({
18 | container: {
19 | flex: 1,
20 | backgroundColor: "#fff",
21 | alignItems: "center",
22 | justifyContent: "center",
23 | },
24 | });
25 |
--------------------------------------------------------------------------------
/packages/kitchn/src/components/list/ordered/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | import { withDecorator } from "../../../hoc/with-decorator";
5 | import { KitchnComponent } from "../../../types";
6 |
7 | export type OrderedListProps = KitchnComponent<
8 | object,
9 | React.OlHTMLAttributes
10 | >;
11 |
12 | const OrderedListComponent = styled((props: OrderedListProps) => {
13 | return
;
14 | })`
15 | padding: 0;
16 | margin: 15px;
17 | margin-left: 25px;
18 | list-style-type: decimal;
19 | `;
20 |
21 | OrderedListComponent.displayName = "KitchnOrderedList";
22 | export const OrderedList = withDecorator(OrderedListComponent);
23 | export default OrderedList;
24 |
--------------------------------------------------------------------------------
/packages/kitchn/src/hooks/use-domobserver.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const useDOMObserver = (
4 | ref: React.MutableRefObject | undefined,
5 | callback: MutationCallback = () => {},
6 | ) => {
7 | const config = { attributes: false, childList: true, subtree: true };
8 |
9 | React.useEffect(() => {
10 | if (!ref || !ref.current) return;
11 | let unmount = false;
12 | const done: MutationCallback = (...params) => {
13 | if (unmount) return;
14 | callback(...params);
15 | };
16 | const observer = new MutationObserver(done);
17 | observer.observe(ref.current, config);
18 | return () => {
19 | unmount = true;
20 | observer.disconnect();
21 | };
22 | }, [ref]);
23 | };
24 |
--------------------------------------------------------------------------------
/examples/gatsby/gatsby-ssr.tsx:
--------------------------------------------------------------------------------
1 | import { KitchnProvider, ServerStyleSheet, StyleSheetManager } from "kitchn";
2 | import React from "react";
3 |
4 | import "kitchn/fonts.css";
5 |
6 | const sheetByPathname = new Map();
7 |
8 | export const wrapRootElement = ({ element, pathname }) => {
9 | const sheet = new ServerStyleSheet();
10 | sheetByPathname.set(pathname, sheet);
11 | return (
12 |
13 | {element}
14 |
15 | );
16 | };
17 |
18 | export const onRenderBody = ({ setHeadComponents, pathname }) => {
19 | const sheet = sheetByPathname.get(pathname);
20 | if (sheet) {
21 | setHeadComponents([sheet.getStyleElement()]);
22 | sheetByPathname.delete(pathname);
23 | }
24 | };
25 |
--------------------------------------------------------------------------------
/docs/components/landing/showcase/index.tsx:
--------------------------------------------------------------------------------
1 | import showcases from "data/showcases";
2 | import { Container, Text } from "kitchn";
3 |
4 | import ShowcaseList from "./list";
5 |
6 | const Showcase: React.FC = () => {
7 | return (
8 |
9 |
10 | {"Trusted by teams"}{" "}
11 |
12 | {"around the world\r"}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 | };
22 |
23 | export default Showcase;
24 |
--------------------------------------------------------------------------------
/packages/kitchn/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "jsx": "preserve",
6 | "lib": ["dom", "esnext"],
7 | "importHelpers": true,
8 | "declaration": true,
9 | "sourceMap": true,
10 | "resolveJsonModule": true,
11 | "esModuleInterop": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "isolatedModules": true,
14 | "moduleResolution": "node",
15 | "preserveWatchOutput": true,
16 | "skipLibCheck": true,
17 | "noEmit": true,
18 | "strict": true,
19 | "noImplicitReturns": true,
20 | "noFallthroughCasesInSwitch": true,
21 | "noUnusedLocals": true,
22 | "noUnusedParameters": true
23 | },
24 | "include": ["**/*.ts", "**/*.tsx"],
25 | "exclude": ["node_modules", "dist"]
26 | }
--------------------------------------------------------------------------------
/examples/expo/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "expo",
4 | "slug": "expo",
5 | "version": "1.0.0",
6 | "orientation": "portrait",
7 | "icon": "./assets/icon.png",
8 | "userInterfaceStyle": "light",
9 | "splash": {
10 | "image": "./assets/splash.png",
11 | "resizeMode": "contain",
12 | "backgroundColor": "#ffffff"
13 | },
14 | "updates": {
15 | "fallbackToCacheTimeout": 0
16 | },
17 | "assetBundlePatterns": ["**/*"],
18 | "ios": {
19 | "supportsTablet": true
20 | },
21 | "android": {
22 | "adaptiveIcon": {
23 | "foregroundImage": "./assets/adaptive-icon.png",
24 | "backgroundColor": "#FFFFFF"
25 | }
26 | },
27 | "web": {
28 | "favicon": "./assets/favicon.png"
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/kitchn/src/components/link/fragment/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | import Link, { LinkProps } from "..";
5 | import { withDecorator } from "../../../hoc/with-decorator";
6 | import { KitchnComponent } from "../../../types";
7 |
8 | type Props = {
9 | /**
10 | * The link's id.
11 | */
12 | id: string;
13 | };
14 |
15 | export type FragmentLinkProps = KitchnComponent;
16 |
17 | const FragmentLinkComponent = styled(({ id, ...props }: FragmentLinkProps) => {
18 | const href = `#${id}`;
19 | return ;
20 | })``;
21 |
22 | FragmentLinkComponent.displayName = "KitchnFragmentLink";
23 | export const FragmentLink = withDecorator(FragmentLinkComponent);
24 | export default FragmentLink;
25 |
--------------------------------------------------------------------------------
/packages/kitchn/src/hooks/use-click-away.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export type ClickAwayGetContainer = () => HTMLElement | null;
4 |
5 | export const useClickAway = (
6 | ref: React.MutableRefObject,
7 | handler: (event: Event) => void,
8 | ) => {
9 | const handlerRef = React.useRef(handler);
10 | React.useEffect(() => {
11 | handlerRef.current = handler;
12 | }, [handler]);
13 |
14 | React.useEffect(() => {
15 | const callback = (event: MouseEvent) => {
16 | const el = ref.current;
17 | if (!event || !el || el.contains(event.target as Node)) return;
18 | handlerRef.current(event);
19 | };
20 |
21 | document.addEventListener("click", callback);
22 | return () => document.removeEventListener("click", callback);
23 | }, [ref]);
24 | };
25 |
--------------------------------------------------------------------------------
/examples/next-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": false,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "incremental": true,
15 | "esModuleInterop": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "jsx": "preserve",
21 | "plugins": [
22 | {
23 | "name": "next"
24 | }
25 | ]
26 | },
27 | "include": [
28 | "next-env.d.ts",
29 | "**/*.ts",
30 | "**/*.tsx",
31 | ".next/types/**/*.ts"
32 | ],
33 | "exclude": [
34 | "node_modules"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/docs/pages/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Kitchn: Modern React Styled-Components UI Kit"
3 | description: UI kit for React developers to build beautiful web apps
4 | ---
5 | import Landing from '@/screens/landing';
6 |
7 |
8 |
9 | export const getStaticProps = async ({ params }) => {
10 | const repoRes = await fetch('https://api.github.com/repos/tonightpass/kitchn');
11 | const repo = await repoRes.json();
12 |
13 | const contributorsRes = await fetch(repo.contributors_url);
14 | let contributors = await contributorsRes.json();
15 | // sort by contributions most to least
16 | contributors = contributors.sort((a, b) => b.contributions - a.contributions);
17 |
18 | return {
19 | props: {
20 | ssg: {
21 | repo,
22 | contributors
23 | }
24 | },
25 | revalidate: 60
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/workshop/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": false,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "incremental": true,
15 | "esModuleInterop": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "jsx": "preserve",
21 | "plugins": [
22 | {
23 | "name": "next"
24 | }
25 | ],
26 | "strictNullChecks": true
27 | },
28 | "include": [
29 | "next-env.d.ts",
30 | "**/*.ts",
31 | "**/*.tsx",
32 | ".next/types/**/*.ts"
33 | ],
34 | "exclude": [
35 | "node_modules"
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/packages/kitchn/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, type Options } from "tsup";
2 |
3 | const config: Options = {
4 | format: ["cjs", "esm"],
5 | entry: [
6 | "src/index.ts",
7 | "src/next/index.ts",
8 | "src/next/document/index.tsx",
9 | "src/next/registry/index.tsx",
10 | "src/native/index.ts",
11 | "src/logos/index.ts",
12 | ],
13 | splitting: true,
14 | treeshake: false, // https://github.com/egoist/tsup/issues/835
15 | sourcemap: true,
16 | clean: false,
17 | platform: "browser",
18 | dts: true,
19 | minify: true,
20 | outExtension({ format }) {
21 | return {
22 | js: `.${format}.js`,
23 | mjs: `.${format}.js`,
24 | };
25 | },
26 | esbuildOptions: (options) => {
27 | options.banner = {
28 | // eslint-disable-next-line quotes
29 | js: '"use client"',
30 | };
31 | },
32 | };
33 |
34 | export default defineConfig(config);
35 |
--------------------------------------------------------------------------------
/docs/components/landing/showcase/list/card/index.tsx:
--------------------------------------------------------------------------------
1 | import kitchn from "kitchn";
2 | import { Company, SortedProject } from "types/showcase";
3 |
4 | export type ShowcaseCardProps = {
5 | company: Company | SortedProject["owner"];
6 | };
7 |
8 | const ShowcaseCard: React.FC = ({
9 | company,
10 | }: ShowcaseCardProps) => {
11 | const { logo: Logo } = company;
12 |
13 | return (
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | const Container = kitchn.span`
21 | position: relative;
22 | height: 2rem;
23 | margin: 0 1rem;
24 | bottom: 0;
25 | opacity: 0.8;
26 | transition: opacity 125ms ease-in-out;
27 |
28 | .dark & {
29 | filter: brightness(0) invert(1);
30 | }
31 |
32 | svg {
33 | height: 100%;
34 | max-width: 128px;
35 | }
36 |
37 | &:hover {
38 | opacity: 1;
39 | }
40 | `;
41 |
42 | export default ShowcaseCard;
43 |
--------------------------------------------------------------------------------
/docs/public/static/images/showcases/thumbnails/onruntime.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/kitchn/src/utils/get-translate-by-placement.ts:
--------------------------------------------------------------------------------
1 | import { ToastPlacement } from "../hooks/use-toasts";
2 |
3 | export const getTranslateByPlacement = (
4 | placement: ToastPlacement,
5 | ): {
6 | enter: string;
7 | leave: string;
8 | } => {
9 | const translateInByPlacement: Record = {
10 | topLeft: "translate(-60px, -60px)",
11 | topRight: "translate(60px, -60px)",
12 | bottomLeft: "translate(-60px, 60px)",
13 | bottomRight: "translate(60px, 60px)",
14 | };
15 | const translateOutByPlacement: Record = {
16 | topLeft: "translate(-50px, 15px) scale(0.85)",
17 | topRight: "translate(50px, 15px) scale(0.85)",
18 | bottomLeft: "translate(-50px, -15px) scale(0.85)",
19 | bottomRight: "translate(50px, -15px) scale(0.85)",
20 | };
21 | return {
22 | enter: translateInByPlacement[placement],
23 | leave: translateOutByPlacement[placement],
24 | };
25 | };
26 |
--------------------------------------------------------------------------------
/.size-limit.js:
--------------------------------------------------------------------------------
1 | const modifyWebpackConfig = (config) => {
2 | return {
3 | ...config,
4 | resolve: {
5 | ...config.resolve,
6 | fallback: {
7 | path: require.resolve("path-browserify"),
8 | },
9 | },
10 | externals: {
11 | "react-native": true,
12 | "react-native-remix-icon": true,
13 | },
14 | };
15 | };
16 |
17 | module.exports = [
18 | {
19 | path: "packages/kitchn/dist/index.cjs.js",
20 | },
21 | {
22 | path: "packages/kitchn/dist/index.esm.js",
23 | },
24 | {
25 | path: "packages/kitchn/dist/next/index.cjs.js",
26 | modifyWebpackConfig,
27 | },
28 | {
29 | path: "packages/kitchn/dist/next/index.esm.js",
30 | modifyWebpackConfig,
31 | },
32 | {
33 | path: "packages/kitchn/dist/native/index.cjs.js",
34 | modifyWebpackConfig,
35 | },
36 | {
37 | path: "packages/kitchn/dist/native/index.esm.js",
38 | modifyWebpackConfig,
39 | },
40 | ];
41 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "docs",
3 | "private": true,
4 | "scripts": {
5 | "dev": "next -p 3001",
6 | "build": "next build",
7 | "postbuild": "next-sitemap",
8 | "start": "next start -p 3001"
9 | },
10 | "dependencies": {
11 | "@vercel/og": "^0.6.2",
12 | "@vercel/speed-insights": "^1.0.12",
13 | "kitchn": "workspace:*",
14 | "next": "^14.0.4",
15 | "next-sitemap": "^4.2.3",
16 | "next-themes": "^0.4.0",
17 | "nextra": "^2.13.2",
18 | "nextra-theme-docs": "^2.13.2",
19 | "prism-react-renderer": "^2.0.0",
20 | "react": "^18.2.0",
21 | "react-dom": "^18.2.0",
22 | "react-icons": "^5.0.1",
23 | "react-live": "^4.0.0",
24 | "react-wrap-balancer": "^1.1.1",
25 | "urlcat": "^3.0.0"
26 | },
27 | "devDependencies": {
28 | "@types/node": "^22.0.0",
29 | "@types/react": "^18.2.67",
30 | "@types/react-dom": "^18.2.22",
31 | "typescript": "^5.0.3"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/kitchn/src/hooks/use-current-state.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export type CurrentStateType = [
4 | S,
5 | React.Dispatch>,
6 | React.MutableRefObject,
7 | ];
8 |
9 | export const useCurrentState = (
10 | initialState: S | (() => S),
11 | ): CurrentStateType => {
12 | const [state, setState] = React.useState(() => {
13 | return typeof initialState === "function"
14 | ? (initialState as () => S)()
15 | : initialState;
16 | });
17 | const ref = React.useRef(initialState as S);
18 |
19 | React.useEffect(() => {
20 | ref.current = state;
21 | }, [state]);
22 |
23 | const setValue = (val: React.SetStateAction) => {
24 | const result =
25 | typeof val === "function"
26 | ? (val as (prevState: S) => S)(ref.current)
27 | : val;
28 | ref.current = result;
29 | setState(result);
30 | };
31 |
32 | return [state, setValue, ref];
33 | };
34 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": ["config:base"],
4 | "commitMessagePrefix": "⬆️",
5 | "commitMessageAction": "upgrade",
6 | "commitMessageTopic": "`{{depName}}`",
7 | "commitMessageExtra": "to `{{#if isPinDigest}}{{{newDigestShort}}}{{else}}{{#if isMajor}}{{prettyNewMajor}}{{else}}{{#if isSingleVersion}}{{prettyNewVersion}}{{else}}{{#if newValue}}{{{newValue}}}{{else}}{{{newDigestShort}}}{{/if}}{{/if}}{{/if}}{{/if}}`",
8 | "dependencyDashboard": true,
9 | "addLabels": ["dependencies"],
10 | "prConcurrentLimit": 0,
11 | "prHourlyLimit": 0,
12 | "packageRules": [
13 | {
14 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"],
15 | "automerge": true
16 | },
17 | {
18 | "matchDepTypes": ["devDependencies"],
19 | "automerge": true
20 | }
21 | ],
22 | "platformAutomerge": true,
23 | "schedule": [
24 | "every weekend"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/packages/kitchn/src/components/code/inline/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | import { withDecorator } from "../../../hoc/with-decorator";
5 | import { KitchnComponent } from "../../../types";
6 |
7 | export type InlineCodeProps = KitchnComponent<
8 | object,
9 | React.HTMLAttributes
10 | >;
11 |
12 | const InlineCodeComponent = styled((props: InlineCodeProps) => {
13 | return ;
14 | })`
15 | display: inline-block;
16 | background: ${({ theme }) => theme.colors.layout.dark};
17 | color: ${({ theme }) => theme.colors.text.lighter};
18 | border-radius: 5px;
19 | font-family: ${({ theme }) => theme.family.monospace};
20 | padding: 0 5px;
21 | line-height: 1.5;
22 | white-space: pre-wrap;
23 | `;
24 |
25 | InlineCodeComponent.displayName = "KitchnInlineCode";
26 | export const InlineCode = withDecorator(InlineCodeComponent);
27 | export default InlineCode;
28 |
--------------------------------------------------------------------------------
/examples/expo/metro.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 | // Learn more https://docs.expo.dev/guides/monorepos
3 | const { getDefaultConfig } = require("expo/metro-config");
4 | const path = require("path");
5 |
6 | // Find the project and workspace directories
7 | const projectRoot = __dirname;
8 | // This can be replaced with `find-yarn-workspace-root`
9 | const workspaceRoot = path.resolve(projectRoot, "../..");
10 |
11 | const config = getDefaultConfig(projectRoot);
12 |
13 | // 1. Watch all files within the monorepo
14 | config.watchFolders = [workspaceRoot];
15 | // 2. Let Metro know where to resolve packages and in what order
16 | config.resolver.nodeModulesPaths = [
17 | path.resolve(projectRoot, "node_modules"),
18 | path.resolve(workspaceRoot, "node_modules"),
19 | ];
20 | // 3. Force Metro to resolve (sub)dependencies only from the `nodeModulesPaths`
21 | config.resolver.disableHierarchicalLookup = true;
22 |
23 | module.exports = config;
24 |
--------------------------------------------------------------------------------
/docs/pages/docs/components/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "avatar": "Avatar",
3 | "badge": "Badge",
4 | "button": "Button",
5 | "calendar": "Calendar",
6 | "checkbox": "Checkbox",
7 | "collapse": "Collapse",
8 | "combobox": "Combobox",
9 | "drawer": "Drawer",
10 | "dropdown": "Dropdown",
11 | "entity": "Entity",
12 | "error": "Error",
13 | "fieldset": "Fieldset",
14 | "footer": "Footer",
15 | "grid": "Grid",
16 | "highlight": "Highlight",
17 | "icon": "Icon",
18 | "image": "Image",
19 | "input": "Input",
20 | "link": "Link",
21 | "modal": "Modal",
22 | "menu": "Menu",
23 | "note": "Note",
24 | "progress": "Progress",
25 | "scroller": "Scroller",
26 | "select": "Select",
27 | "skeleton": "Skeleton",
28 | "snippet": "Snippet",
29 | "spacer": "Spacer",
30 | "spinner": "Spinner",
31 | "switch": "Switch",
32 | "tabs": "Tabs",
33 | "text": "Text",
34 | "textarea": "Textarea",
35 | "toast": "Toast",
36 | "toggle": "Toggle",
37 | "tooltip": "Tooltip"
38 | }
--------------------------------------------------------------------------------
/docs/pages/docs/components/image.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "React Image component"
3 | description: "Display image content."
4 | ---
5 |
6 | import Playground from "@/components/playground";
7 |
8 | # Image
9 |
10 | Display image content.
11 |
12 | ## Usage
13 |
14 | First of all, you need to import the `Image` component from the `kitchn` package.
15 |
16 | ```js
17 | import { Image } from "kitchn"
18 | ```
19 |
20 | ## Static
21 |
22 |
32 | `}
33 | />
34 |
35 | ## Props
36 |
37 | | Name | Type | Default | Required | Description | Accepted values |
38 | | :--- | :--- | :-----: | :------: | :---------- | :-------------- |
39 | | `src` | `string` | `undefined` | ✅ | The URL or path of the image. | - |
40 | | `alt` | `string` | `undefined` | ✅ | The alternative text for the image. | - |
--------------------------------------------------------------------------------
/examples/gatsby/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gatsby",
3 | "private": true,
4 | "scripts": {
5 | "develop": "gatsby develop -p 3003",
6 | "dev": "gatsby develop",
7 | "start": "gatsby serve -p 3003 -H 0.0.0.0",
8 | "build": "gatsby build",
9 | "serve": "gatsby serve",
10 | "clean": "gatsby clean",
11 | "typecheck": "tsc --noEmit"
12 | },
13 | "dependencies": {
14 | "gatsby": "^5.8.1",
15 | "kitchn": "latest",
16 | "react": "^18.2.0",
17 | "react-dom": "^18.2.0"
18 | },
19 | "devDependencies": {
20 | "@types/node": "^18.15.11",
21 | "@types/react": "^18.2.67",
22 | "@types/react-dom": "^18.2.22",
23 | "ajv": "^7",
24 | "babel-plugin-styled-components": "^2.0.7",
25 | "eslint-config-react-app": "^7.0.1",
26 | "eslint-plugin-flowtype": "^8.0.3",
27 | "gatsby-plugin-pnpm": "^1.2.10",
28 | "gatsby-plugin-styled-components": "^6.8.0",
29 | "typescript": "^5.0.3"
30 | },
31 | "engines": {
32 | "node": ">=18.0.0"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/docs/components/blog/list/index.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Link, Text } from "kitchn";
2 | import { getPagesUnderRoute } from "nextra/context";
3 |
4 | const List: React.FC = () => {
5 | return getPagesUnderRoute("/blog").map((page) => {
6 | if (page.kind !== "MdxPage") {
7 | return null;
8 | }
9 | return (
10 |
11 |
12 |
13 | {page.frontMatter?.title || page.meta?.title || page.name}
14 |
15 |
16 |
17 | {page.frontMatter?.description}{" "}
18 |
19 | {"Read more →"}
20 |
21 |
22 |
23 | {page.frontMatter?.date}
24 |
25 |
26 | );
27 | });
28 | };
29 |
30 | export default List;
31 |
--------------------------------------------------------------------------------
/packages/kitchn/src/components/list/unordered/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | import { withDecorator } from "../../../hoc/with-decorator";
5 | import { KitchnComponent } from "../../../types";
6 | import ListItem from "../item";
7 |
8 | export type UnorderedListProps = KitchnComponent<
9 | object,
10 | React.OlHTMLAttributes
11 | >;
12 |
13 | const UnorderedListComponent = styled((props: UnorderedListProps) => {
14 | return ;
15 | })`
16 | padding: 0;
17 | list-style-type: none;
18 | margin: 15px;
19 | margin-left: 25px;
20 | ${ListItem}::before {
21 | content: "-";
22 | color: ${(props) => props.theme.colors.text.darker};
23 | margin-left: -15px;
24 | display: inline-block;
25 | position: absolute;
26 | }
27 | `;
28 |
29 | UnorderedListComponent.displayName = "KitchnUnorderedList";
30 | export const UnorderedList = withDecorator(UnorderedListComponent);
31 | export default UnorderedList;
32 |
--------------------------------------------------------------------------------
/packages/kitchn/src/themes/dark.ts:
--------------------------------------------------------------------------------
1 | import { Theme } from "../types/theme";
2 |
3 | export const darkTheme: Theme = {
4 | name: "dark",
5 | colors: {
6 | layout: {
7 | darkest: "rgb(17, 17, 17)",
8 | darker: "rgb(25, 25, 27)",
9 | dark: "rgb(34, 34, 36)",
10 | light: "rgb(150, 150, 150)",
11 | lighter: "rgb(175, 175, 176)",
12 | lightest: "rgb(255, 255, 255)",
13 | },
14 | text: {
15 | lightest: "rgb(255, 255, 255)",
16 | lighter: "rgb(200, 200, 200)",
17 | light: "rgb(175, 175, 175)",
18 | dark: "rgb(160, 160, 160)",
19 | darker: "rgb(125, 125, 125)",
20 | darkest: "rgb(50, 51, 52)",
21 | },
22 | accent: {
23 | primary: "rgb(80, 60, 245)",
24 | secondary: "rgb(70, 38, 228)",
25 | success: "rgb(46, 204, 113)",
26 | warning: "rgb(241, 196, 15)",
27 | danger: "rgb(231, 76, 60)",
28 | info: "rgb(52, 152, 219)",
29 | light: "rgb(255, 255, 255)",
30 | dark: "rgb(0, 0, 0)",
31 | },
32 | },
33 | };
34 |
--------------------------------------------------------------------------------
/packages/kitchn/src/themes/light.ts:
--------------------------------------------------------------------------------
1 | import { Theme } from "../types/theme";
2 |
3 | export const lightTheme: Theme = {
4 | name: "light",
5 | colors: {
6 | layout: {
7 | darkest: "rgb(252, 252, 253)",
8 | darker: "rgb(239, 240, 243)",
9 | dark: "rgb(231, 232, 236)",
10 | light: "rgb(125, 118, 108)",
11 | lighter: "rgb(100, 100, 99)",
12 | lightest: "rgb(0, 0, 0)",
13 | },
14 | text: {
15 | lightest: "rgb(0, 0, 0)",
16 | lighter: "rgb(55, 55, 55)",
17 | light: "rgb(105, 105, 105)",
18 | dark: "rgb(120, 120, 120)",
19 | darker: "rgb(155, 155, 155)",
20 | darkest: "rgb(225, 224, 223)",
21 | },
22 | accent: {
23 | primary: "rgb(80, 60, 245)",
24 | secondary: "rgb(70, 38, 228)",
25 | success: "rgb(46, 204, 113)",
26 | warning: "rgb(241, 196, 15)",
27 | danger: "rgb(231, 76, 60)",
28 | info: "rgb(52, 152, 219)",
29 | light: "rgb(255, 255, 255)",
30 | dark: "rgb(0, 0, 0)",
31 | },
32 | },
33 | };
34 |
--------------------------------------------------------------------------------
/packages/kitchn/src/next/document/index.tsx:
--------------------------------------------------------------------------------
1 | import Document, { DocumentContext, DocumentInitialProps } from "next/document";
2 | import React from "react";
3 | import { ServerStyleSheet } from "styled-components";
4 |
5 | export class KitchnDocument extends Document {
6 | static async getInitialProps(
7 | ctx: DocumentContext,
8 | ): Promise {
9 | const sheet = new ServerStyleSheet();
10 | const originalRenderPage = ctx.renderPage;
11 |
12 | try {
13 | ctx.renderPage = () =>
14 | originalRenderPage({
15 | enhanceApp: (App) => (props) =>
16 | sheet.collectStyles(),
17 | });
18 |
19 | const initialProps = await Document.getInitialProps(ctx);
20 | return {
21 | ...initialProps,
22 | styles: (
23 | <>
24 | {initialProps.styles}
25 | {sheet.getStyleElement()}
26 | >
27 | ),
28 | };
29 | } finally {
30 | sheet.seal();
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/kitchn/src/native/themes/main.ts:
--------------------------------------------------------------------------------
1 | import { MainTheme } from "../types/theme";
2 |
3 | export const mainTheme: MainTheme = {
4 | family: {
5 | primary: "Figtree",
6 | monospace: "Figtree",
7 | },
8 | weight: {
9 | thin: 100,
10 | extraLight: 200,
11 | light: 300,
12 | regular: 400,
13 | medium: 500,
14 | semiBold: 600,
15 | bold: 700,
16 | extraBold: 800,
17 | black: 900,
18 | },
19 | size: {
20 | extraTitle: "48px",
21 | title: "32px",
22 | large: "24px",
23 | medium: "18px",
24 | normal: "16px",
25 | compact: "14px",
26 | small: "13px",
27 | tiny: "11px",
28 | },
29 | breakpoint: {
30 | desktop: "1824px",
31 | laptop: "1224px",
32 | tablet: "1024px",
33 | mobile: "768px",
34 | },
35 | gap: {
36 | tiny: "5px",
37 | small: "10px",
38 | normal: "15px",
39 | medium: "20px",
40 | large: "30px",
41 | extraLarge: "60px",
42 | },
43 | radius: {
44 | square: "8px",
45 | round: "99999px",
46 | },
47 | };
48 |
--------------------------------------------------------------------------------
/packages/kitchn/src/themes/tonightpass.ts:
--------------------------------------------------------------------------------
1 | import { Theme } from "../types/theme";
2 |
3 | export const tonightpassTheme: Theme = {
4 | name: "tonightpass",
5 | colors: {
6 | layout: {
7 | darkest: "rgb(5, 21, 39)",
8 | darker: "rgb(6, 25, 46)",
9 | dark: "rgb(52, 68, 111)",
10 | light: "rgb(130, 137, 147)",
11 | lighter: "rgb(155, 155, 156)",
12 | lightest: "rgb(255, 255, 255)",
13 | },
14 | text: {
15 | lightest: "rgb(255, 255, 255)",
16 | lighter: "rgb(200, 200, 200)",
17 | light: "rgb(150, 150, 150)",
18 | dark: "rgb(135, 135, 135)",
19 | darker: "rgb(100, 100, 100)",
20 | darkest: "rgb(30, 31, 32)",
21 | },
22 | accent: {
23 | primary: "rgb(176, 36, 241)",
24 | secondary: "rgb(176, 96, 241)",
25 | success: "rgb(46, 204, 113)",
26 | warning: "rgb(241, 196, 15)",
27 | danger: "rgb(231, 76, 60)",
28 | info: "rgb(52, 152, 219)",
29 | light: "rgb(255, 255, 255)",
30 | dark: "rgb(0, 0, 0)",
31 | },
32 | },
33 | };
34 |
--------------------------------------------------------------------------------
/packages/kitchn/src/themes/main.ts:
--------------------------------------------------------------------------------
1 | import { MainTheme } from "../types/theme";
2 |
3 | export const mainTheme: MainTheme = {
4 | family: {
5 | primary: "Figtree, -apple-system, sans-serif",
6 | monospace: "Fira Code, monospace",
7 | },
8 | weight: {
9 | thin: 100,
10 | extraLight: 200,
11 | light: 300,
12 | regular: 400,
13 | medium: 500,
14 | semiBold: 600,
15 | bold: 700,
16 | extraBold: 800,
17 | black: 900,
18 | },
19 | size: {
20 | extraTitle: "48px",
21 | title: "32px",
22 | large: "24px",
23 | medium: "18px",
24 | normal: "16px",
25 | compact: "14px",
26 | small: "13px",
27 | tiny: "11px",
28 | },
29 | breakpoint: {
30 | desktop: "1824px",
31 | laptop: "1224px",
32 | tablet: "1024px",
33 | mobile: "768px",
34 | },
35 | gap: {
36 | tiny: "5px",
37 | small: "10px",
38 | normal: "15px",
39 | medium: "20px",
40 | large: "30px",
41 | extraLarge: "60px",
42 | },
43 | radius: {
44 | square: "8px",
45 | round: "99999px",
46 | },
47 | };
48 |
--------------------------------------------------------------------------------
/docs/components/blog/featured/index.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Text } from "kitchn";
2 | import Balancer from "react-wrap-balancer";
3 |
4 | const Featured: React.FC = () => {
5 | return (
6 |
33 | );
34 | };
35 |
36 | export default Featured;
37 |
--------------------------------------------------------------------------------
/packages/kitchn/src/components/combobox/empty/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { withDecorator } from "../../../hoc";
4 | import { KitchnComponent } from "../../../types";
5 | import Container, { ContainerProps } from "../../container";
6 | import Text, { TextProps } from "../../text";
7 |
8 | type Props = {
9 | textProps?: TextProps;
10 | };
11 |
12 | export type ComboboxEmptyProps = KitchnComponent;
13 |
14 | const ComboboxEmptyComponent: React.FC = ({
15 | children,
16 | textProps,
17 | ...props
18 | }) => {
19 | return (
20 |
27 |
28 | {children}
29 |
30 |
31 | );
32 | };
33 |
34 | ComboboxEmptyComponent.displayName = "KitchenComboboxEmpty";
35 | export const ComboboxEmpty = withDecorator(ComboboxEmptyComponent);
36 | export default ComboboxEmpty;
37 |
--------------------------------------------------------------------------------
/packages/kitchn/src/next/registry/index.tsx:
--------------------------------------------------------------------------------
1 | import { useServerInsertedHTML } from "next/navigation";
2 | import React from "react";
3 |
4 | import { ServerStyleSheet, StyleSheetManager } from "../../styled-components";
5 |
6 | export type KitchnRegistryProps = {
7 | children?: React.ReactNode;
8 | };
9 |
10 | export const KitchnRegistry: React.FC = ({
11 | children,
12 | }: KitchnRegistryProps) => {
13 | // Only create stylesheet once with lazy initial state
14 | // x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
15 | const [styledComponentsStyleSheet] = React.useState(
16 | () => new ServerStyleSheet(),
17 | );
18 |
19 | useServerInsertedHTML(() => {
20 | const styles = styledComponentsStyleSheet.getStyleElement();
21 | styledComponentsStyleSheet.instance.clearTag();
22 | return <>{styles}>;
23 | });
24 |
25 | if (typeof window !== "undefined") return <>{children}>;
26 |
27 | return (
28 |
29 | {children}
30 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/workshop/pages/calendar.tsx:
--------------------------------------------------------------------------------
1 | import { Calendar, Container, DateRange, Text } from "kitchn";
2 | import { NextPage } from "next";
3 | import React from "react";
4 |
5 | const CalendarPage: NextPage = () => {
6 | const [selected, setSelected] = React.useState();
7 | const [days, setDays] = React.useState();
8 | const [range, setRange] = React.useState();
9 |
10 | return (
11 |
12 |
13 | {"Single"}
14 |
15 |
16 |
17 |
18 | {"Multiple"}
19 |
20 |
21 |
22 |
23 | {"Range"}
24 |
25 |
26 |
27 | );
28 | };
29 |
30 | export default CalendarPage;
31 |
--------------------------------------------------------------------------------
/packages/kitchn/src/components/combobox/searching/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { withDecorator } from "../../../hoc";
4 | import { KitchnComponent } from "../../../types";
5 | import Container, { ContainerProps } from "../../container";
6 | import Text, { TextProps } from "../../text";
7 |
8 | type Props = {
9 | textProps?: TextProps;
10 | };
11 |
12 | export type ComboboxSearchingProps = KitchnComponent;
13 |
14 | const ComboboxSearchingComponent: React.FC = ({
15 | children,
16 | textProps,
17 | ...props
18 | }) => {
19 | return (
20 |
27 |
28 | {children}
29 |
30 |
31 | );
32 | };
33 |
34 | ComboboxSearchingComponent.displayName = "KitchenComboboxSearching";
35 | export const ComboboxSearching = withDecorator(ComboboxSearchingComponent);
36 | export default ComboboxSearching;
37 |
--------------------------------------------------------------------------------
/packages/kitchn/src/components/global-style/theme.ts:
--------------------------------------------------------------------------------
1 | import { css } from "styled-components";
2 |
3 | import { PREFIX } from "../../constants";
4 | import { convertThemeToCssVariables } from "../../themes";
5 | import { Themes } from "../../types";
6 | import { KitchnProviderProps } from "../provider";
7 |
8 | export const generateThemeCss = (
9 | attribute: KitchnProviderProps["attribute"],
10 | themes: Themes,
11 | theme: keyof Themes,
12 | ) => {
13 | theme = theme.trim();
14 |
15 | return `
16 | ${attribute === "class" ? `:root.${theme}` : `[${attribute}="${theme}"]`} {
17 | ${convertThemeToCssVariables(themes[theme], PREFIX)}
18 | }
19 | `;
20 | };
21 |
22 | export const themeCss = css<{
23 | staticThemes: Themes;
24 | attribute: KitchnProviderProps["attribute"];
25 | }>`
26 | ${({ staticThemes, attribute }) => `
27 | :root {
28 | ${convertThemeToCssVariables(staticThemes.light, PREFIX)}
29 | }
30 |
31 | ${Object.keys(staticThemes)
32 | .filter((theme) => theme !== "light")
33 | .map((theme) => generateThemeCss(attribute, staticThemes, theme))}
34 | `}
35 | `;
36 |
--------------------------------------------------------------------------------
/packages/kitchn/src/hoc/with-decorator/background.ts:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from "react";
2 | import { css } from "styled-components";
3 |
4 | import { AccentColors, LayoutColors } from "../../types/theme";
5 |
6 | export type BackgroundProps = {
7 | background?: CSSProperties["background"] | keyof LayoutColors;
8 | bg?: BackgroundProps["background"];
9 | backgroundColor?: CSSProperties["backgroundColor"] | keyof LayoutColors;
10 | bgc?: BackgroundProps["backgroundColor"];
11 | backgroundAccent?: keyof AccentColors;
12 | bga?: BackgroundProps["backgroundAccent"];
13 | };
14 |
15 | export const backgroundCss = css`
16 | ${({ theme, background, bg, backgroundColor, bgc, backgroundAccent, bga }) =>
17 | (background || bg || backgroundColor || bgc || backgroundAccent || bga) &&
18 | `background: ${
19 | theme.colors.layout[(background || bg) as keyof LayoutColors] ||
20 | theme.colors.layout[(backgroundColor || bgc) as keyof LayoutColors] ||
21 | theme.colors.accent[(backgroundAccent || bga) as keyof AccentColors] ||
22 | background ||
23 | bg
24 | };`}
25 | `;
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Tonight Pass
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/workshop/pages/image.tsx:
--------------------------------------------------------------------------------
1 | import { Image, Container } from "kitchn";
2 | import { NextPage } from "next";
3 | import React from "react";
4 |
5 | const ImagePage: NextPage = () => {
6 | return (
7 | <>
8 |
15 | {"static"}
16 |
23 | {"gif with caption"}
24 |
25 |
36 |
37 |
38 | >
39 | );
40 | };
41 |
42 | export default ImagePage;
43 |
--------------------------------------------------------------------------------
/packages/kitchn/src/hooks/use-portal.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { useSSR } from "./use-ssr";
4 | import { PREFIX } from "../constants";
5 | import { getId } from "../utils/get-id";
6 |
7 | export const createElement = (id: string): HTMLElement => {
8 | const el = document.createElement(id);
9 | return el;
10 | };
11 |
12 | export const usePortal = (
13 | selectId: string = getId(),
14 | getContainer?: () => HTMLElement | null,
15 | ): HTMLElement | null => {
16 | const id = `${PREFIX}-portal-${selectId}`;
17 | const { isBrowser } = useSSR();
18 |
19 | const [elSnapshot, setElSnapshot] = React.useState(
20 | isBrowser ? createElement(id) : null,
21 | );
22 |
23 | React.useEffect(() => {
24 | const customContainer = getContainer ? getContainer() : null;
25 | const parentElement = customContainer || document.body;
26 | const hasElement = parentElement.querySelector(id);
27 | const el = hasElement || createElement(id);
28 |
29 | if (!hasElement) {
30 | parentElement.appendChild(el);
31 | }
32 | setElSnapshot(el);
33 | }, [getContainer, id]);
34 |
35 | return elSnapshot;
36 | };
37 |
--------------------------------------------------------------------------------
/docs/next.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 | const { withKitchnConfig } = require("kitchn/next");
3 | const nextra = require("nextra");
4 |
5 | const withNextra = nextra({
6 | theme: "nextra-theme-docs",
7 | themeConfig: "./theme.config.tsx",
8 | });
9 |
10 | module.exports = withNextra(
11 | withKitchnConfig({
12 | reactStrictMode: true,
13 | async redirects() {
14 | return [
15 | {
16 | source: "/docs/frameworks/nextjs",
17 | destination: "/docs/frameworks/nextjs-pages",
18 | permanent: true,
19 | },
20 | ];
21 | },
22 | async headers() {
23 | return [
24 | {
25 | source: "/(.*)",
26 | headers: [
27 | {
28 | key: "X-Frame-Options",
29 | value: "SAMEORIGIN",
30 | },
31 | {
32 | key: "X-Content-Type-Options",
33 | value: "nosniff",
34 | },
35 | {
36 | key: "Referrer-Policy",
37 | value: "strict-origin-when-cross-origin",
38 | },
39 | ],
40 | },
41 | ];
42 | },
43 | }),
44 | );
45 |
--------------------------------------------------------------------------------
/docs/pages/docs/figma.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Kitchn Figma design system and components"
3 | description: "Kitchn Figma design system, components, and styles."
4 | ---
5 |
6 | import kitchn, { Container } from "kitchn";
7 | import { Callout } from "nextra-theme-docs";
8 |
9 | export const Iframe = kitchn.iframe`
10 | aspect-ratio: 16 / 9;
11 | width: 100%;
12 | border: none;
13 | border: 1px solid ${({ theme }) => theme.colors.layout.dark};
14 | border-radius: ${({ theme }) => theme.radius.square};
15 | margin-top: 1rem;
16 | margin-bottom: 1rem;
17 | `;
18 |
19 | # Figma
20 |
21 | The Figma UI Kit is open-source and available for anyone to use.
22 |
23 |
24 | The Figma UI Kit is a work in progress and will be updated as we continue to
25 | develop the Kitchn design system, feel free to suggest improvements by [opening
26 | an issue](https://github.com/tonightpass/kitchn/issues/new).
27 |
28 |
29 |
32 |
33 | ## Grab a copy
34 |
35 | https://www.figma.com/community/file/1393011417870200862/kitchn
36 |
--------------------------------------------------------------------------------
/docs/components/brands/showcase/index.tsx:
--------------------------------------------------------------------------------
1 | import kitchn from "kitchn";
2 | import React from "react";
3 |
4 | const Svg = kitchn.svg`
5 | width: inherit;
6 | height: inherit;
7 | `;
8 |
9 | export const OnRuntimeLogo: React.FC> = ({
10 | ...props
11 | }) => (
12 |
28 | );
29 |
--------------------------------------------------------------------------------
/packages/kitchn/src/hooks/use-media-query.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export function useMediaQuery(query: string): boolean {
4 | const getMatches = (query: string): boolean => {
5 | // Prevents SSR issues
6 | if (typeof window !== "undefined") {
7 | return window.matchMedia(query).matches;
8 | }
9 | return false;
10 | };
11 |
12 | const [matches, setMatches] = React.useState(getMatches(query));
13 |
14 | function handleChange() {
15 | setMatches(getMatches(query));
16 | }
17 |
18 | React.useEffect(() => {
19 | const matchMedia = window.matchMedia(query);
20 |
21 | // Triggered at the first client-side load and if query changes
22 | handleChange();
23 |
24 | // Listen matchMedia
25 | if (matchMedia.addListener) {
26 | matchMedia.addListener(handleChange);
27 | } else {
28 | matchMedia.addEventListener("change", handleChange);
29 | }
30 |
31 | return () => {
32 | if (matchMedia.removeListener) {
33 | matchMedia.removeListener(handleChange);
34 | } else {
35 | matchMedia.removeEventListener("change", handleChange);
36 | }
37 | };
38 | // eslint-disable-next-line react-hooks/exhaustive-deps
39 | }, [query]);
40 |
41 | return matches;
42 | }
43 |
--------------------------------------------------------------------------------
/docs/pages/docs/frameworks.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Frameworks compatible with Kitchn"
3 | description: "Frameworks that are compatible with Kitchn."
4 | ---
5 |
6 | import { Card, Cards } from 'nextra-theme-docs'
7 | import { RiReactjsLine, RiNextjsLine, RiGatsbyLine } from "react-icons/ri";
8 |
9 | # Frameworks
10 |
11 | Kitchn is compatible with any framework, and we've put together several guides to help you get started with Kitchn and your preferred framework.
12 |
13 |
14 | } title="Create React App or Vite React" href="/docs/frameworks/create-react-app" />
15 | } title="React Native" href="/docs/frameworks/react-native" />
16 | } title="Next.js (App Router)" href="/docs/frameworks/nextjs-app" />
17 | } title="Next.js (Pages Router)" href="/docs/frameworks/nextjs-pages" />
18 | } title="Gatsby" href="/docs/frameworks/gatsby" />
19 | {/*
20 |
21 | */}
22 |
23 |
--------------------------------------------------------------------------------
/docs/pages/docs/frameworks/react-native.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Getting Started with React Native and Kitchn
3 | description: A guide for installing Kitchn with React Native
4 | tags: ['react-native', 'installation', 'getting-started']
5 | category: frameworks
6 | ---
7 |
8 | import { Callout, Tab, Tabs } from "nextra-theme-docs";
9 |
10 | # Getting Started with React Native
11 |
12 |
13 | This package is still in active development and is not yet ready for
14 | react-native projects.
15 |
16 |
17 | ## Installation
18 | In your React Native or Expo project, install Kitchn by running either of the following:
19 |
20 | ```sh npm2yarn
21 | npm i kitchn --save
22 | ```
23 |
24 | ## Provider Setup
25 |
26 | After installing Kitchn, you need to set up the `KitchnProvider` at the root
27 | of your application. This can be either in your `App.jsx` or `App.tsx` file.
28 |
29 | ```jsx
30 | // App.jsx
31 | import { KitchnProvider } from "kitchn/native";
32 |
33 | export default function App({ Component, pageProps }) {
34 | return (
35 |
36 |
37 |
38 | );
39 | }
40 | ```
41 |
42 | In addition, here is a complete [project example](https://github.com/tonightpass/kitchn/tree/master/examples/expo) using Kitchn with Expo.
--------------------------------------------------------------------------------
/workshop/pages/spacer.tsx:
--------------------------------------------------------------------------------
1 | import { Spacer, Container, Text } from "kitchn";
2 | import { NextPage } from "next";
3 | import React from "react";
4 |
5 | const SpacerPage: NextPage = () => {
6 | return (
7 | <>
8 |
15 | {"vertical"}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | {"horizontal"}
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | >
40 | );
41 | };
42 |
43 | export default SpacerPage;
44 |
--------------------------------------------------------------------------------
/packages/kitchn/src/components/image/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | import { withDecorator } from "../../hoc/with-decorator";
5 | import { KitchnComponent } from "../../types";
6 |
7 | type Props = {
8 | htmlWidth?: string | number;
9 | htmlHeight?: string | number;
10 | src: string;
11 | alt: string;
12 | objectFit?:
13 | | "cover"
14 | | "contain"
15 | | "fill"
16 | | "scale-down"
17 | | "inherit"
18 | | "initial"
19 | | "unset"
20 | | "none";
21 | };
22 |
23 | export type ImageProps = KitchnComponent<
24 | Props,
25 | React.ImgHTMLAttributes
26 | >;
27 |
28 | const ImageComponent = styled(
29 | ({
30 | src,
31 | alt,
32 | htmlWidth,
33 | htmlHeight,
34 | width: _width,
35 | height: _height,
36 | ...props
37 | }: ImageProps) => {
38 | return (
39 |
47 | );
48 | },
49 | )`
50 | ${({ objectFit }) => objectFit && `object-fit: ${objectFit};`}
51 | `;
52 |
53 | ImageComponent.displayName = "KitchnImage";
54 | export const Image = withDecorator(ImageComponent);
55 | export default Image;
56 |
--------------------------------------------------------------------------------
/packages/kitchn/src/contexts/navigation-menu.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { ReactiveDomReact } from "../hooks";
4 |
5 | export type NavigationMenuContextType = {
6 | containerRef: React.MutableRefObject;
7 | handleMouseOver: (event: React.MouseEvent, id: string) => void;
8 | handleMouseLeave: () => void;
9 | handleTooltipMouseEnter: () => void;
10 | handleTooltipMouseLeave: () => void;
11 | activeId: string | null;
12 | previousId: string | null;
13 | tooltipContent: React.ReactNode | null;
14 | setTooltipContent: React.Dispatch>;
15 | displayHighlight: boolean;
16 | setDisplayHighlight: React.Dispatch>;
17 | rect: ReactiveDomReact;
18 | setRect: (
19 | event: React.MouseEvent | React.FocusEvent,
20 | getContainer?: () => HTMLElement | null,
21 | ) => void;
22 | isTooltipHovered: boolean;
23 | setIsTooltipHovered: React.Dispatch>;
24 | highlight: boolean;
25 | hoverHeightRatio: number;
26 | hoverWidthRatio: number;
27 | menuItems: string[];
28 | setMenuItems: React.Dispatch>;
29 | };
30 |
31 | export const NavigationMenuContext =
32 | React.createContext(null);
33 |
--------------------------------------------------------------------------------
/docs/pages/docs/components/spacer.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "React Spacer component"
3 | description: "The Spacer component is a utility for creating consistent spacing between elements. It allows for flexible and responsive gaps in your layout."
4 | ---
5 |
6 | import Playground from "@/components/playground";
7 |
8 | # Spacer
9 |
10 | The Spacer component is a versatile utility designed to simplify the process of adding consistent spacing between elements in your layout. By using the `x` and `y` props, you can easily control the horizontal and vertical spacing, respectively.
11 |
12 | ## Usage
13 |
14 | First of all, you need to import the `Spacer` component from the `kitchn` package.
15 |
16 | ```js
17 | import { Spacer } from "kitchn"
18 | ```
19 |
20 | ## Default
21 |
22 |
25 | Before
26 |
27 | After
28 |
29 | `}
30 | />
31 |
32 | ## Props
33 |
34 | | Name | Type | Default | Required | Description
35 | |------|------|---------|----------|-------------
36 | | `x` | `number \| string \| keyof Gap` | `1px` | No | Specifies the horizontal spacing. Can be a theme gap key, a number multiplier, or a CSS length unit.
37 | | `y` | `number \| string \| keyof Gap` | `1px` | No | Specifies the vertical spacing. Can be a theme gap key, a number multiplier, or a CSS length unit.
38 |
--------------------------------------------------------------------------------
/docs/pages/docs/components/spinner.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "React Spinner component"
3 | description: "The Spinner component is used to indicate loading states in your application. It displays animated bouncing dots to convey progress."
4 | ---
5 |
6 | import Playground from "@/components/playground";
7 |
8 | # Spinner
9 |
10 | The Spinner component is a simple yet effective way to indicate that a process or action is currently loading. It displays three animated bouncing dots, making it clear to users that they should wait for the process to complete.
11 |
12 | ## Usage
13 |
14 | First of all, you need to import the `Spinner` component from the `kitchn` package.
15 |
16 | ```js
17 | import { Spinner } from "kitchn"
18 | ```
19 |
20 | ## Default
21 |
22 |
25 | `}
26 | />
27 |
28 | ## Size
29 |
30 |
33 | `}
34 | />
35 |
36 | ## Color
37 |
38 |
41 | `}
42 | />
43 |
44 | ## Props
45 |
46 | | Name | Type | Default | Required | Description
47 | |------|------|---------|----------|-------------
48 | | `size` | `number` | `20` | No | The size of the Spinner, which controls both width and height in pixels.
49 | | `color` | `string` | `theme.colors.accent.light` | No | The color of the bouncing dots. Can be any valid CSS color value.
50 |
--------------------------------------------------------------------------------
/.husky/scripts/validate-commit-message.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | separator="|"
4 | remoteGitmojis=$(curl -s https://raw.githubusercontent.com/carloscuesta/gitmoji/master/packages/gitmojis/src/gitmojis.json)
5 | gitmojis=$(node -e "console.log(JSON.parse(process.argv[1]).gitmojis.map(gitmoji => gitmoji.emoji + ' ' + gitmoji.code))" "$remoteGitmojis")
6 | gitmojis="$(echo "$gitmojis" | tr -d "'" | tr -d "\"" | tr -d "," | tr -d "[" | tr -d "]")"
7 | gitmojis=($gitmojis)
8 |
9 | emojiRegex="$( printf "${separator}%s" "${gitmojis[@]}" )"
10 | emojiRegex="${emojiRegex:${#separator}}"
11 |
12 | separator="|"
13 | types=(add fix improve update remove refactor rename move upgrade downgrade release merge)
14 | typesRegex="$( printf "${separator}%s" "${types[@]}" )"
15 | typesRegex="${typesRegex:${#separator}}"
16 |
17 | message="$(cat $1)"
18 |
19 | regex="^(${emojiRegex}) (${typesRegex}) (.*[a-z0-9(-)#@']{1,})$"
20 |
21 | echo "---------[ Commit validation ]--------"
22 | echo ""
23 | if [[ ! $message =~ $regex ]];
24 | then
25 | echo "Your commit message is not valid! Please check our contributing guidelines:"
26 | echo "https://docs.onruntime.com/contributing/commits"
27 | else
28 | echo "Your commit message is valid!"
29 | fi
30 | echo ""
31 | echo "Your commit message was:"
32 | echo $message
33 | echo ""
34 | echo "--------------------------------------"
35 | [[ $message =~ $regex ]] || exit 1
--------------------------------------------------------------------------------
/workshop/pages/badge.tsx:
--------------------------------------------------------------------------------
1 | import { Badge, Container } from "kitchn";
2 | import { NextPage } from "next";
3 | import React from "react";
4 |
5 | const BadgePage: NextPage = () => {
6 | return (
7 | <>
8 |
15 | {"default"}
16 |
17 | {"1"}
18 | {"5"}
19 | {"Badge"}
20 |
21 | {"size"}
22 |
23 | {"Small"}
24 | {"Normal"}
25 | {"Large"}
26 |
27 | {"variants"}
28 |
29 | {"Primary"}
30 | {"Secondary"}
31 | {"Info"}
32 | {"Success"}
33 | {"Warning"}
34 | {"Danger"}
35 |
36 |
37 | >
38 | );
39 | };
40 |
41 | export default BadgePage;
42 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on: [push]
3 | env:
4 | NODE_OPTIONS: "--max_old_space_size=4096"
5 | jobs:
6 | build:
7 | name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }}
8 |
9 | runs-on: ${{ matrix.os }}
10 | strategy:
11 | matrix:
12 | node: ["20.x"]
13 | os: [ubuntu-latest]
14 |
15 | steps:
16 | - name: Checkout repo
17 | uses: actions/checkout@v4
18 |
19 | - name: Extract pnpm version
20 | id: extract_pnpm_version
21 | run: echo "::set-output name=pnpm_version::$(jq -r '.packageManager' package.json | cut -d'@' -f2)"
22 |
23 | - name: Use pnpm
24 | uses: pnpm/action-setup@v4.1.0
25 | with:
26 | version: ${{ steps.extract_pnpm_version.outputs.pnpm_version }}
27 |
28 | - name: Use Node ${{ matrix.node }}
29 | uses: actions/setup-node@v4
30 | with:
31 | node-version: ${{ matrix.node }}
32 | cache: "pnpm"
33 |
34 | - name: Install Dependencies
35 | run: pnpm i
36 |
37 | - name: Lint
38 | run: pnpm lint
39 |
40 | - name: Build Packages
41 | run: pnpm build:packages
42 |
43 | - name: Build Workshop
44 | run: pnpm build:workshop
45 |
46 | - name: Build Docs
47 | run: pnpm build:docs
48 |
49 | - name: Build Examples
50 | run: pnpm build:examples
51 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 | on:
3 | push:
4 | branches:
5 | - master
6 | env:
7 | NODE_OPTIONS: "--max_old_space_size=4096"
8 | jobs:
9 | release:
10 | if: github.repository == 'tonightpass/kitchn'
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - name: Checkout repo
16 | uses: actions/checkout@v4
17 |
18 | - name: Extract pnpm version
19 | id: extract_pnpm_version
20 | run: echo "::set-output name=pnpm_version::$(jq -r '.packageManager' package.json | cut -d'@' -f2)"
21 |
22 | - name: Use pnpm
23 | uses: pnpm/action-setup@v4.1.0
24 | with:
25 | version: ${{ steps.extract_pnpm_version.outputs.pnpm_version }}
26 |
27 | - name: Use Node
28 | uses: actions/setup-node@v4
29 | with:
30 | cache: "pnpm"
31 |
32 | - name: Install Dependencies
33 | run: pnpm i
34 |
35 | - name: Lint
36 | run: pnpm lint
37 |
38 | - name: Build Packages
39 | run: pnpm build:packages
40 |
41 | - name: Create Release Pull Request or Publish to npm
42 | uses: changesets/action@v1
43 | with:
44 | publish: pnpm run release
45 | version: pnpm run version
46 | commit: "🔖 release version"
47 | title: "🔖 release version"
48 | env:
49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
50 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
51 |
--------------------------------------------------------------------------------
/docs/next-sitemap.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 | const { execSync } = require("child_process");
3 | const fs = require("fs");
4 | const packageJson = require("kitchn/package.json");
5 |
6 | /** @type {import('next-sitemap').IConfig} */
7 | module.exports = {
8 | siteUrl: packageJson.homepage,
9 | generateRobotsTxt: true,
10 | robotsTxtOptions: {
11 | policies: [{ userAgent: "*", allow: "/" }],
12 | },
13 | transform: async (config, path) => {
14 | let filePath = `pages${path}.md`;
15 | let exist = fs.existsSync(filePath);
16 |
17 | if (!exist) {
18 | filePath += "x";
19 | exist = fs.existsSync(filePath);
20 | }
21 |
22 | let lastmod = config.autoLastmod ? new Date().toISOString() : undefined;
23 |
24 | if (exist) {
25 | try {
26 | const cmd = `git log -1 --format=%ct ${filePath}`;
27 | const result = execSync(cmd).toString().trim();
28 |
29 | if (result) {
30 | lastmod = new Date(+result * 1000).toISOString();
31 | }
32 | } catch (error) {
33 | console.error(
34 | `Error executing git command for file ${filePath}:`,
35 | error.message,
36 | );
37 | }
38 | }
39 |
40 | return {
41 | loc: path,
42 | changefreq: config.changefreq,
43 | priority: config.priority,
44 | lastmod,
45 | alternateRefs: config.alternateRefs ?? [],
46 | };
47 | },
48 | };
49 |
--------------------------------------------------------------------------------
/packages/kitchn/src/contexts/toasts.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { Toast, ToastLayout } from "../hooks/use-toasts";
4 |
5 | export type UpdateToastsFunction = (fn: (toasts: Toast[]) => Toast[]) => void;
6 | export type UpdateToastsLayoutFunction = (
7 | fn: (layout: Required) => Required,
8 | ) => void;
9 | export type UpdateToastsIDFunction = (fn: () => string | null) => void;
10 |
11 | export interface ToastsContextParams {
12 | toasts: Toast[];
13 | updateToasts: UpdateToastsFunction;
14 | toastLayout: Required;
15 | updateToastLayout: UpdateToastsLayoutFunction;
16 | lastUpdateToastId: string | null;
17 | updateLastToastId: UpdateToastsIDFunction;
18 | }
19 |
20 | export const defaultToastLayout: Required = {
21 | padding: "12px 16px",
22 | margin: "8px 0",
23 | width: "420px",
24 | maxWidth: "90vw",
25 | maxHeight: "75px",
26 | placement: "bottomRight",
27 | };
28 |
29 | const defaultParams: ToastsContextParams = {
30 | toasts: [],
31 | toastLayout: defaultToastLayout,
32 | updateToastLayout: (t) => t,
33 | updateToasts: (t) => t,
34 | lastUpdateToastId: null,
35 | updateLastToastId: () => null,
36 | };
37 |
38 | export const ToastsContent: React.Context =
39 | React.createContext(defaultParams);
40 |
41 | export const useToastsContext = (): ToastsContextParams =>
42 | React.useContext(ToastsContent);
43 |
--------------------------------------------------------------------------------
/workshop/pages/snippet.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Snippet, Text } from "kitchn";
2 | import { NextPage } from "next";
3 | import React from "react";
4 |
5 | const SnippetPage: NextPage = () => {
6 | return (
7 | <>
8 |
9 | {"default"}
10 |
11 |
12 |
13 |
14 | {"type"}
15 |
16 |
21 |
22 |
23 | {"muli-line"}
24 |
25 |
32 |
33 |
34 | {"no prompt"}
35 |
36 |
37 |
38 |
39 | {"callback"}
40 |
41 | alert("copied")}
45 | />
46 |
47 |
48 | >
49 | );
50 | };
51 |
52 | export default SnippetPage;
53 |
--------------------------------------------------------------------------------
/docs/components/showcase/featured/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Container, Link, Text } from "kitchn";
2 | import Balancer from "react-wrap-balancer";
3 |
4 | const Featured: React.FC = () => {
5 | return (
6 |
42 | );
43 | };
44 |
45 | export default Featured;
46 |
--------------------------------------------------------------------------------
/docs/components/landing/examples/progress/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Container, Progress, useTheme } from "kitchn";
2 | import React from "react";
3 |
4 | const ProgressExample: React.FC = () => {
5 | const { theme } = useTheme();
6 | const [value, setValue] = React.useState(0);
7 |
8 | return (
9 |
10 |
27 |
28 |
31 |
34 |
37 |
38 |
39 | );
40 | };
41 |
42 | export default ProgressExample;
43 |
--------------------------------------------------------------------------------
/packages/kitchn/src/components/spacer/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | import { withDecorator } from "../../hoc/with-decorator";
5 | import { KitchnComponent } from "../../types";
6 | import { Gap } from "../../types/theme";
7 | import { isNumber } from "../../utils/is-number";
8 |
9 | type Props = {
10 | x?: number | string | keyof Gap;
11 | y?: number | string | keyof Gap;
12 | };
13 |
14 | export type SpacerProps = KitchnComponent;
15 |
16 | const SpacerComponent = styled(({ ...props }: SpacerProps) => {
17 | return ;
18 | })`
19 | display: block;
20 | width: 1px;
21 | height: 1px;
22 | min-width: 1px;
23 | min-height: 1px;
24 | margin-left: calc(
25 | ${(props) =>
26 | props.x
27 | ? props.theme.gap[props.x as keyof Gap] ||
28 | (isNumber(props.x)
29 | ? `${props.theme.gap.normal} * ${props.x}`
30 | : props.x)
31 | : "1px"} -
32 | 1px
33 | );
34 | margin-top: calc(
35 | ${(props) =>
36 | props.y
37 | ? props.theme.gap[props.y as keyof Gap] ||
38 | (isNumber(props.y)
39 | ? `${props.theme.gap.normal} * ${props.y}`
40 | : props.y)
41 | : "1px"} -
42 | 1px
43 | );
44 | `;
45 |
46 | SpacerComponent.displayName = "KitchnSpacer";
47 | export const Spacer = withDecorator(SpacerComponent);
48 | export default Spacer;
49 |
--------------------------------------------------------------------------------
/packages/kitchn/src/components/spinner/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | import { withDecorator } from "../../hoc/with-decorator";
5 | import { KitchnComponent } from "../../types";
6 | import { skBouncedelay } from "../../utils/animate";
7 |
8 | type Props = {
9 | color?: string;
10 | size?: number;
11 | };
12 |
13 | export type SpinnerProps = KitchnComponent;
14 |
15 | const SpinnerComponent = styled(({ ...props }: SpinnerProps) => {
16 | return (
17 |
22 | );
23 | })`
24 | display: flex;
25 | align-items: center;
26 | justify-content: center;
27 | width: ${({ size }) => size || 50}px;
28 | height: ${({ size }) => size || 50}px;
29 | text-align: center;
30 |
31 | div {
32 | width: 25%;
33 | height: 25%;
34 | background-color: ${({ color, theme }) =>
35 | color || theme.colors.accent.light};
36 |
37 | border-radius: 100%;
38 | display: inline-block;
39 | animation: ${skBouncedelay} 1.4s infinite ease-in-out both;
40 | }
41 |
42 | .bounce1 {
43 | animation-delay: -0.32s;
44 | }
45 |
46 | .bounce2 {
47 | animation-delay: -0.16s;
48 | }
49 | `;
50 |
51 | SpinnerComponent.displayName = "KitchnSpinner";
52 | export const Spinner = withDecorator(SpinnerComponent);
53 | export default Spinner;
54 |
--------------------------------------------------------------------------------
/packages/kitchn/src/components/combobox/dropdown/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { withDecorator } from "../../../hoc";
4 | import { useCombobox } from "../../../hooks";
5 | import { KitchnComponent } from "../../../types";
6 | import Container from "../../container";
7 | import Dropdown from "../../dropdown";
8 |
9 | type Props = {
10 | visible: boolean;
11 | disableMatchWidth?: boolean;
12 | };
13 |
14 | export type ComboboxDropdownProps = KitchnComponent;
15 |
16 | const ComboboxDropdownComponent: React.FC = ({
17 | visible,
18 | disableMatchWidth,
19 | children,
20 | }) => {
21 | const { ref } = useCombobox();
22 |
23 | const clickHandler = (event: React.MouseEvent) => {
24 | event.preventDefault();
25 | event.stopPropagation();
26 | event.nativeEvent.stopImmediatePropagation();
27 | };
28 |
29 | return (
30 |
35 |
44 | {children}
45 |
46 |
47 | );
48 | };
49 |
50 | ComboboxDropdownComponent.displayName = "KitchenComboboxDropdown";
51 | export const ComboboxDropdown = withDecorator(ComboboxDropdownComponent);
52 | export default ComboboxDropdown;
53 |
--------------------------------------------------------------------------------
/docs/pages/docs/acknowledgements.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Acknowledgements and thanks to contributors"
3 | description: "Thank you to all these developers, build ui kits, and open source projects that have inspired and helped us build Kitchn."
4 | ---
5 |
6 | # Acknowledgements
7 |
8 | Kitchn was originally created by [Antoine Kingue](https://twitter.com/antoinekingue) as a closed-source enterprise software offering. In late 2022, [Tonight Pass](https://tonightpass.com) started working on Kitchn and open sourced the codebase.
9 |
10 | Today, Kitchn has dedicated full-time team working on it as well as a growing list of [open source contributors](https://github.com/tonightpass/kitchn/graphs/contributors).
11 |
12 | ## Prior Art and Inspiration
13 |
14 | At [Tonight Pass](https://tonightpass.com), we believe in the power of open source. We are grateful to the many open source projects that have inspired and helped us build Kitchn.
15 |
16 | We'd like to make a special shoutout to other ui kits and libraries that have inspired us:
17 |
18 | - [Vercel Design](https://vercel.com/design/introduction)
19 | - [Geist UI](https://geist-ui.dev)
20 | - [Chakra UI](https://chakra-ui.com)
21 | - [shadcn/ui](https://ui.shadcn.com)
22 | - [Tailwind CSS](https://tailwindcss.com)
23 | - [Turborepo](https://turbo.build/repo)
24 | - [next-themes](https://github.com/pacocoursey/next-themes)
25 | - [usehooks-ts](https://usehooks-ts.com)
26 |
27 | ## Additional Thanks
28 |
29 | Additionally, we're grateful to:
30 |
31 | - [Vercel](https://vercel.com) for providing us with a free open source plan
32 |
--------------------------------------------------------------------------------
/docs/pages/docs/hooks/use-clickanywhere.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Handling global clicks in React components"
3 | description: "A custom hook that triggers a callback whenever a click occurs anywhere on the document."
4 | ---
5 |
6 | import Playground from "@/components/playground";
7 |
8 | # useClickAnyWhere
9 |
10 | The `useClickAnyWhere` hook allows you to execute a function whenever a click event occurs anywhere in the document. This can be useful for implementing features like closing a modal or dropdown when the user clicks outside of it.
11 |
12 | ## Usage
13 |
14 | First, you need to import the `useClickAnyWhere` hook from the `kitchn` package.
15 |
16 | ```jsx
17 | import { useClickAnyWhere } from "kitchn";
18 | ```
19 |
20 | ## Example
21 |
22 | Here is an example of how to use the `useClickAnyWhere` hook in a component:
23 |
24 | {
27 | const handleClick = (event) => {
28 | console.log("Clicked somewhere on the document", event);
29 | };
30 |
31 | useClickAnyWhere(handleClick);
32 |
33 | return (
34 | Click anywhere on the document and check the console.
35 | );
36 | };
37 | `}
38 | />
39 |
40 | ## Parameters
41 |
42 | The `useClickAnyWhere` hook accepts the following parameter:
43 |
44 | | Name | Type | Description
45 | |-----------|--------------------------|-------------------------------------------------------
46 | | `handler` | `(event: Event) => void` | A function to be called whenever a click event occurs.
47 |
48 | ## Return Value
49 |
50 | The `useClickAnyWhere` hook does not return any value.
51 |
--------------------------------------------------------------------------------
/docs/pages/docs/hooks/use-warning.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Logging warnings with traceability in React"
3 | description: "A custom hook for logging warnings to the console, with an optional component tag for better traceability."
4 | ---
5 |
6 | import Playground from "@/components/playground";
7 |
8 | # useWarning
9 |
10 | The `useWarning` hook provides a way to log warnings to the console in a consistent and controlled manner. This hook is particularly useful for development purposes, allowing you to flag potential issues in your code with clear messages that can optionally be associated with a specific component.
11 |
12 | ## Usage
13 |
14 | First, you need to import the `useWarning` hook from the `kitchn` package.
15 |
16 | ```jsx
17 | import { useWarning } from "kitchn";
18 | ```
19 |
20 | ## Example
21 |
22 | Here is an example of how to use the `useWarning` hook in a component:
23 |
24 | {
27 | useWarning("This is a warning message", "ExampleComponent");
28 |
29 | return (
30 | Check the console for a warning message.
31 | );
32 | };
33 | `}
34 | />
35 |
36 | ## Parameters
37 |
38 | The `useWarning` hook accepts the following parameters:
39 |
40 | | Name | Type | Description
41 | |-------------|------------|---------------------------------------------------
42 | | `isBrowser` | `boolean` | `true` if the code is running in the browser; `false` otherwise.
43 | | `isServer ` | `boolean` | `true` if the code is running on the server; `false` otherwise.
44 |
45 | ## Return Value
46 |
47 | The `useWarning` hook does not return any value.
48 |
--------------------------------------------------------------------------------
/packages/kitchn/src/hooks/use-clipboard.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { usePortal } from "./use-portal";
4 | import { useWarning } from "./use-warning";
5 |
6 | export type UseClipboardOptions = {
7 | onError: () => unknown;
8 | };
9 |
10 | export type UseClipboardResult = {
11 | copy: (text: string) => void;
12 | };
13 |
14 | const defaultOptions: UseClipboardOptions = {
15 | // eslint-disable-next-line react-hooks/rules-of-hooks
16 | onError: () => useWarning("Failed to copy.", "useClipboard"),
17 | };
18 |
19 | export const useClipboard = (
20 | options: UseClipboardOptions = defaultOptions,
21 | ): UseClipboardResult => {
22 | const el = usePortal("clipboard");
23 |
24 | const copyText = (el: HTMLElement | null, text: string) => {
25 | if (!el || !text) return;
26 | const selection = window.getSelection();
27 | if (!selection) return;
28 |
29 | el.style.whiteSpace = "pre";
30 | el.textContent = text;
31 |
32 | const range = window.document.createRange();
33 | selection.removeAllRanges();
34 | range.selectNode(el);
35 | selection.addRange(range);
36 | try {
37 | window.document.execCommand("copy");
38 | } catch (e) {
39 | options.onError && options.onError();
40 | }
41 |
42 | selection.removeAllRanges();
43 | if (el) {
44 | el.textContent = "";
45 | }
46 | };
47 |
48 | const copy = React.useCallback(
49 | (text: string) => {
50 | copyText(el, text);
51 | },
52 | // eslint-disable-next-line react-hooks/exhaustive-deps
53 | [el],
54 | );
55 |
56 | return { copy };
57 | };
58 |
--------------------------------------------------------------------------------
/packages/kitchn/src/native/contexts/theme.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useColorScheme } from "react-native";
3 | import {
4 | DefaultTheme,
5 | ThemeProvider as StyledThemeProvider,
6 | } from "styled-components/native";
7 |
8 | import { defaultThemes } from "../themes";
9 |
10 | const ThemeContext = React.createContext({
11 | theme: defaultThemes.dark,
12 | // eslint-disable-next-line @typescript-eslint/no-empty-function
13 | setTheme: (_theme: DefaultTheme) => {},
14 | });
15 |
16 | type ThemeProviderProps = {
17 | children: React.ReactNode;
18 | theme?: DefaultTheme;
19 | };
20 |
21 | /**
22 | * System: 0
23 | * Dark: 1
24 | * Light: 2
25 | */
26 |
27 | const ThemeProvider = ({
28 | children,
29 | theme: customTheme,
30 | ...props
31 | }: ThemeProviderProps) => {
32 | const colorScheme = useColorScheme();
33 |
34 | const isDarkTheme = colorScheme === "dark";
35 |
36 | const [theme, setTheme] = React.useState(
37 | customTheme || (isDarkTheme ? defaultThemes.dark : defaultThemes.light),
38 | );
39 |
40 | React.useEffect(() => {
41 | if (customTheme) {
42 | setTheme(customTheme);
43 | } else if (!isDarkTheme) {
44 | setTheme(defaultThemes.light);
45 | } else {
46 | setTheme(defaultThemes.dark);
47 | }
48 | }, [customTheme, isDarkTheme]);
49 |
50 | return (
51 |
52 | {children}
53 |
54 | );
55 | };
56 |
57 | export { ThemeContext, ThemeProvider };
58 |
--------------------------------------------------------------------------------
/docs/components/playground/dynamic-live-server.tsx:
--------------------------------------------------------------------------------
1 | import { useTheme } from "kitchn";
2 | import React from "react";
3 |
4 | import EditorServer from "./editor-server";
5 |
6 | export interface Props {
7 | code: string;
8 | scope: {
9 | [key: string]: unknown;
10 | };
11 | }
12 |
13 | const DynamicLiveServer: React.FC = ({ code }) => {
14 | const { theme } = useTheme();
15 |
16 | return (
17 | <>
18 |
24 |
25 |
51 | >
52 | );
53 | };
54 |
55 | export default DynamicLiveServer;
56 |
--------------------------------------------------------------------------------
/packages/kitchn/src/native/components/icon/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import RemixIcon from "react-native-remix-icon";
3 |
4 | import { withScale } from "../../hoc";
5 | import { useTheme } from "../../hooks";
6 | import { AccentColors, KitchnComponent, Size, TextColors } from "../../types";
7 |
8 | type Props = {
9 | name: string;
10 | size?: number | string | keyof Size;
11 | fill?: boolean;
12 | /**
13 | * The text color. Strictly limited to colors of our design system. If you want to pass accent color make sure to pass `accent` instead of `color`.
14 | */
15 | color?: keyof TextColors | string;
16 | /**
17 | * The accent color. Strictly limited to colors of our design system, but can be used in combination with `color` prop.
18 | */
19 | accent?: keyof AccentColors;
20 | };
21 |
22 | export type IconProps = KitchnComponent;
23 |
24 | const IconComponent = ({
25 | name,
26 | size = "normal",
27 | fill,
28 | color,
29 | accent,
30 | ...props
31 | }: IconProps) => {
32 | const { theme } = useTheme();
33 | return (
34 |
45 | );
46 | };
47 |
48 | IconComponent.displayName = "KitchnIcon";
49 | export const Icon = withScale(IconComponent);
50 | export default Icon;
51 |
--------------------------------------------------------------------------------
/workshop/pages/logos.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Text } from "kitchn";
2 | import {
3 | KitchnMark,
4 | KitchnWordmark,
5 | TonightPassMark,
6 | TonightPassWordmark,
7 | } from "kitchn/logos";
8 | import { NextPage } from "next";
9 |
10 | const LogosPage: NextPage = () => {
11 | return (
12 |
13 |
14 | {"tonightpass mark"}
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | {"tonightpass wordmark"}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | {"kitchn mark"}
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | {"kitchn wordmark"}
39 |
40 |
41 |
42 |
43 |
44 |
45 | );
46 | };
47 |
48 | export default LogosPage;
49 |
--------------------------------------------------------------------------------
/workshop/pages/toggle.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Text, Toggle, useCheckbox } from "kitchn";
2 | import { NextPage } from "next";
3 | import React from "react";
4 |
5 | const TogglePage: NextPage = () => {
6 | const [checked1, toggle1] = useCheckbox();
7 | const [checked2, toggle2] = useCheckbox(true);
8 | const [checked3, toggle3] = useCheckbox();
9 | const [checked4, toggle4] = useCheckbox();
10 | return (
11 | <>
12 |
19 | {"default"}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | {"disabled"}
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | {"sizes"}
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | >
51 | );
52 | };
53 |
54 | export default TogglePage;
55 |
--------------------------------------------------------------------------------
/packages/kitchn/src/hoc/with-decorator/position.ts:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from "react";
2 | import { css } from "styled-components";
3 |
4 | import { handleValue } from "./decorator";
5 | import { Breakpoint, Gap } from "../../types/theme";
6 |
7 | export type PositionProps = {
8 | position?: CSSProperties["position"];
9 | pos?: PositionProps["position"];
10 | zIndex?: CSSProperties["zIndex"];
11 | zi?: PositionProps["zIndex"];
12 | left?: CSSProperties["left"] | keyof Gap | keyof Breakpoint;
13 | right?: CSSProperties["right"] | keyof Gap | keyof Breakpoint;
14 | top?: CSSProperties["top"] | keyof Gap | keyof Breakpoint;
15 | bottom?: CSSProperties["bottom"] | keyof Gap | keyof Breakpoint;
16 | l?: PositionProps["left"];
17 | r?: PositionProps["right"];
18 | t?: PositionProps["top"];
19 | b?: PositionProps["bottom"];
20 | };
21 |
22 | export const positionCss = css`
23 | ${({ position, pos }) =>
24 | (position ?? pos) !== undefined ? `position: ${position ?? pos};` : ""}
25 | ${({ zIndex, zi }) =>
26 | (zIndex ?? zi) !== undefined ? `z-index: ${zIndex ?? zi};` : ""}
27 | ${({ theme, left, l }) =>
28 | (left ?? l) !== undefined ? `left: ${handleValue(theme, left ?? l)};` : ""}
29 | ${({ theme, right, r }) =>
30 | (right ?? r) !== undefined
31 | ? `right: ${handleValue(theme, right ?? r)};`
32 | : ""}
33 | ${({ theme, top, t }) =>
34 | (top ?? t) !== undefined ? `top: ${handleValue(theme, top ?? t)};` : ""}
35 | ${({ theme, bottom, b }) =>
36 | (bottom ?? b) !== undefined
37 | ? `bottom: ${handleValue(theme, bottom ?? b)};`
38 | : ""}
39 | `;
40 |
--------------------------------------------------------------------------------
/docs/components/playground/dynamic-live-client.tsx:
--------------------------------------------------------------------------------
1 | import { useTheme } from "kitchn";
2 | import React from "react";
3 | import { LivePreview, LiveProvider, LiveError } from "react-live";
4 |
5 | import makeCodeTheme from "./code-theme";
6 | import EditorClient from "./editor-client";
7 | import kitchnScope from "./scope";
8 |
9 | export interface Props {
10 | code: string;
11 | scope: {
12 | [key: string]: unknown;
13 | };
14 | }
15 |
16 | const DynamicLiveClient: React.FC = ({ code, scope }) => {
17 | const { theme } = useTheme();
18 | const codeTheme = makeCodeTheme(theme);
19 |
20 | return (
21 |
26 |
27 |
28 |
29 |
30 |
31 |
52 |
53 | );
54 | };
55 |
56 | export default DynamicLiveClient;
57 |
--------------------------------------------------------------------------------
/workshop/pages/avatar.tsx:
--------------------------------------------------------------------------------
1 | import { Avatar, Container, AvatarGroup } from "kitchn";
2 | import { NextPage } from "next";
3 |
4 | const AvatarPage: NextPage = () => {
5 | const url = "https://picsum.photos/200/300";
6 |
7 | return (
8 |
15 | {"default"}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | {"text"}
29 |
30 |
31 |
32 |
33 |
34 |
35 | {"group"}
36 |
44 |
45 |
54 |
55 |
56 | );
57 | };
58 |
59 | export default AvatarPage;
60 |
--------------------------------------------------------------------------------
/docs/components/showcase/projects/promoted-projects/card/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Container,
3 | ContainerProps,
4 | Icon,
5 | Image,
6 | KitchnComponent,
7 | Link,
8 | ScaleProps,
9 | Text,
10 | } from "kitchn";
11 | import { RiExternalLinkFill, RiGithubFill } from "react-icons/ri";
12 |
13 | import { SortedProject } from "@/types/showcase";
14 |
15 | export type CardProps = KitchnComponent<
16 | {
17 | project: SortedProject;
18 | },
19 | ContainerProps & ScaleProps
20 | >;
21 |
22 | const Card: React.FC = ({ project, ...props }: CardProps) => {
23 | return (
24 |
33 |
42 |
43 |
44 | {project.title}
45 |
46 |
47 | {project.repo && (
48 |
49 |
50 |
51 | )}
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | );
60 | };
61 |
62 | export default Card;
63 |
--------------------------------------------------------------------------------
/docs/pages/docs/hooks/use-ssr.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Detecting server vs. browser environments in React"
3 | description: "A custom hook that helps determine whether your code is running on the server or the browser, providing flags for both environments."
4 | ---
5 |
6 | import Playground from "@/components/playground";
7 |
8 | # useSSR
9 |
10 | The `useSSR` hook allows you to identify whether your code is running in a server-side or client-side environment. This is particularly useful for handling logic that should only execute in the browser, such as DOM manipulations or accessing browser-specific APIs.
11 |
12 | ## Usage
13 |
14 | First, you need to import the `useSSR` hook from the `kitchn` package.
15 |
16 | ```jsx
17 | import { useSSR } from "kitchn";
18 | ```
19 |
20 | ## Example
21 |
22 | Here is an example of how to use the `useSSR` hook in a component:
23 |
24 | {
27 | const { isBrowser, isServer } = useSSR();
28 |
29 | return (
30 |
31 | Is this running in the browser? {isBrowser ? "Yes" : "No"}
32 | Is this running on the server? {isServer ? "Yes" : "No"}
33 |
34 | );
35 | };
36 | `}
37 | />
38 |
39 | ## Parameters
40 |
41 | The `useSSR` hook does not accept any parameters.
42 |
43 | ## Return Value
44 |
45 | The `useSSR` hook returns an object with the following properties:
46 |
47 | | Name | Type | Description
48 | |-------------|------------|---------------------------------------------------
49 | | `isBrowser` | `boolean` | `true` if the code is running in the browser; `false` otherwise.
50 | | `isServer ` | `boolean` | `true` if the code is running on the server; `false` otherwise.
51 |
52 |
--------------------------------------------------------------------------------
/packages/kitchn/src/components/toast/actions/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | import { withDecorator } from "../../../hoc/with-decorator";
5 | import { ToastAction } from "../../../hooks/use-toasts";
6 | import { KitchnComponent } from "../../../types";
7 | import { capitalize } from "../../../utils/capitalize";
8 | import Button from "../../button";
9 | import Container from "../../container";
10 |
11 | type Props = {
12 | actions: ToastAction[];
13 | cancelHandle: () => void;
14 | };
15 |
16 | export type ToastActionsProps = KitchnComponent;
17 |
18 | const ToastActionsComponent = styled(
19 | ({ actions, cancelHandle, ...props }: ToastActionsProps) => {
20 | const handler = (
21 | event: React.MouseEvent,
22 | userHandler: ToastAction["handler"],
23 | ) => {
24 | userHandler && userHandler(event, cancelHandle);
25 | };
26 |
27 | return (
28 |
29 |
30 | {actions.map((action, i) => (
31 |
41 | ))}
42 |
43 |
44 | );
45 | },
46 | )``;
47 |
48 | ToastActionsComponent.displayName = "KitchnToastActions";
49 | export const ToastActions = withDecorator(ToastActionsComponent);
50 | export default ToastActions;
51 |
--------------------------------------------------------------------------------
/docs/components/showcase/projects/promoted-projects/index.tsx:
--------------------------------------------------------------------------------
1 | import { Container, useBreakpoint } from "kitchn";
2 |
3 | import Card from "./card";
4 |
5 | import { SortedProject } from "@/types/showcase";
6 |
7 | export type PromotedProjectsProps = {
8 | projects: SortedProject[];
9 | };
10 |
11 | const PromotedProjects: React.FC = ({
12 | projects,
13 | }: PromotedProjectsProps) => {
14 | const { isTablet } = useBreakpoint();
15 | return (
16 |
24 |
25 | {!!projects[0] && }
26 |
27 |
28 | {projects.length > 1 && (
29 |
30 | {!!projects[1] && (
31 |
32 | )}
33 | {!!projects[2] && (
34 |
35 | )}
36 |
37 | )}
38 | {projects.length > 3 && (
39 |
40 | {!!projects[3] && (
41 |
42 | )}
43 | {!!projects[4] && (
44 |
45 | )}
46 |
47 | )}
48 |
49 |
50 | );
51 | };
52 |
53 | export default PromotedProjects;
54 |
--------------------------------------------------------------------------------
/workshop/pages/textarea.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Text, Textarea } from "kitchn";
2 | import { NextPage } from "next";
3 | import React from "react";
4 |
5 | const TextareaPage: NextPage = () => {
6 | return (
7 | <>
8 |
15 | {"default"}
16 |
17 |
22 |
23 |
24 | {"disabled"}
25 |
26 |
32 |
33 |
34 | {"error"}
35 |
36 |
42 |
43 |
44 | >
45 | );
46 | };
47 |
48 | export default TextareaPage;
49 |
--------------------------------------------------------------------------------
/docs/components/landing/examples/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Container, Link, Tabs } from "kitchn";
2 | import React from "react";
3 |
4 | import AuthenticationExample from "./authentication";
5 | import ProgressExample from "./progress";
6 | import ShopItemExample from "./shop-item";
7 | const Examples: React.FC = () => {
8 | const tabs = [
9 | {
10 | title: "Authentication",
11 | value: "authentication",
12 | file: "Authentication",
13 | },
14 | { title: "Shop Item", value: "shop-item", file: "ShopItem" },
15 | { title: "Progress", value: "progress", file: "Progress" },
16 | ];
17 | const [selected, setSelected] = React.useState("authentication");
18 |
19 | return (
20 |
21 | {
23 | return { title: tab.title, value: tab.value };
24 | })}
25 | selected={selected}
26 | setSelected={setSelected}
27 | />
28 |
36 |
37 | tab.value === selected)?.file
40 | }/index.tsx`}
41 | >
42 |
43 |
44 |
45 | {selected === "authentication" && }
46 | {selected === "progress" && }
47 | {selected === "shop-item" && }
48 |
49 |
50 | );
51 | };
52 |
53 | export default Examples;
54 |
--------------------------------------------------------------------------------
/docs/pages/docs/hooks/use-mediaquery.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Media queries in React for responsive design"
3 | description: "A custom React hook that allows you to monitor and respond to media query changes, enabling responsive design in your components."
4 | ---
5 |
6 | import Playground from "@/components/playground";
7 |
8 | # useMediaQuery
9 |
10 | The `useMediaQuery` hook provides an easy way to track and respond to media query changes in a React component. This is particularly useful for implementing responsive design where you need to conditionally render or style components based on the viewport or device characteristics.
11 |
12 | ## Usage
13 |
14 | First, you need to import the `useMediaQuery` hook from the `kitchn` package.
15 |
16 | ```jsx
17 | import { useMediaQuery } from "kitchn";
18 | ```
19 |
20 | ## Example
21 |
22 | Here is an example of how to use the `useMediaQuery` hook in a component:
23 |
24 | {
27 | const isDesktop = useMediaQuery("(min-width: 1224px)");
28 |
29 | return (
30 |
31 | {isDesktop ? (
32 | You are on a desktop or laptop screen.
33 | ) : (
34 | You are on a mobile or tablet screen.
35 | )}
36 |
37 | );
38 | };
39 | `}
40 | />
41 |
42 | ## Parameters
43 |
44 | The `useMediaQuery` hook accepts the following parameter:
45 |
46 | | Name | Type | Description
47 | |-----------|----------|-----------------------------------
48 | | `query` | `string` | The media query string to match against. This should be a valid CSS media query.
49 |
50 | ## Return Value
51 |
52 | The `useMediaQuery` hook returns a boolean value:
53 |
54 | | Type | Description
55 | |----------|-----------------------------------
56 | | `boolean`| `true` if the media query matches the current viewport, `false` otherwise.
57 |
--------------------------------------------------------------------------------
/packages/kitchn/src/hoc/with-decorator/margin.ts:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from "react";
2 | import { css } from "styled-components";
3 |
4 | import { handleValue } from "./decorator";
5 | import { Gap } from "../../types/theme";
6 |
7 | export type MarginProps = {
8 | margin?: CSSProperties["margin"] | keyof Gap;
9 | m?: MarginProps["margin"];
10 | marginLeft?: CSSProperties["marginLeft"] | keyof Gap;
11 | marginRight?: CSSProperties["marginRight"] | keyof Gap;
12 | marginTop?: CSSProperties["marginTop"] | keyof Gap;
13 | marginBottom?: CSSProperties["marginBottom"] | keyof Gap;
14 | ml?: MarginProps["marginLeft"];
15 | mr?: MarginProps["marginRight"];
16 | mt?: MarginProps["marginTop"];
17 | mb?: MarginProps["marginBottom"];
18 | mx?: MarginProps["margin"];
19 | my?: MarginProps["margin"];
20 | };
21 |
22 | export const marginCss = css`
23 | ${({ theme, margin, m }) =>
24 | margin !== undefined || m !== undefined
25 | ? `margin: ${handleValue(theme, margin || m)};`
26 | : ""}
27 | ${({ theme, marginLeft, ml, mx }) =>
28 | marginLeft !== undefined || ml !== undefined || mx !== undefined
29 | ? `margin-left: ${handleValue(theme, marginLeft || ml || mx)};`
30 | : ""}
31 | ${({ theme, marginRight, mr, mx }) =>
32 | marginRight !== undefined || mr !== undefined || mx !== undefined
33 | ? `margin-right: ${handleValue(theme, marginRight || mr || mx)};`
34 | : ""}
35 | ${({ theme, marginTop, mt, my }) =>
36 | marginTop !== undefined || mt !== undefined || my !== undefined
37 | ? `margin-top: ${handleValue(theme, marginTop || mt || my)};`
38 | : ""}
39 | ${({ theme, marginBottom, mb, my }) =>
40 | marginBottom !== undefined || mb !== undefined || my !== undefined
41 | ? `margin-bottom: ${handleValue(theme, marginBottom || mb || my)};`
42 | : ""}
43 | `;
44 |
--------------------------------------------------------------------------------
/packages/kitchn/src/components/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./provider";
2 | export * from "./avatar";
3 | export * from "./avatar/group";
4 | export * from "./badge";
5 | export * from "./button";
6 | export * from "./calendar";
7 | export * from "./checkbox";
8 | export * from "./code";
9 | export * from "./code/inline";
10 | export * from "./collapse";
11 | export * from "./combobox";
12 | export * from "./combobox/dropdown";
13 | export * from "./combobox/empty";
14 | export * from "./combobox/item";
15 | export * from "./combobox/searching";
16 | export * from "./container";
17 | export * from "./drawer";
18 | export * from "./dropdown";
19 | export * from "./entity";
20 | export * from "./entity/field";
21 | export * from "./error";
22 | export * from "./fieldset";
23 | export * from "./footer";
24 | export * from "./global-style";
25 | export * from "./highlight";
26 | export * from "./icon";
27 | export * from "./image";
28 | export * from "./input";
29 | export * from "./input/controlled";
30 | export * from "./input/password";
31 | export * from "./list/item";
32 | export * from "./list/ordered";
33 | export * from "./list/unordered";
34 | export * from "./link";
35 | export * from "./link/fragment";
36 | export * from "./menu";
37 | export * from "./modal";
38 | export * from "./navigation-menu";
39 | export * from "./note";
40 | export * from "./progress";
41 | export * from "./scroller";
42 | export * from "./select";
43 | export * from "./skeleton";
44 | export * from "./snippet";
45 | export * from "./spacer";
46 | export * from "./spinner";
47 | export * from "./switch";
48 | export * from "./tabs";
49 | export * from "./text";
50 | export * from "./textarea";
51 | export * from "./toast/actions";
52 | export * from "./toast/item";
53 | export * from "./toast/container";
54 | export * from "./toggle";
55 | export * from "./tooltip";
56 | export * from "./tooltip/content";
57 |
--------------------------------------------------------------------------------
/docs/pages/docs/hooks/use-clickaway.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Detecting outside clicks in React components"
3 | description: "A custom hook that triggers a callback when a click occurs outside a specified element."
4 | ---
5 |
6 | import Playground from "@/components/playground";
7 |
8 | # useClickAway
9 |
10 | The `useClickAway` hook allows you to execute a function when a click occurs outside a specified element. This is useful for scenarios such as closing a dropdown, modal, or tooltip when the user clicks outside of it.
11 |
12 | ## Usage
13 |
14 | First, you need to import the `useClickAway` hook from the `kitchn` package.
15 |
16 | ```jsx
17 | import { useClickAway } from "kitchn";
18 | ```
19 |
20 | ## Example
21 |
22 | Here is an example of how to use the `useClickAway` hook in a component:
23 |
24 | {
27 | const ref = React.useRef(null);
28 |
29 | const handleClickAway = (event) => {
30 | console.log("Clicked outside of the element", event);
31 | };
32 |
33 | useClickAway(ref, handleClickAway);
34 |
35 | return (
36 |
37 | Click outside this box to trigger the event.
38 |
39 | );
40 | };
41 | `}
42 | />
43 |
44 | ## Parameters
45 |
46 | The `useClickAway` hook accepts the following parameters:
47 |
48 | | Name | Type | Description
49 | |-----------|---------------------------------------|-------------------------------------------------------
50 | | `ref` | `React.MutableRefObject` | A `React.MutableRefObject` that should be attached to the element you want to monitor for outside clicks.
51 | | `handler` | `(event: Event) => void` | A function to be called whenever a click event occurs.
52 |
53 | ## Return Value
54 |
55 | The `useClickAway` hook does not return any value.
56 |
--------------------------------------------------------------------------------
/packages/kitchn/src/hoc/with-decorator/padding.ts:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from "react";
2 | import { css } from "styled-components";
3 |
4 | import { handleValue } from "./decorator";
5 | import { Gap } from "../../types/theme";
6 |
7 | export type PaddingProps = {
8 | padding?: CSSProperties["padding"] | keyof Gap;
9 | p?: PaddingProps["padding"];
10 | paddingLeft?: CSSProperties["paddingLeft"] | keyof Gap;
11 | paddingRight?: CSSProperties["paddingRight"] | keyof Gap;
12 | paddingTop?: CSSProperties["paddingTop"] | keyof Gap;
13 | paddingBottom?: CSSProperties["paddingBottom"] | keyof Gap;
14 | pl?: PaddingProps["paddingLeft"];
15 | pr?: PaddingProps["paddingRight"];
16 | pt?: PaddingProps["paddingTop"];
17 | pb?: PaddingProps["paddingBottom"];
18 | px?: PaddingProps["padding"];
19 | py?: PaddingProps["padding"];
20 | };
21 |
22 | export const paddingCss = css`
23 | ${({ theme, padding, p }) =>
24 | padding !== undefined || p !== undefined
25 | ? `padding: ${handleValue(theme, padding || p)};`
26 | : ""}
27 | ${({ theme, paddingLeft, pl, px }) =>
28 | paddingLeft !== undefined || pl !== undefined || px !== undefined
29 | ? `padding-left: ${handleValue(theme, paddingLeft || pl || px)};`
30 | : ""}
31 | ${({ theme, paddingRight, pr, px }) =>
32 | paddingRight !== undefined || pr !== undefined || px !== undefined
33 | ? `padding-right: ${handleValue(theme, paddingRight || pr || px)};`
34 | : ""}
35 | ${({ theme, paddingTop, pt, py }) =>
36 | paddingTop !== undefined || pt !== undefined || py !== undefined
37 | ? `padding-top: ${handleValue(theme, paddingTop || pt || py)};`
38 | : ""}
39 | ${({ theme, paddingBottom, pb, py }) =>
40 | paddingBottom !== undefined || pb !== undefined || py !== undefined
41 | ? `padding-bottom: ${handleValue(theme, paddingBottom || pb || py)};`
42 | : ""}
43 | `;
44 |
--------------------------------------------------------------------------------
/docs/pages/docs/hooks/use-checkbox.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Managing checkbox state in React"
3 | description: "A custom hook to manage the state of a checkbox, providing a boolean value and a toggle function."
4 | ---
5 |
6 | import Playground from "@/components/playground";
7 |
8 | # useCheckbox
9 |
10 | The `useCheckbox` hook simplifies the management of checkbox state by providing a boolean value and a toggle function. It can be used for any scenario where a binary toggle is required, such as checkboxes, switches, or other on/off controls.
11 |
12 | ## Usage
13 |
14 | First, you need to import the `useCheckbox` hook from the `kitchn` package.
15 |
16 | ```jsx
17 | import { useCheckbox } from "kitchn";
18 | ```
19 |
20 | ## Example
21 |
22 | Here is an example of how to use the useCheckbox hook in a component:
23 |
24 | {
27 | const [checked, toggle] = useCheckbox();
28 |
29 | return (
30 |
31 | Checkbox is {checked ? "checked" : "unchecked"}
32 |
33 | );
34 | };
35 | `}
36 | />
37 |
38 | ## Parameters
39 |
40 | The `useCheckbox` hook accepts the following parameter:
41 |
42 | | Name | Type | Default | Description
43 | |----------------|--------------------|---------|----------------------------------------------------------------
44 | | `defaultValue` | `UseCheckboxValue` | `false `| The initial state of the checkbox. Should be `true` or `false`.
45 |
46 | ## Return Value
47 |
48 | The `useCheckbox` hook returns a tuple with the following elements:
49 |
50 | | Name | Type | Description
51 | |-----------|--------------|-----------------------------------------------------------------------
52 | | `checked` | `boolean` | The current state of the checkbox (either true or false).
53 | | `toggle` | `() => void` | A function to toggle the state of the checkbox between true and false.
54 |
--------------------------------------------------------------------------------
/packages/kitchn/src/components/navigation-menu/item/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | import { NavigationMenuItemContext } from "../../../contexts/navigation-menu-item";
5 | import useNavigationMenu from "../../../hooks/use-navigation-menu";
6 | import { getId } from "../../../utils";
7 | import NavigationMenuContent from "../content";
8 |
9 | type NavigationMenuItemProps = {
10 | children: React.ReactNode;
11 | } & React.HTMLAttributes;
12 |
13 | const NavigationMenuItem = styled.li`
14 | padding: 0;
15 | margin: 0;
16 | display: flex;
17 | align-items: center;
18 | position: relative;
19 | `;
20 |
21 | const NavigationMenuItemWithContext = ({
22 | children,
23 | ...props
24 | }: NavigationMenuItemProps) => {
25 | const { menuItems, setMenuItems } = useNavigationMenu();
26 | const [itemId] = React.useState(getId());
27 |
28 | const hasContent = React.Children.toArray(children).some(
29 | (child) =>
30 | React.isValidElement(child) && child.type === NavigationMenuContent,
31 | );
32 |
33 | React.useEffect(() => {
34 | if (menuItems.includes(itemId)) return;
35 | setMenuItems((prev) => {
36 | const newItems = [...prev];
37 | if (!newItems.includes(itemId)) {
38 | newItems.push(itemId);
39 | }
40 | return newItems;
41 | });
42 |
43 | return () => {
44 | setMenuItems((prev) => prev.filter((id) => id !== itemId));
45 | };
46 | }, []);
47 |
48 | return (
49 |
56 |
57 | {children}
58 |
59 |
60 | );
61 | };
62 |
63 | export default NavigationMenuItemWithContext;
64 |
--------------------------------------------------------------------------------
/packages/kitchn/src/components/input/password/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef } from "react";
2 | import { RiEyeCloseLine, RiEyeLine } from "react-icons/ri";
3 | import styled from "styled-components";
4 |
5 | import Input, { InputProps } from "..";
6 | import { withDecorator } from "../../../hoc";
7 | import { KitchnComponent } from "../../../types";
8 |
9 | type Props = {
10 | hideToggle?: boolean;
11 | };
12 |
13 | export type InpuPasswordProps = KitchnComponent;
14 |
15 | const ForwardedInputPassword = forwardRef(
16 | (
17 | { hideToggle = false, ...props }: InpuPasswordProps,
18 | ref: React.Ref,
19 | ) => {
20 | const inputRef = React.useRef(null);
21 | const [visible, setVisible] = React.useState(false);
22 | React.useImperativeHandle(ref, () => inputRef.current);
23 |
24 | const iconClickHandler = () => {
25 | setVisible((v) => !v);
26 | if (inputRef && inputRef.current) {
27 | inputRef.current.focus();
28 | }
29 | };
30 |
31 | const inputProps = React.useMemo(
32 | () => ({
33 | ...props,
34 | ref: inputRef,
35 | clickableIcon: true,
36 | onIconClick: iconClickHandler,
37 | htmlType: visible ? "text" : "password",
38 | }),
39 | [props, iconClickHandler, visible, inputRef],
40 | );
41 |
42 | const icon = React.useMemo(() => {
43 | if (hideToggle) return null;
44 | return visible ? RiEyeLine : RiEyeCloseLine;
45 | }, [hideToggle, visible]);
46 |
47 | return ;
48 | },
49 | );
50 |
51 | ForwardedInputPassword.displayName = "PasswordInput";
52 |
53 | const InputComponent = styled(ForwardedInputPassword)``;
54 |
55 | InputComponent.displayName = "KitchnPasswordInput";
56 | export const PasswordInput = withDecorator(InputComponent);
57 | export default PasswordInput;
58 |
--------------------------------------------------------------------------------
/packages/kitchn/src/hoc/with-decorator/area.ts:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from "react";
2 | import { css } from "styled-components";
3 |
4 | import { handleValue } from "./decorator";
5 | import { Breakpoint, Gap } from "../../types/theme";
6 |
7 | export type AreaProps = {
8 | width?: CSSProperties["width"] | keyof Gap | keyof Breakpoint;
9 | height?: CSSProperties["height"] | keyof Gap | keyof Breakpoint;
10 | w?: AreaProps["width"];
11 | h?: AreaProps["height"];
12 | minWidth?: CSSProperties["minWidth"] | keyof Gap | keyof Breakpoint;
13 | minHeight?: CSSProperties["minHeight"] | keyof Gap | keyof Breakpoint;
14 | maxWidth?: CSSProperties["maxWidth"] | keyof Gap | keyof Breakpoint;
15 | maxHeight?: CSSProperties["maxHeight"] | keyof Gap | keyof Breakpoint;
16 | minW?: AreaProps["minWidth"];
17 | minH?: AreaProps["minHeight"];
18 | maxW?: AreaProps["maxWidth"];
19 | maxH?: AreaProps["maxHeight"];
20 | };
21 |
22 | export const areaCss = css`
23 | ${({ theme, width, w }) =>
24 | width !== undefined || w !== undefined
25 | ? `width: ${handleValue(theme, width || w)};`
26 | : ""}
27 | ${({ theme, height, h }) =>
28 | height !== undefined || h !== undefined
29 | ? `height: ${handleValue(theme, height || h)};`
30 | : ""}
31 | ${({ theme, minWidth, minW }) =>
32 | minWidth !== undefined || minW !== undefined
33 | ? `min-width: ${handleValue(theme, minWidth || minW)};`
34 | : ""}
35 | ${({ theme, minHeight, minH }) =>
36 | minHeight !== undefined || minH !== undefined
37 | ? `min-height: ${handleValue(theme, minHeight || minH)};`
38 | : ""}
39 | ${({ theme, maxWidth, maxW }) =>
40 | maxWidth !== undefined || maxW !== undefined
41 | ? `max-width: ${handleValue(theme, maxWidth || maxW)};`
42 | : ""}
43 | ${({ theme, maxHeight, maxH }) =>
44 | maxHeight !== undefined || maxH !== undefined
45 | ? `max-height: ${handleValue(theme, maxHeight || maxH)};`
46 | : ""}
47 | `;
48 |
--------------------------------------------------------------------------------
/docs/pages/docs/components/badge.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "React Badge component"
3 | description: "Display an indicator that requires attention."
4 | ---
5 |
6 | import Playground from "@/components/playground";
7 |
8 | # Badge
9 |
10 | Display an indicator that requires attention.
11 |
12 | ## Default
13 |
14 |
17 |
18 | 1
19 |
20 |
21 |
22 | 5
23 |
24 |
25 |
26 | 50
27 |
28 |
29 |
30 | 150
31 |
32 |
33 |
34 | New
35 |
36 |
37 | `}
38 | />
39 |
40 | ## Variants
41 |
42 |
45 |
46 | Primary
47 |
48 |
49 |
50 | Secondary
51 |
52 |
53 |
54 | Info
55 |
56 |
57 |
58 | Success
59 |
60 |
61 |
62 | Warning
63 |
64 |
65 |
66 | Danger
67 |
68 |
69 | `}
70 | />
71 |
72 | ## Sizes
73 |
74 |
77 |
78 | small
79 |
80 |
81 |
82 | normal
83 |
84 |
85 |
86 | normal
87 |
88 |
89 | `}
90 | />
91 |
--------------------------------------------------------------------------------
/packages/kitchn/src/components/input/controlled/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | FieldValues,
4 | ControllerProps,
5 | Control,
6 | Controller,
7 | Path,
8 | } from "react-hook-form";
9 |
10 | import Input, { type InputProps } from "..";
11 | import { withDecorator } from "../../../hoc/with-decorator";
12 |
13 | type ControllerPropsEx = Omit<
14 | ControllerProps,
15 | "render" | "control" | "name"
16 | >;
17 |
18 | type ControlledType = {
19 | name: Path;
20 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
21 | control: Control;
22 | controllerProps?: ControllerPropsEx;
23 | } & T;
24 |
25 | type ControlledTextInputProps = ControlledType<
26 | F,
27 | Omit<
28 | InputProps,
29 | "onChange" | "onBlur" | "value" | "errorMessage" | "ref" | "name"
30 | >
31 | > &
32 | InputProps;
33 |
34 | const ControlledInputComponent = React.forwardRef(
35 | (
36 | { name, control, controllerProps, ...props }: ControlledTextInputProps,
37 | ref: React.Ref,
38 | ) => (
39 | (
51 |
61 | )}
62 | />
63 | ),
64 | );
65 |
66 | ControlledInputComponent.displayName = "KitchnControlledInput";
67 | export const ControlledInput = withDecorator(ControlledInputComponent);
68 | export default ControlledInput;
69 |
--------------------------------------------------------------------------------
/workshop/pages/codesandbox.tsx:
--------------------------------------------------------------------------------
1 | import { Badge, Button, Container, Image, Text } from "kitchn";
2 |
3 | const CodeSandboxPage = () => {
4 | return (
5 |
6 |
7 |
8 |
16 |
17 | {"Superhost"}
18 |
19 |
20 |
21 | {"Apartment in Paris"}
22 |
23 | {"⭐"}{" "}
24 |
25 | {"4.9\r"}
26 | {" "}
27 | {"(20)\r"}
28 |
29 |
30 |
31 | {"Large 2 rooms flat with a terrace"}
32 | {"1 king bed"}
33 | {"Aug 19 - 26"}
34 |
35 |
36 |
37 | {"$114\r"}
38 | {" "}
39 |
40 | {"$120\r"}
41 | {" "}
42 |
43 | {"night ·"}
44 | {" "}
45 | {"$735 total\r"}
46 |
47 |
48 |
49 |
50 |
51 |
52 | );
53 | };
54 |
55 | export default CodeSandboxPage;
56 |
--------------------------------------------------------------------------------
/packages/kitchn/src/components/combobox/item/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | import { withDecorator } from "../../../hoc";
5 | import { useCombobox } from "../../../hooks";
6 | import { KitchnComponent } from "../../../types";
7 | import Container, { ContainerProps } from "../../container";
8 | import Text from "../../text";
9 |
10 | type Props = {
11 | value: string;
12 | isLabelOnly?: boolean;
13 | };
14 |
15 | export type ComboboxItemProps = KitchnComponent;
16 |
17 | const ComboboxItemComponent = styled(
18 | ({
19 | value: identValue,
20 | children,
21 | isLabelOnly,
22 | onClick,
23 | ...props
24 | }: ComboboxItemProps) => {
25 | const { value, updateValue, updateVisible } = useCombobox();
26 | const selectHandler = (
27 | event: React.MouseEvent,
28 | ) => {
29 | updateValue && updateValue(identValue);
30 | updateVisible && updateVisible(false);
31 | onClick && onClick(event);
32 | };
33 |
34 | const isActive = React.useMemo(
35 | () => value === identValue,
36 | [identValue, value],
37 | );
38 |
39 | return (
40 |
48 | {isLabelOnly ? (
49 |
50 | {children}
51 |
52 | ) : (
53 |
54 | {children}
55 |
56 | )}
57 |
58 | );
59 | },
60 | )`
61 | transition: all 0.2s;
62 | &:hover {
63 | background-color: ${({ theme }) => theme.colors.layout.dark};
64 | }
65 | `;
66 |
67 | ComboboxItemComponent.displayName = "KitchenComboboxItem";
68 | export const ComboboxItem = withDecorator(ComboboxItemComponent);
69 | export default ComboboxItem;
70 |
--------------------------------------------------------------------------------
/packages/kitchn/src/utils/codes.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * KeyBinding Codes
3 | * The content of this file is based on the design of the open source project "microsoft/vscode",
4 | * Copyright (c) Microsoft Corporation. All rights reserved.
5 | *
6 | * We inherit the KeyMod values from "microsoft/vscode",
7 | * but use the Browser's KeyboardEvent event implementation, and all values are used only as identification.
8 | */
9 |
10 | export enum KeyCode {
11 | Unknown = 0,
12 | Backspace = 8,
13 | Tab = 9,
14 | Enter = 13,
15 | Shift = 16,
16 | Ctrl = 17,
17 | Alt = 18,
18 | PauseBreak = 19,
19 | CapsLock = 20,
20 | Escape = 27,
21 | Space = 32,
22 | PageUp = 33,
23 | PageDown = 34,
24 | End = 35,
25 | Home = 36,
26 | LeftArrow = 37,
27 | UpArrow = 38,
28 | RightArrow = 39,
29 | DownArrow = 40,
30 | Insert = 45,
31 | Delete = 46,
32 | KEY_0 = 48,
33 | KEY_1 = 49,
34 | KEY_2 = 50,
35 | KEY_3 = 51,
36 | KEY_4 = 52,
37 | KEY_5 = 53,
38 | KEY_6 = 54,
39 | KEY_7 = 55,
40 | KEY_8 = 56,
41 | KEY_9 = 57,
42 | KEY_A = 65,
43 | KEY_B = 66,
44 | KEY_C = 67,
45 | KEY_D = 68,
46 | KEY_E = 69,
47 | KEY_F = 70,
48 | KEY_G = 71,
49 | KEY_H = 72,
50 | KEY_I = 73,
51 | KEY_J = 74,
52 | KEY_K = 75,
53 | KEY_L = 76,
54 | KEY_M = 77,
55 | KEY_N = 78,
56 | KEY_O = 79,
57 | KEY_P = 80,
58 | KEY_Q = 81,
59 | KEY_R = 82,
60 | KEY_S = 83,
61 | KEY_T = 84,
62 | KEY_U = 85,
63 | KEY_V = 86,
64 | KEY_W = 87,
65 | KEY_X = 88,
66 | KEY_Y = 89,
67 | KEY_Z = 90,
68 | Meta = 91,
69 | F1 = 112,
70 | F2 = 113,
71 | F3 = 114,
72 | F4 = 115,
73 | F5 = 116,
74 | F6 = 117,
75 | F7 = 118,
76 | F8 = 119,
77 | F9 = 120,
78 | F10 = 121,
79 | F11 = 122,
80 | F12 = 123,
81 | NumLock = 144,
82 | ScrollLock = 145,
83 | Equal = 187,
84 | Minus = 189,
85 | Backquote = 192,
86 | Backslash = 220,
87 | }
88 |
89 | export enum KeyMod {
90 | CtrlCmd = (1 << 11) >>> 0,
91 | Shift = (1 << 10) >>> 0,
92 | Alt = (1 << 9) >>> 0,
93 | WinCtrl = (1 << 8) >>> 0,
94 | }
95 |
--------------------------------------------------------------------------------
/docs/pages/docs/hooks/use-resize.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Handling window resize events in React"
3 | description: "A custom hook that triggers a callback function whenever the window is resized, with an option to invoke the callback immediately upon mounting."
4 | ---
5 |
6 | import Playground from "@/components/playground";
7 |
8 | # useResize
9 |
10 | The `useResize` hook allows you to execute a callback function whenever the window is resized. This can be useful for handling responsive layouts, recalculating element positions, or performing other actions that depend on the window's dimensions. Additionally, the hook provides an option to invoke the callback immediately when the component mounts.
11 |
12 | ## Usage
13 |
14 | First, you need to import the `useResize` hook from the `kitchn` package.
15 |
16 | ```jsx
17 | import { useResize } from "kitchn";
18 | ```
19 |
20 | ## Example
21 |
22 | Here is an example of how to use the `useResize` hook in a component:
23 |
24 | {
27 | const [windowSize, setWindowSize] = React.useState({
28 | width: window.innerWidth,
29 | height: window.innerHeight,
30 | });
31 |
32 | useResize(() => {
33 | setWindowSize({
34 | width: window.innerWidth,
35 | height: window.innerHeight,
36 | });
37 | });
38 |
39 | return (
40 |
41 | Window Width: {windowSize.width}px
42 | Window Height: {windowSize.height}px
43 |
44 | );
45 | };
46 | `}
47 | />
48 |
49 | ## Parameters
50 |
51 | The `useResize` hook accepts the following parameters:
52 |
53 | | Name | Type | Description
54 | |----------------|-------------------------------------------------|---------------------------------------------------
55 | | `callback` | `() => unknown` | A function to be called whenever the window is resized.
56 | | `immediatelyInvoke` | `boolean` | Whether to invoke the callback immediately upon component mount.
57 |
58 | ## Return Value
59 |
60 | The `useResize` hook does not return any value.
61 |
--------------------------------------------------------------------------------
/packages/kitchn/src/components/global-style/main.ts:
--------------------------------------------------------------------------------
1 | import { css } from "styled-components";
2 |
3 | export const mainCss = css`
4 | body {
5 | font-family:
6 | -apple-system, BlinkMacSystemFont, Roboto, "Segoe UI", "Fira Sans",
7 | Avenir, "Helvetica Neue", "Lucida Grande", sans-serif;
8 | background-color: ${({ theme }) => theme.colors.layout.darkest};
9 | }
10 |
11 | option {
12 | background-color: ${({ theme }) => theme.colors.layout.darkest};
13 | }
14 |
15 | /* scrollbar */
16 |
17 | html,
18 | body {
19 | scrollbar-color: ${({ theme }) => theme.colors.layout.dark}
20 | ${({ theme }) => theme.colors.layout.darkest};
21 | }
22 |
23 | ::-webkit-scrollbar {
24 | width: 5px;
25 | /* for vertical scrollbars */
26 | height: 5px;
27 | /* for horizontal scrollbars */
28 | }
29 |
30 | ::-webkit-scrollbar-track {
31 | background: transparent;
32 | }
33 |
34 | ::-webkit-scrollbar-thumb {
35 | background: ${({ theme }) => theme.colors.layout.dark};
36 | height: 5px;
37 | border-radius: 5px;
38 | }
39 |
40 | ::-webkit-scrollbar-track-piece {
41 | height: 30px;
42 | }
43 |
44 | /* fonts */
45 |
46 | body,
47 | input,
48 | button,
49 | textarea,
50 | select,
51 | option {
52 | font-family: ${({ theme }) => theme.family.primary};
53 | font-size: ${({ theme }) => theme.size.normal};
54 | font-weight: ${({ theme }) => theme.weight.regular};
55 | text-align: left;
56 | color: ${({ theme }) => theme.colors.text.lightest};
57 | line-height: 1.25;
58 | }
59 |
60 | span {
61 | line-height: 1;
62 | }
63 |
64 | strong,
65 | b {
66 | font-weight: ${(props) => props.theme.weight.bold};
67 | }
68 |
69 | a {
70 | text-decoration: none;
71 | color: inherit;
72 | cursor: pointer;
73 |
74 | &:hover {
75 | filter: brightness(0.8);
76 | }
77 | }
78 |
79 | pre,
80 | code {
81 | font-family: ${({ theme }) => theme.family.monospace};
82 |
83 | span {
84 | font-family: ${({ theme }) => theme.family.monospace};
85 | }
86 | }
87 | `;
88 |
--------------------------------------------------------------------------------
/packages/kitchn/src/components/code/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | import { withDecorator } from "../../hoc/with-decorator";
5 | import { KitchnComponent } from "../../types";
6 |
7 | type Props = {
8 | title: string;
9 | };
10 |
11 | export type CodeProps = KitchnComponent<
12 | Props,
13 | React.HTMLAttributes
14 | >;
15 |
16 | const CodeComponent = styled(({ children, title, ...props }: CodeProps) => {
17 | return (
18 |
19 | {title && (
20 |
21 | {title}
22 |
23 | )}
24 | {children}
25 |
26 | );
27 | })`
28 | position: relative;
29 | border: 1px solid ${({ theme }) => theme.colors.layout.dark};
30 | border-radius: 8px;
31 | padding: ${({ title }) => (title ? "35px 15px 15px" : "15px")};
32 | margin: 30px 0;
33 | white-space: pre;
34 | overflow: auto;
35 | `;
36 |
37 | export const CodeHeader = styled.header`
38 | position: absolute;
39 | top: 0;
40 | left: 0;
41 | display: flex;
42 | justify-content: space-between;
43 | border-radius: 6px;
44 | `;
45 |
46 | export const CodeTitle = styled.div`
47 | font-size: ${({ theme }) => theme.size.small};
48 | background-color: ${({ theme }) => theme.colors.layout.dark};
49 | color: ${({ theme }) => theme.colors.text.lighter};
50 | border-top-left-radius: 8px;
51 | border-bottom-right-radius: 8px;
52 | padding: 5px;
53 | `;
54 |
55 | export const CodeContent = styled.code`
56 | color: ${({ theme }) => theme.colors.text.lightest};
57 | text-align: left;
58 | white-space: pre;
59 | word-spacing: normal;
60 | word-break: normal;
61 | font-size: ${({ theme }) => theme.size.small};
62 | line-height: 1.5;
63 | font-family: ${({ theme }) => theme.family.monospace};
64 | tab-size: 4;
65 | hyphens: none;
66 | `;
67 |
68 | CodeComponent.displayName = "KitchnCode";
69 | export const Code = withDecorator(CodeComponent);
70 | export default Code;
71 |
--------------------------------------------------------------------------------
/packages/kitchn/src/components/avatar/group/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | import Avatar, { AvatarProps } from "..";
5 | import { withDecorator } from "../../../hoc/with-decorator";
6 | import { KitchnComponent } from "../../../types";
7 | import Text from "../../text";
8 |
9 | type Props = {
10 | /**
11 | * The members of the avatar group.
12 | */
13 | members: {
14 | username?: AvatarProps["username"];
15 | src?: AvatarProps["src"];
16 | text?: AvatarProps["text"];
17 | }[];
18 |
19 | /**
20 | * The limit of the avatar group.
21 | * If the members are more than the limit,
22 | * the last avatar will be a counter.
23 | */
24 | limit?: number;
25 |
26 | /**
27 | * The size of the avatar.
28 | */
29 | size?: number | string;
30 | };
31 |
32 | export type AvatarGroupProps = KitchnComponent;
33 |
34 | const AvatarGroupComponent = styled(
35 | ({ members, size, limit, ...props }: AvatarGroupProps) => {
36 | return (
37 |
38 | {members
39 | .slice(0, limit ? limit : members.length)
40 | .map((member, index) => {
41 | return
;
42 | })}
43 | {limit && members.length > limit && (
44 |
45 | {"+"}
46 | {members.length - limit}
47 |
48 | )}
49 |
50 | );
51 | },
52 | )`
53 | display: flex;
54 | align-items: center;
55 |
56 | ${Text} {
57 | margin-left: ${({ theme }) => theme.gap.tiny};
58 | }
59 |
60 | ${Avatar} {
61 | margin-left: -10px;
62 |
63 | ${Text} {
64 | margin-left: 0;
65 | }
66 |
67 | &:first-child {
68 | margin-left: 0;
69 | }
70 | }
71 |
72 | &:hover {
73 | ${Avatar} {
74 | margin-left: 0;
75 | }
76 | }
77 | `;
78 |
79 | AvatarGroupComponent.displayName = "KitchnAvatarGroup";
80 | export const AvatarGroup = withDecorator(AvatarGroupComponent);
81 | export default AvatarGroup;
82 |
--------------------------------------------------------------------------------