= {
31 | id: 'error',
32 | social: { facebook: 'error' },
33 | };
34 | // type touched = {
35 | // id?: {} | undefined;
36 | // social?: {
37 | // facebook?: {} | undefined;
38 | // } | undefined;
39 | // }
40 | const id: {} | undefined = errors.id;
41 | expect(id).toBe('error');
42 | const facebook: {} | undefined = errors.social!.facebook;
43 | expect(facebook).toBe('error');
44 | });
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/packages/formik/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src", "types"],
4 | "compilerOptions": {
5 | "rootDir": "./src"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/packages/formik/types/global.d.ts:
--------------------------------------------------------------------------------
1 | // Declare global variables for TypeScript and VSCode.
2 | // Do not rename this file or move these types into index.d.ts
3 | // @see https://code.visualstudio.com/docs/nodejs/working-with-javascript#_global-variables-and-type-checking
4 | declare const __DEV__: boolean;
5 |
--------------------------------------------------------------------------------
/packages/formik/types/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'tiny-warning' {
2 | export default function warning(condition: any, message: string): void;
3 | }
4 |
5 | declare module 'react-lifecycles-compat' {
6 | import React from 'react';
7 | export function polyfill(
8 | Comp: React.ComponentType
9 | ): React.ComponentType
;
10 | }
11 |
12 | declare module 'deepmerge' {
13 | export = deepmerge;
14 |
15 | function deepmerge(
16 | x: Partial,
17 | y: Partial,
18 | options?: deepmerge.Options
19 | ): T;
20 | function deepmerge(
21 | x: T1,
22 | y: T2,
23 | options?: deepmerge.Options
24 | ): T1 & T2;
25 |
26 | namespace deepmerge {
27 | interface Options {
28 | clone?: boolean;
29 | arrayMerge?(destination: any[], source: any[], options?: Options): any[];
30 | isMergeableObject?(value: object): boolean;
31 | }
32 |
33 | function all(objects: Array>, options?: Options): T;
34 | }
35 | }
36 |
37 | declare module 'scheduler' {
38 | export const unstable_NoPriority = 0;
39 | export const unstable_ImmediatePriority = 1;
40 | export const unstable_UserBlockingPriority = 2;
41 | export const unstable_NormalPriority = 3;
42 | export const unstable_LowPriority = 4;
43 | export const unstable_IdlePriority = 5;
44 |
45 | export function unstable_runWithPriority(
46 | priorityLevel: number,
47 | eventHandler: () => T
48 | ): T;
49 |
50 | export interface Task {
51 | id: number;
52 | }
53 |
54 | export interface ScheduleCallbackOptions {
55 | delay?: number;
56 | }
57 |
58 | export function unstable_scheduleCallback(
59 | priorityLevel: number,
60 | callback: () => void,
61 | options?: ScheduleCallbackOptions
62 | ): Task;
63 |
64 | export function unstable_cancelCallback(task: Task): void;
65 | }
66 |
--------------------------------------------------------------------------------
/playwright.config.ts:
--------------------------------------------------------------------------------
1 | import { PlaywrightTestProject, defineConfig, devices } from '@playwright/test';
2 |
3 | /**
4 | * Read environment variables from file.
5 | * https://github.com/motdotla/dotenv
6 | */
7 | // require('dotenv').config();
8 |
9 | /**
10 | * See https://playwright.dev/docs/test-configuration.
11 | */
12 | export default defineConfig({
13 | testDir: './e2e',
14 | /* Run tests in files in parallel */
15 | fullyParallel: true,
16 | /* Fail the build on CI if you accidentally left test.only in the source code. */
17 | forbidOnly: !!process.env.CI,
18 | /* Retry on CI only */
19 | retries: process.env.CI ? 2 : 0,
20 | /* Opt out of parallel tests on CI. */
21 | workers: process.env.CI ? 1 : undefined,
22 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */
23 | reporter: 'html',
24 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
25 | use: {
26 | /* Base URL to use in actions like `await page.goto('/')`. */
27 | baseURL: 'http://127.0.0.1:3000',
28 |
29 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
30 | trace: 'on-first-retry',
31 | },
32 |
33 | /* Configure projects for major browsers */
34 | projects: [
35 | {
36 | name: 'chromium',
37 | use: { ...devices['Desktop Chrome'] },
38 | },
39 |
40 | {
41 | name: 'firefox',
42 | use: { ...devices['Desktop Firefox'] },
43 | },
44 |
45 | // this one does not seem to be functional in github actions
46 | !process.env.CI
47 | ? {
48 | name: 'webkit',
49 | use: { ...devices['Desktop Safari'] },
50 | }
51 | : null,
52 |
53 | /* Test against mobile viewports. */
54 | // {
55 | // name: 'Mobile Chrome',
56 | // use: { ...devices['Pixel 5'] },
57 | // },
58 | // {
59 | // name: 'Mobile Safari',
60 | // use: { ...devices['iPhone 12'] },
61 | // },
62 |
63 | /* Test against branded browsers. */
64 | // {
65 | // name: 'Microsoft Edge',
66 | // use: { ...devices['Desktop Edge'], channel: 'msedge' },
67 | // },
68 | // {
69 | // name: 'Google Chrome',
70 | // use: { ..devices['Desktop Chrome'], channel: 'chrome' },
71 | // },
72 | ].filter(Boolean) as PlaywrightTestProject[],
73 |
74 | /* Run your local dev server before starting the tests */
75 | webServer: {
76 | command: 'npm run start:app',
77 | url: 'http://127.0.0.1:3000',
78 | reuseExistingServer: !process.env.CI,
79 | },
80 | });
81 |
--------------------------------------------------------------------------------
/scripts/btag.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | git branch $1-branch $1
4 | git checkout $1-branch
--------------------------------------------------------------------------------
/scripts/retag.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | git tag -d $1
4 | git tag $1
5 | git push origin :$1
6 | git push origin $1
7 | git fetch --tags
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "esnext",
4 | "lib": ["dom", "esnext"],
5 | "importHelpers": true,
6 | "noImplicitAny": true,
7 | "alwaysStrict": true,
8 | // output .d.ts declaration files for consumers
9 | "declaration": true,
10 | // output .js.map sourcemap files for consumers
11 | "sourceMap": true,
12 | // stricter type-checking for stronger correctness. Recommended by TS
13 | "strict": true,
14 | // linter checks for common issues
15 | "noImplicitReturns": true,
16 | "noFallthroughCasesInSwitch": true,
17 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | // use Node's module resolution algorithm, instead of the legacy TS one
21 | "moduleResolution": "node",
22 | // transpile JSX to React.createElement
23 | "jsx": "react",
24 | // interop between ESM and CJS modules. Recommended by TS
25 | "esModuleInterop": true,
26 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS
27 | "skipLibCheck": true,
28 | // error out if import and file system have a casing mismatch. Recommended by TS
29 | "forceConsistentCasingInFileNames": true,
30 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc`
31 | "noEmit": true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base.json",
3 | "include": ["packages", "types", "scripts"],
4 | "compilerOptions": {
5 | "allowJs": false,
6 | "baseUrl": ".",
7 | "typeRoots": ["./node_modules/@types", "./types"],
8 | "paths": {
9 | "formik": ["./packages/react/src"],
10 | "formik-native": ["./packages/react-native/src"],
11 | "$test/*": ["test/*"]
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "pipeline": {
4 | "build": {
5 | "dependsOn": ["^build"],
6 | "outputs": ["dist/**", ".next/**", "!.next/cache/**"]
7 | },
8 | "lint": {
9 | "dependsOn": ["^build", "build"]
10 | },
11 | "test": {
12 | "dependsOn": ["^build", "build"]
13 | },
14 | "start": {
15 | "cache": false,
16 | "persistent": true
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/website/.eslintignore:
--------------------------------------------------------------------------------
1 | **/node_modules/*
2 | **/out/*
3 | **/.next/*
4 |
--------------------------------------------------------------------------------
/website/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
--------------------------------------------------------------------------------
/website/.gitignore:
--------------------------------------------------------------------------------
1 | .next
2 | .now
3 | .env
4 | .env.*
5 | node_modules
6 | *.log
7 | .DS_Store
8 | .vercel
9 | public/sitemap.xml
--------------------------------------------------------------------------------
/website/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .next
3 | yarn.lock
4 | package-lock.json
5 | public
6 |
--------------------------------------------------------------------------------
/website/README.md:
--------------------------------------------------------------------------------
1 | # Formik Docs
2 |
3 | This is source code to formik.org. It is built with:
4 |
5 | - Next.js
6 | - MDX
7 | - Tailwind
8 | - Algolia
9 | - Notion
10 |
11 | ## Running locally
12 |
13 | ```sh
14 | yarn install
15 | ```
16 |
17 | At the moment, you need to signup for Notion, and [follow these instructions](https://github.com/ijjk/notion-blog#getting-blog-index-and-token) to get a token and create a blog in order to develop locally. Not ideal, but hopefully will fix soon.
18 |
19 | With tokens and page index in hand, rename `.sample.env` and `.sample.env.build` to just `.env` and `.env.build`. In each one, add respective parameters:
20 |
21 | ```diff
22 | -NOTION_TOKEN=XXXX
23 | +NOTION_TOKEN=
24 | -BLOG_INDEX_ID=XXXXX
25 | +BLOG_INDEX_ID=
26 | ```
27 |
28 | Now it will work. Run `yarn dev` to get going.
29 |
30 | If you get stuck or need help, [send a DM to Jared](https://twitter.com/jaredpalmer) on Twitter.
31 |
--------------------------------------------------------------------------------
/website/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/website/next.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const fs = require('fs');
3 | const path = require('path');
4 | const visit = require('unist-util-visit');
5 | const remarkPlugins = require('./src/lib/docs/remark-plugins');
6 |
7 | module.exports = {
8 | pageExtensions: ['jsx', 'js', 'ts', 'tsx', 'mdx', 'md'],
9 | env: {
10 | NEXT_PUBLIC_GA_TRACKING_ID: process.env.NEXT_PUBLIC_GA_TRACKING_ID || '',
11 | },
12 | rewrites() {
13 | return [
14 | {
15 | source: '/feed.xml',
16 | destination: '/_next/static/feed.xml',
17 | },
18 | {
19 | source: '/docs{/}?',
20 | destination: '/docs/overview',
21 | },
22 | {
23 | source: '/docs/tag/:tag{/}?',
24 | destination: '/docs/tag/:tag/overview',
25 | },
26 | ];
27 | },
28 | webpack: (config, { dev, isServer, ...options }) => {
29 | config.module.rules.push({
30 | test: /.mdx?$/, // load both .md and .mdx files
31 | use: [
32 | options.defaultLoaders.babel,
33 | {
34 | loader: '@mdx-js/loader',
35 | options: {
36 | remarkPlugins,
37 | },
38 | },
39 | path.join(__dirname, './src/lib/docs/md-loader'),
40 | ],
41 | });
42 |
43 | if (!dev && isServer) {
44 | // we're in build mode so enable shared caching for the GitHub API
45 | process.env.USE_CACHE = 'true';
46 | const originalEntry = config.entry;
47 |
48 | config.entry = async () => {
49 | const entries = { ...(await originalEntry()) };
50 | return entries;
51 | };
52 | }
53 |
54 | return config;
55 | },
56 | };
57 |
--------------------------------------------------------------------------------
/website/postcss.config.js:
--------------------------------------------------------------------------------
1 | // postcss.config.js
2 | module.exports = {
3 | plugins: {
4 | tailwindcss: {},
5 | autoprefixer: {},
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/website/public/images/blog/algolia-docsearch-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredpalmer/formik/0e0cf9ea09ec864dd63c52cf775f862795ef2cf4/website/public/images/blog/algolia-docsearch-screenshot.png
--------------------------------------------------------------------------------
/website/public/images/blog/docusaurus-v1-vs-v2-nav.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredpalmer/formik/0e0cf9ea09ec864dd63c52cf775f862795ef2cf4/website/public/images/blog/docusaurus-v1-vs-v2-nav.mp4
--------------------------------------------------------------------------------
/website/public/images/blog/formik-landing-page-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredpalmer/formik/0e0cf9ea09ec864dd63c52cf775f862795ef2cf4/website/public/images/blog/formik-landing-page-screenshot.png
--------------------------------------------------------------------------------
/website/public/images/blog/formik-mdx-docs-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredpalmer/formik/0e0cf9ea09ec864dd63c52cf775f862795ef2cf4/website/public/images/blog/formik-mdx-docs-screenshot.png
--------------------------------------------------------------------------------
/website/public/images/blog/notion-cms-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredpalmer/formik/0e0cf9ea09ec864dd63c52cf775f862795ef2cf4/website/public/images/blog/notion-cms-screenshot.png
--------------------------------------------------------------------------------
/website/public/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredpalmer/formik/0e0cf9ea09ec864dd63c52cf775f862795ef2cf4/website/public/images/favicon.png
--------------------------------------------------------------------------------
/website/public/images/formik-mark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/public/images/formik-og.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredpalmer/formik/0e0cf9ea09ec864dd63c52cf775f862795ef2cf4/website/public/images/formik-og.png
--------------------------------------------------------------------------------
/website/public/images/formik-twitter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredpalmer/formik/0e0cf9ea09ec864dd63c52cf775f862795ef2cf4/website/public/images/formik-twitter.png
--------------------------------------------------------------------------------
/website/public/images/formik.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredpalmer/formik/0e0cf9ea09ec864dd63c52cf775f862795ef2cf4/website/public/images/formik.png
--------------------------------------------------------------------------------
/website/public/images/hero6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredpalmer/formik/0e0cf9ea09ec864dd63c52cf775f862795ef2cf4/website/public/images/hero6.png
--------------------------------------------------------------------------------
/website/public/images/logo-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredpalmer/formik/0e0cf9ea09ec864dd63c52cf775f862795ef2cf4/website/public/images/logo-white.png
--------------------------------------------------------------------------------
/website/public/images/logos/artsy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredpalmer/formik/0e0cf9ea09ec864dd63c52cf775f862795ef2cf4/website/public/images/logos/artsy.png
--------------------------------------------------------------------------------
/website/public/images/logos/campusjaeger.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredpalmer/formik/0e0cf9ea09ec864dd63c52cf775f862795ef2cf4/website/public/images/logos/campusjaeger.png
--------------------------------------------------------------------------------
/website/public/images/logos/capsule.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/public/images/logos/frameio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredpalmer/formik/0e0cf9ea09ec864dd63c52cf775f862795ef2cf4/website/public/images/logos/frameio.png
--------------------------------------------------------------------------------
/website/public/images/logos/gitconnected-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredpalmer/formik/0e0cf9ea09ec864dd63c52cf775f862795ef2cf4/website/public/images/logos/gitconnected-logo.png
--------------------------------------------------------------------------------
/website/public/images/logos/gremlin.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/public/images/logos/letgo-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredpalmer/formik/0e0cf9ea09ec864dd63c52cf775f862795ef2cf4/website/public/images/logos/letgo-logo.png
--------------------------------------------------------------------------------
/website/public/images/logos/lyft.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/website/public/images/logos/nokia.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/public/images/logos/priceline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredpalmer/formik/0e0cf9ea09ec864dd63c52cf775f862795ef2cf4/website/public/images/logos/priceline.png
--------------------------------------------------------------------------------
/website/public/images/logos/sony.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/public/images/logos/state-street.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredpalmer/formik/0e0cf9ea09ec864dd63c52cf775f862795ef2cf4/website/public/images/logos/state-street.png
--------------------------------------------------------------------------------
/website/public/images/logos/stripe.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
25 |
--------------------------------------------------------------------------------
/website/public/images/logos/viacom.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/public/images/logos/zauberware-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/public/images/oss_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredpalmer/formik/0e0cf9ea09ec864dd63c52cf775f862795ef2cf4/website/public/images/oss_logo.png
--------------------------------------------------------------------------------
/website/public/images/pattern.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredpalmer/formik/0e0cf9ea09ec864dd63c52cf775f862795ef2cf4/website/public/images/pattern.png
--------------------------------------------------------------------------------
/website/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
--------------------------------------------------------------------------------
/website/public/twemoji/1f600.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/website/public/twemoji/1f615.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/website/public/twemoji/1f62d.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/public/twemoji/1f929.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/scripts/build-sitemap.js:
--------------------------------------------------------------------------------
1 | const sitemap = require('nextjs-sitemap-generator');
2 | const fs = require('fs');
3 |
4 | // This is needed for the plugin to work
5 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
6 | const BUILD_ID = fs.readFileSync('.next/BUILD_ID').toString();
7 |
8 | sitemap({
9 | baseUrl: 'https://formik.org',
10 | pagesDirectory: process.cwd() + '/.next/server/pages',
11 | targetDirectory: 'public/',
12 | ignoredExtensions: ['js', 'map'],
13 | ignoredPaths: ['/404', '/blog/[...slug]'],
14 | });
15 |
--------------------------------------------------------------------------------
/website/src/components/ArrowRight.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export function ArrowRight({ fill = '#718096', width = 6, height = 10 }: any) {
4 | return (
5 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/website/src/components/Banner.tsx:
--------------------------------------------------------------------------------
1 | import { ExternalLink } from './ExternalLink';
2 |
3 | export function Banner() {
4 | return null;
5 | }
6 |
--------------------------------------------------------------------------------
/website/src/components/Container.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import cn from 'classnames';
3 | import { HTMLProps } from 'react';
4 |
5 | export const Container: React.FC = ({
6 | className,
7 | ...props
8 | }) => {
9 | return ;
10 | };
11 |
12 | Container.displayName = 'Container';
13 |
--------------------------------------------------------------------------------
/website/src/components/ExternalLink.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export function ExternalLink(props: any) {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/website/src/components/MDXComponents.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import dynamic from 'next/dynamic';
3 | import Image from 'next/image';
4 | import Head from 'next/head';
5 | import Link from 'next/link';
6 |
7 | const Img = (props: any) => (
8 |
17 | );
18 |
19 | export default {
20 | // default tags
21 | pre: (p: any) => ,
22 | img: Img,
23 | code: dynamic(() => import('./Highlight2')),
24 | a: Link,
25 | Head,
26 | };
27 |
--------------------------------------------------------------------------------
/website/src/components/ReactionForm.tsx:
--------------------------------------------------------------------------------
1 | import va from '@vercel/analytics';
2 | import cn from 'classnames';
3 | import { useRouter } from 'next/router';
4 | import * as React from 'react';
5 | import { mergeProps, useButton, useHover } from 'react-aria';
6 |
7 | export interface FeedbackButtonProps {
8 | intent: 'tears' | 'meh' | 'happy' | 'awesome';
9 | onPress: () => void;
10 | }
11 |
12 | const mapIntentToSource = {
13 | tears: '/twemoji/1f62d.svg',
14 | meh: '/twemoji/1f615.svg',
15 | happy: '/twemoji/1f600.svg',
16 | awesome: '/twemoji/1f929.svg',
17 | };
18 |
19 | export function FeedbackButton({ intent, ...props }: FeedbackButtonProps) {
20 | const ref = React.useRef(null);
21 | const { buttonProps } = useButton(props, ref);
22 | const { isHovered, hoverProps } = useHover({});
23 | const mergedProps = mergeProps(hoverProps, buttonProps);
24 | return (
25 | <>
26 |
38 |
46 | >
47 | );
48 | }
49 |
50 | FeedbackButton.displayName = 'FeedbackButton';
51 |
52 | export function ReactionForm() {
53 | const [feedbackGiven, setFeedbackGiven] = React.useState(false);
54 |
55 | const { asPath } = useRouter();
56 | React.useEffect(() => {
57 | setFeedbackGiven(false);
58 | }, [asPath, setFeedbackGiven]);
59 |
60 | const makeTrackedHandler = (value: number) => () => {
61 | va.track('Feedback Button', {
62 | category: 'Feedback Button',
63 | action: 'feedback',
64 | name: 'feedback',
65 | label: window.location.pathname,
66 | value,
67 | });
68 | setFeedbackGiven(true);
69 | };
70 |
71 | if (feedbackGiven) {
72 | return (
73 |
74 | Thanks for letting us know!
75 |
76 | );
77 | } else {
78 | return (
79 | <>
80 |
81 | Was this page helpful?
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | >
90 | );
91 | }
92 | }
93 |
94 | ReactionForm.displayName = 'ReactionForm';
95 |
--------------------------------------------------------------------------------
/website/src/components/Seo.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Head from 'next/head';
3 | import { useRouter } from 'next/router';
4 |
5 | export interface SeoProps {
6 | title: string;
7 | description?: string;
8 | image?: string;
9 | // jsonld?: JsonLDType | Array;
10 | children?: React.ReactNode;
11 | }
12 |
13 | export const Seo: React.FC = ({
14 | title,
15 | description,
16 | image = '/images/formik-og.png',
17 | children,
18 | }: SeoProps) => {
19 | const router = useRouter();
20 | return (
21 |
22 | {/* DEFAULT */}
23 |
24 | {title != undefined && {title}}
25 | {description != undefined && (
26 |
27 | )}
28 |
29 |
30 |
31 | {/* OPEN GRAPH */}
32 |
33 |
38 | {title != undefined && (
39 |
40 | )}
41 | {description != undefined && (
42 |
47 | )}
48 | {image != undefined && (
49 |
54 | )}
55 |
56 | {/* TWITTER */}
57 |
62 |
63 |
64 | {title != undefined && (
65 |
66 | )}
67 | {description != undefined && (
68 |
73 | )}
74 | {image != undefined && (
75 |
80 | )}
81 |
82 | {children}
83 |
84 | );
85 | };
86 |
--------------------------------------------------------------------------------
/website/src/components/Sidebar.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import cn from 'classnames';
3 | import { Search } from './Search';
4 |
5 | export const Sidebar: React.FC<{
6 | active?: boolean;
7 | fixed?: boolean;
8 | children: React.ReactNode;
9 | }> = ({ active, children, fixed }) => {
10 | const [searching, setSearching] = useState(false);
11 |
12 | return (
13 |
53 | );
54 | };
55 |
--------------------------------------------------------------------------------
/website/src/components/SidebarHeading.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export const SidebarHeading: React.FC<{
4 | title: string;
5 | children: React.ReactNode;
6 | }> = ({ title, children }) => {
7 | return (
8 | //
9 | //
{title}
10 | //
{children}
11 | //
12 |
13 |
{title}
14 |
{children}
15 |
22 |
23 | );
24 | };
25 |
26 | SidebarHeading.displayName = 'SidebarHeading';
27 |
--------------------------------------------------------------------------------
/website/src/components/SidebarNavLink.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Link from 'next/link';
3 | import { useRouter } from 'next/router';
4 | import cn from 'classnames';
5 |
6 | export interface SidebarNavLinkProps {
7 | route: any;
8 | level: number;
9 | onClick?: () => void;
10 | categorySelected?: string;
11 | scrollSelectedIntoView?: boolean;
12 | }
13 |
14 | export function SidebarNavLink({
15 | route: { href, pathname, title, selected },
16 | onClick,
17 | }: SidebarNavLinkProps) {
18 | const router = useRouter();
19 | const onlyHashChange = pathname === router.pathname;
20 |
21 | return (
22 |
23 | {
24 | // NOTE: use just anchor element for triggering `hashchange` event
25 | onlyHashChange ? (
26 |
27 | {title}
28 |
29 | ) : (
30 |
{title}
31 | )
32 | }
33 |
75 |
76 | );
77 | }
78 |
79 | SidebarNavLink.displayName = 'SidebarNavLink';
80 |
--------------------------------------------------------------------------------
/website/src/components/SidebarPost.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { SidebarNavLink } from './SidebarNavLink';
3 | import cn from 'classnames';
4 |
5 | export const SidebarPost: React.FC<{
6 | isMobile?: boolean;
7 | level: number;
8 | route: {
9 | selected: boolean;
10 | href: string;
11 | path: string | undefined;
12 | title: string;
13 | pathname: string;
14 | };
15 | onClick?: () => void;
16 | categorySelected?: string;
17 | scrollSelectedIntoView?: boolean;
18 | }> = ({ isMobile, route, level = 1, onClick, ...props }) => {
19 | const selectedRef = React.useRef(null);
20 | const ref = route.selected ? selectedRef : null;
21 | React.useEffect(() => {
22 | if (ref && ref.current && !isMobile) {
23 | const content = document.querySelector('.sidebar-content');
24 | // 32 is the top and bottom margin for `.link`
25 | const height = ref.current.offsetTop - 32;
26 | if (content) {
27 | content.scrollTop = height - (content as any).offsetHeight / 2;
28 | }
29 | }
30 | }, [ref, isMobile]);
31 | return (
32 |
33 |
40 |
59 |
60 | );
61 | };
62 |
--------------------------------------------------------------------------------
/website/src/components/Sticky.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import cn from 'classnames';
4 |
5 | export interface StickyProps {
6 | offset?: number;
7 | className?: string;
8 | shadow?: boolean;
9 | children: React.ReactNode;
10 | }
11 |
12 | export const Sticky: React.FC = ({ offset, children, shadow }) => {
13 | return (
14 |
15 | {children}
16 |
17 |
26 |
27 | );
28 | };
29 |
30 | Sticky.displayName = 'Sticky';
31 |
--------------------------------------------------------------------------------
/website/src/components/Toc.module.css:
--------------------------------------------------------------------------------
1 | /* purgecss start ignore */
2 | .contents__link {
3 | @apply text-gray-700;
4 | }
5 |
6 | .contents__link:hover {
7 | @apply text-blue-600;
8 | }
9 |
10 | .contents__link--active {
11 | @apply font-medium text-blue-700;
12 | }
13 | /* purgecss end ignore */
14 |
--------------------------------------------------------------------------------
/website/src/components/Toc.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import cx from 'classnames';
3 | import { useTocHighlight } from './useTocHighlight';
4 | import { Post } from 'lib/types';
5 | import styles from './Toc.module.css';
6 |
7 | const TOP_OFFSET = 100;
8 |
9 | function getHeaderAnchors(): Element[] {
10 | return Array.prototype.filter.call(
11 | document.getElementsByClassName('anchor'),
12 | function (testElement) {
13 | return (
14 | testElement.parentNode.nodeName === 'H2' ||
15 | testElement.parentNode.nodeName === 'H3'
16 | );
17 | }
18 | );
19 | }
20 | function getHeaderDataFromAnchors(el: Element) {
21 | return {
22 | url: el.getAttribute('href'),
23 | text: el.parentElement?.innerText,
24 | depth: Number(el.parentElement?.nodeName.replace('H', '')),
25 | };
26 | }
27 |
28 | export const Toc: React.FC<{}> = () => {
29 | const headings = useTocHighlight(
30 | styles.contents__link,
31 | styles['contents__link--active'],
32 | TOP_OFFSET,
33 | getHeaderAnchors,
34 | getHeaderDataFromAnchors,
35 | el => el?.parentElement?.id
36 | );
37 | return (
38 |
39 | {headings &&
40 | headings.length > 0 &&
41 | headings.map((h, i) =>
42 | h.url ? (
43 | - 3,
48 | })}
49 | >
50 |
51 | {h.text}
52 |
53 |
54 | ) : null
55 | )}
56 |
57 | );
58 | };
59 |
--------------------------------------------------------------------------------
/website/src/components/addRouterEvents.tsx:
--------------------------------------------------------------------------------
1 | import type { NextRouter } from 'next/router';
2 |
3 | function hasModifier(event: any) {
4 | return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
5 | }
6 | /**
7 | * Util to add router events to anchors that weren't added with React Components
8 | */
9 | export default function addRouterEvents(
10 | node: Node,
11 | router: NextRouter,
12 | { href }: { href: string }
13 | ) {
14 | function onClick(e: any) {
15 | const linkTarget = e.currentTarget.target;
16 | if (
17 | !(
18 | e.defaultPrevented ||
19 | hasModifier(e) ||
20 | (linkTarget && linkTarget !== '_self')
21 | )
22 | ) {
23 | e.preventDefault();
24 | router.push(href).then(success => {
25 | if (success) {
26 | window.scrollTo(0, 0);
27 | document.body.focus();
28 | }
29 | });
30 | }
31 | }
32 | function onMouseEnter() {
33 | router.prefetch(href, href, { priority: true });
34 | }
35 | node.addEventListener('click', onClick as any); // EventListener
36 | node.addEventListener('mouseenter', onMouseEnter);
37 | return () => {
38 | node.removeEventListener('click', onClick); // EventListener
39 | node.removeEventListener('mouseenter', onMouseEnter);
40 | };
41 | }
42 |
--------------------------------------------------------------------------------
/website/src/components/clients/Client.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Image from 'next/image';
3 |
4 | interface ClientProps {
5 | name: string;
6 | image: string;
7 | className?: string;
8 | style?: React.CSSProperties;
9 | }
10 |
11 | export const Client = React.memo(
12 | ({ name, image, style, ...rest }) => (
13 |
14 |
26 |
27 | )
28 | );
29 |
--------------------------------------------------------------------------------
/website/src/components/clients/ClientsMarquee.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Client } from './Client';
3 | import { users } from 'users';
4 |
5 | const pinnedLogos = users.filter(p => p.pinned);
6 |
7 | export const ClientsMarquee = React.memo(props => {
8 | return (
9 |
10 |
11 |
12 | {pinnedLogos.map(({ caption, infoLink, image, style }) => (
13 |
20 | ))}
21 |
22 |
23 |
41 |
42 |
43 | );
44 | });
45 |
46 | (ClientsMarquee as any).displayName = 'ClientsMarquee';
47 |
--------------------------------------------------------------------------------
/website/src/components/clients/Filters.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const Filters: React.FC<{}> = () => (
4 | <>
5 |
17 |
29 |
41 | >
42 | );
43 |
--------------------------------------------------------------------------------
/website/src/components/useBoolean.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useCallback } from 'react';
2 |
3 | interface BooleanUpdater {
4 | setValue: (value: boolean) => void;
5 | toggle: () => void;
6 | setTrue: () => void;
7 | setFalse: () => void;
8 | }
9 |
10 | export const useBoolean = (initial: boolean) => {
11 | const [value, setValue] = useState(initial);
12 | return [
13 | value,
14 | {
15 | setValue,
16 | toggle: useCallback(() => setValue(v => !v), []),
17 | setTrue: useCallback(() => setValue(true), []),
18 | setFalse: useCallback(() => setValue(false), []),
19 | },
20 | ] as [boolean, BooleanUpdater];
21 | };
22 |
--------------------------------------------------------------------------------
/website/src/components/useClipboard.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import copy from 'copy-to-clipboard';
3 |
4 | /**
5 | * React hook to copy content to clipboard
6 | *
7 | * @param text the text or value to copy
8 | * @param timeout delay (in ms) to switch back to initial state once copied.
9 | */
10 | export function useClipboard(text: string, timeout = 1500) {
11 | const [hasCopied, setHasCopied] = React.useState(false);
12 |
13 | const onCopy = React.useCallback(() => {
14 | const didCopy = copy(text);
15 | setHasCopied(didCopy);
16 | }, [text]);
17 |
18 | // @ts-ignore
19 | React.useEffect(() => {
20 | if (hasCopied) {
21 | const id = setTimeout(() => {
22 | setHasCopied(false);
23 | }, timeout);
24 |
25 | return () => clearTimeout(id);
26 | }
27 | }, [timeout, hasCopied]);
28 |
29 | return [hasCopied, onCopy] as const;
30 | }
31 |
--------------------------------------------------------------------------------
/website/src/components/useIsMobile.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useCallback, useEffect } from 'react';
2 |
3 | const useMediaQuery = (width: number) => {
4 | const [targetReached, setTargetReached] = useState(false);
5 |
6 | const updateTarget = useCallback((e: any) => {
7 | if (e.matches) {
8 | setTargetReached(true);
9 | } else {
10 | setTargetReached(false);
11 | }
12 | }, []);
13 |
14 | useEffect(() => {
15 | const media = window.matchMedia(`(max-width: ${width}px)`);
16 | media.addListener(updateTarget);
17 |
18 | // Check on mount (callback is not called until a change occurs)
19 | if (media.matches) {
20 | setTargetReached(true);
21 | }
22 |
23 | return () => media.removeListener(updateTarget);
24 | }, []);
25 |
26 | return targetReached;
27 | };
28 |
29 | const useIsMobile = () => {
30 | return useMediaQuery(640);
31 | };
32 |
33 | export { useMediaQuery, useIsMobile };
34 |
--------------------------------------------------------------------------------
/website/src/components/useOverScroll.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import {
3 | useViewportScroll,
4 | motion,
5 | useTransform,
6 | HTMLMotionProps,
7 | } from 'framer-motion';
8 | import { throttle } from './utils/throttle';
9 |
10 | const throttleFn = (cb: Function) => throttle(cb, 100);
11 |
12 | export const useOverScroll = () => {
13 | const { scrollY } = useViewportScroll();
14 | const ref = React.useRef(null);
15 | const refInner = React.useRef(null);
16 | const [height, setHeight] = React.useState(
17 | typeof window !== 'undefined' ? window.innerHeight : 0
18 | );
19 | const [rect, setRect] = React.useState({
20 | top: 0,
21 | left: 0,
22 | right: 0,
23 | bottom: 0,
24 | x: 0,
25 | y: 0,
26 | } as any);
27 | const [rectInner, setRectInner] = React.useState({
28 | top: 0,
29 | left: 0,
30 | right: 0,
31 | bottom: 0,
32 | x: 0,
33 | y: 0,
34 | } as any);
35 | const y = useTransform(
36 | scrollY,
37 | [rect.bottom - height, rect.top + 300],
38 | [0, -rectInner.height + rect.height]
39 | );
40 | React.useEffect(() => {
41 | if (ref.current) {
42 | setRect(ref.current.getBoundingClientRect());
43 | }
44 | }, [setRect, ref]);
45 |
46 | React.useEffect(() => {
47 | if (refInner.current) {
48 | setRectInner(refInner.current.getBoundingClientRect());
49 | }
50 | }, [setRectInner, refInner]);
51 |
52 | return { ref, refInner, y };
53 | };
54 |
--------------------------------------------------------------------------------
/website/src/components/useTocHighlight.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | interface HeadingData {
4 | text?: string | null;
5 | url?: string | null;
6 | depth?: number;
7 | }
8 | /**
9 | * Sets up Table of Contents highlighting. It requires that
10 | */
11 | export function useTocHighlight(
12 | linkClassName: string,
13 | linkActiveClassName: string,
14 | topOffset: number,
15 | getHeaderAnchors: () => Element[],
16 | getHeaderDataFromAnchor: (el: Element) => HeadingData,
17 | getAnchorHeaderIdentifier: (el: Element) => string | undefined
18 | ) {
19 | const [lastActiveLink, setLastActiveLink] = React.useState<
20 | Element | undefined
21 | >(undefined);
22 | const [headings, setHeadings] = React.useState([]);
23 |
24 | React.useEffect(() => {
25 | setHeadings(getHeaderAnchors().map(getHeaderDataFromAnchor));
26 | }, [setHeadings]);
27 |
28 | React.useEffect(() => {
29 | let headersAnchors: any[] = [];
30 | let links: any[] = [];
31 |
32 | function setActiveLink() {
33 | function getActiveHeaderAnchor() {
34 | let index = 0;
35 | let activeHeaderAnchor = null;
36 |
37 | headersAnchors = getHeaderAnchors();
38 | while (index < headersAnchors.length && !activeHeaderAnchor) {
39 | const headerAnchor = headersAnchors[index];
40 | const { top } = headerAnchor.getBoundingClientRect();
41 |
42 | if (top >= 0 && top <= topOffset) {
43 | activeHeaderAnchor = headerAnchor;
44 | }
45 |
46 | index += 1;
47 | }
48 |
49 | return activeHeaderAnchor;
50 | }
51 |
52 | const activeHeaderAnchor = getActiveHeaderAnchor();
53 |
54 | if (activeHeaderAnchor) {
55 | let index = 0;
56 | let itemHighlighted = false;
57 |
58 | links = document.getElementsByClassName(linkClassName) as any;
59 |
60 | while (index < links.length && !itemHighlighted) {
61 | const link = links[index];
62 | const { href } = link;
63 | const anchorValue = decodeURIComponent(
64 | href.substring(href.indexOf('#') + 1)
65 | );
66 |
67 | if (getAnchorHeaderIdentifier(activeHeaderAnchor) === anchorValue) {
68 | if (lastActiveLink) {
69 | lastActiveLink.classList.remove(linkActiveClassName);
70 | }
71 |
72 | link.classList.add(linkActiveClassName);
73 | setLastActiveLink(link);
74 | itemHighlighted = true;
75 | }
76 |
77 | index += 1;
78 | }
79 | }
80 | }
81 |
82 | document.addEventListener('scroll', setActiveLink);
83 | document.addEventListener('resize', setActiveLink);
84 |
85 | setActiveLink();
86 |
87 | return () => {
88 | document.removeEventListener('scroll', setActiveLink);
89 | document.removeEventListener('resize', setActiveLink);
90 | };
91 | });
92 |
93 | return headings;
94 | }
95 |
--------------------------------------------------------------------------------
/website/src/components/utils/throttle.ts:
--------------------------------------------------------------------------------
1 | export const throttle = (func: any, limit: any) => {
2 | let inThrottle: any;
3 | return function () {
4 | const args = arguments;
5 | // @ts-ignore
6 | const context = this;
7 | if (!inThrottle) {
8 | func.apply(context, args);
9 | inThrottle = true;
10 | setTimeout(() => (inThrottle = false), limit);
11 | }
12 | };
13 | };
14 |
--------------------------------------------------------------------------------
/website/src/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'remark-html';
2 | declare module 'remark-toc';
3 | declare module 'remark-footnotes';
4 | declare module 'remark-github';
5 | declare module 'remark-slug';
6 | declare module 'remark-autolink-headings';
7 | declare module 'remark-images';
8 | declare module 'rehype-shiki';
9 | declare module 'remark-rehype';
10 | declare module 'rehype-format';
11 | declare module 'rehype-img-size';
12 | declare module 'rehype-stringify';
13 | declare module 'classnames';
14 | declare module 'docsearch.js';
15 | declare module '@docsearch/react';
16 | declare module '@docsearch/react/modal';
17 | declare module 'prismjs';
18 | declare module 'async-sema';
19 | declare module 'node-fetch';
20 | declare module '@zeit/react-jsx-parser';
21 | declare module 'github-slugger';
22 | declare module '@reactions/component';
23 |
--------------------------------------------------------------------------------
/website/src/lib/blog/mdxUtils.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 |
4 | // POSTS_PATH is useful when you want to get the path to a specific file
5 | export const POSTS_PATH = path.join(process.cwd(), 'src', 'blog');
6 |
7 | // postFilePaths is the list of all mdx files inside the POSTS_PATH directory
8 | export const postFilePaths = fs
9 | .readdirSync(POSTS_PATH)
10 | // Only include md(x) files
11 | .filter(path => /\.mdx?$/.test(path));
12 |
--------------------------------------------------------------------------------
/website/src/lib/docs/config.ts:
--------------------------------------------------------------------------------
1 | // Default tag if the latest release was not found
2 | export const TAG = 'v2.4.0';
3 | // If a version different from the latest release is required, update TAG with the wanted
4 | // version and set this to `true`
5 | export const FORCE_TAG = true;
6 |
--------------------------------------------------------------------------------
/website/src/lib/docs/findRouteByPath.tsx:
--------------------------------------------------------------------------------
1 | import { removeFromLast } from './utils';
2 | import { RouteItem } from '../types';
3 |
4 | // @ts-ignore
5 | export function findRouteByPath(path: string, routes: RouteItem[]): RouteItem {
6 | // eslint-disable-next-line
7 | for (const route of routes) {
8 | if (route.path && removeFromLast(route.path, '.') === path) {
9 | return route;
10 | }
11 | const childPath = route.routes && findRouteByPath(path, route.routes);
12 | if (childPath) return childPath;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/website/src/lib/docs/md-loader.js:
--------------------------------------------------------------------------------
1 | const fm = require('gray-matter');
2 |
3 | // makes mdx in next.js suck less by injecting necessary exports so that
4 | // the docs are still readable on github
5 | // (Shamelessly stolen from Expo.io docs)
6 | // @see https://github.com/expo/expo/blob/master/docs/common/md-loader.js
7 | module.exports = async function (src) {
8 | const callback = this.async();
9 | const { content, data } = fm(src);
10 | const layout = data.layout || 'Docs';
11 | const code =
12 | `import { Layout${layout} } from 'components/Layout${layout}';
13 |
14 | export default function Wrapper ({ children, ...props }) { return (
15 | {children}
18 | );
19 | }
20 |
21 |
22 | ` + content;
23 |
24 | return callback(null, code);
25 | };
26 |
--------------------------------------------------------------------------------
/website/src/lib/docs/page.tsx:
--------------------------------------------------------------------------------
1 | import { getLatestTag } from '../github/api';
2 | import { getRawFileFromRepo } from '../github/raw';
3 | import { removeFromLast } from './utils';
4 | import { TAG, FORCE_TAG } from './config';
5 | import { RouteItem } from '../types';
6 | import util from 'util';
7 | import fs from 'fs';
8 | import { join } from 'path';
9 |
10 | const read = util.promisify(fs.readFile);
11 |
12 | export async function getCurrentTag(tag?: string) {
13 | if (tag) return tag;
14 | if (FORCE_TAG) return TAG;
15 | return getLatestTag();
16 | }
17 |
18 | const postsDirectory = join(process.cwd(), '../.');
19 |
20 | export async function getRawFileFromLocal(path: string) {
21 | const fullPath = join(postsDirectory, path);
22 | const fileContents = await read(fullPath, 'utf8');
23 | return fileContents;
24 | }
25 |
26 | export async function fetchLocalDocsManifest() {
27 | const routes = await getRawFileFromLocal('/docs/manifest.json');
28 | return JSON.parse(routes);
29 | }
30 |
31 | export async function fetchRemoteDocsManifest(tag: string) {
32 | const res = await getRawFileFromRepo('/docs/manifest.json', tag);
33 | return JSON.parse(res);
34 | }
35 |
36 | export function getPaths(
37 | nextRoutes: RouteItem[],
38 | carry: string[] = []
39 | ): string[] {
40 | nextRoutes.forEach(({ path, routes }) => {
41 | if (path) {
42 | carry.push(removeFromLast(path, '.'));
43 | } else if (routes) {
44 | getPaths(routes, carry);
45 | }
46 | });
47 |
48 | return carry;
49 | }
50 |
--------------------------------------------------------------------------------
/website/src/lib/docs/remark-paragraph-alerts.js:
--------------------------------------------------------------------------------
1 | const is = require('unist-util-is');
2 | const visit = require('unist-util-visit');
3 |
4 | const sigils = {
5 | '=>': 'success',
6 | '->': 'info',
7 | '~>': 'warning',
8 | '!>': 'danger',
9 | };
10 |
11 | module.exports = function paragraphCustomAlertsPlugin() {
12 | return function transformer(tree) {
13 | visit(tree, 'paragraph', (pNode, _, parent) => {
14 | visit(pNode, 'text', textNode => {
15 | Object.keys(sigils).forEach(symbol => {
16 | if (textNode.value.startsWith(`${symbol} `)) {
17 | // Remove the literal sigil symbol from string contents
18 | textNode.value = textNode.value.replace(`${symbol} `, '');
19 |
20 | // Wrap matched nodes with (containing proper attributes)
21 | parent.children = parent.children.map(node => {
22 | return is(pNode, node)
23 | ? {
24 | type: 'wrapper',
25 | children: [node],
26 | data: {
27 | hName: 'div',
28 | hProperties: {
29 | className: ['alert', `alert-${sigils[symbol]}`],
30 | role: 'alert',
31 | },
32 | },
33 | }
34 | : node;
35 | });
36 | }
37 | });
38 | });
39 | });
40 | };
41 | };
42 |
--------------------------------------------------------------------------------
/website/src/lib/docs/remark-plugins.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | require('remark-slug'),
3 | require('./remark-paragraph-alerts'),
4 | [
5 | require('remark-autolink-headings'),
6 | {
7 | behavior: 'append',
8 | linkProperties: {
9 | class: ['anchor'],
10 | title: 'Direct link to heading',
11 | },
12 | },
13 | ],
14 | [
15 | require('remark-toc'),
16 | {
17 | skip: 'Reference',
18 | maxDepth: 6,
19 | },
20 | ],
21 | require('remark-emoji'),
22 | require('remark-footnotes'),
23 | require('remark-images'),
24 | ];
25 |
--------------------------------------------------------------------------------
/website/src/lib/docs/utils.ts:
--------------------------------------------------------------------------------
1 | import { NextRouter } from 'next/router';
2 |
3 | export function getSlug({ slug }: { slug: string[] }) {
4 | if (!slug) {
5 | return { slug: '/docs/overview.md' };
6 | }
7 | if (slug[0] === 'tag') {
8 | return {
9 | tag: slug[1],
10 | slug: `/docs/${slug.slice(2).join('/')}`,
11 | };
12 | }
13 | return { slug: `/docs/${slug && slug.join('/')}` };
14 | }
15 |
16 | export function removeFromLast(path: string, key: string) {
17 | const i = path.lastIndexOf(key);
18 | return i === -1 ? path : path.substring(0, i);
19 | }
20 |
21 | export function addTagToSlug(slug: string, tag?: string) {
22 | return tag ? slug.replace('/docs', `/docs/tag/${tag}`) : slug;
23 | }
24 |
--------------------------------------------------------------------------------
/website/src/lib/fs-utils.tsx:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import { promisify } from 'util';
3 |
4 | export const readFile = promisify(fs.readFile);
5 | export const writeFile = promisify(fs.writeFile);
6 |
--------------------------------------------------------------------------------
/website/src/lib/get-route-context.tsx:
--------------------------------------------------------------------------------
1 | import { RouteItem, Routes } from './types';
2 |
3 | export interface RouteContext {
4 | parent?: RouteItem;
5 | prevRoute?: RouteItem;
6 | nextRoute?: RouteItem;
7 | route?: RouteItem;
8 | }
9 | /**
10 | * Returns the siblings of a specific route (that is the previous and next routes).
11 | */
12 | export function getRouteContext(
13 | _route: RouteItem,
14 | routes: RouteItem[],
15 | ctx: RouteContext = {}
16 | ) {
17 | if (!_route) {
18 | return ctx;
19 | }
20 |
21 | const { path } = _route;
22 | const { parent } = ctx;
23 |
24 | for (let i = 0; i < routes.length; i += 1) {
25 | const route = routes[i];
26 |
27 | if (route.routes) {
28 | ctx.parent = route;
29 | ctx = getRouteContext(_route, route.routes, ctx);
30 |
31 | // If the active route and the next route was found in nested routes, return it
32 | if (ctx.nextRoute) return ctx;
33 | }
34 | if (!route) continue;
35 | if (!route.path) continue;
36 |
37 | if (ctx.route) {
38 | ctx.nextRoute =
39 | parent && i === 0
40 | ? {
41 | ...route,
42 | title: `${_route.title} | ${parent.title}`,
43 | }
44 | : route;
45 |
46 | return ctx;
47 | }
48 |
49 | if (route && route.path === path) {
50 | ctx.route = {
51 | ..._route,
52 | title:
53 | parent && !parent.heading
54 | ? `${_route.title} | ${parent.title}`
55 | : _route.title,
56 | };
57 | // Continue the loop until we know the next route
58 | continue;
59 | }
60 |
61 | ctx.prevRoute =
62 | parent && !parent.heading && !routes[i + 1]?.path
63 | ? {
64 | ...route,
65 | title: `${route.title} | ${parent.title}`,
66 | }
67 | : route;
68 | }
69 |
70 | // The loop ended and the previous route was found, or nothing
71 | return ctx;
72 | }
73 |
--------------------------------------------------------------------------------
/website/src/lib/github/api.tsx:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import fetch from 'isomorphic-unfetch';
3 | import { readFile, writeFile } from '../fs-utils';
4 | import { GITHUB_API_URL, REPO_NAME } from './constants';
5 |
6 | const USE_CACHE = process.env.USE_CACHE === 'true';
7 | const TAG_CACHE_PATH = path.resolve('.github-latest-tag');
8 |
9 | export async function getLatestTag() {
10 | let cachedTag;
11 |
12 | if (USE_CACHE) {
13 | try {
14 | cachedTag = await readFile(TAG_CACHE_PATH, 'utf8');
15 | } catch (error) {
16 | // A cached file is not required
17 | }
18 | }
19 |
20 | if (!cachedTag) {
21 | const res = await fetch(
22 | `${GITHUB_API_URL}/repos/${REPO_NAME}/releases/latest`
23 | );
24 |
25 | if (res.ok) {
26 | const data = await res.json();
27 | const tag = data.tag_name;
28 |
29 | if (USE_CACHE) {
30 | try {
31 | await writeFile(TAG_CACHE_PATH, tag, 'utf8');
32 | } catch (error) {
33 | // A cached file is not required
34 | }
35 | }
36 |
37 | cachedTag = tag;
38 | }
39 | }
40 |
41 | return cachedTag;
42 | }
43 |
--------------------------------------------------------------------------------
/website/src/lib/github/constants.tsx:
--------------------------------------------------------------------------------
1 | export const GITHUB_URL = 'https://github.com';
2 |
3 | export const GITHUB_API_URL = 'https://api.github.com';
4 |
5 | export const RAW_GITHUB_URL = 'https://raw.githubusercontent.com';
6 |
7 | export const REPO_NAME = 'formik/formik';
8 |
--------------------------------------------------------------------------------
/website/src/lib/github/raw.tsx:
--------------------------------------------------------------------------------
1 | import fetch from 'isomorphic-unfetch';
2 | import { RAW_GITHUB_URL, REPO_NAME } from './constants';
3 |
4 | function getErrorText(res: Response) {
5 | try {
6 | return res.text();
7 | } catch (err) {
8 | return res.statusText;
9 | }
10 | }
11 |
12 | type GHError = Error & { status?: Response['status']; headers?: any };
13 |
14 | async function getError(res: Response): Promise
{
15 | const errorText = await getErrorText(res);
16 | const error: GHError = new Error(
17 | `GitHub raw download error (${res.status}): ${errorText}`
18 | );
19 |
20 | error.status = res.status;
21 | error.headers = (res.headers as any).raw();
22 |
23 | return error;
24 | }
25 |
26 | export async function getRawFileFromGitHub(path: string) {
27 | const res = await fetch(RAW_GITHUB_URL + path);
28 |
29 | if (res.ok) return res.text();
30 | throw await getError(res);
31 | }
32 |
33 | export async function getRawFileFromRepo(path: string, tag: string) {
34 | return getRawFileFromGitHub(`/${REPO_NAME}/${tag}${path}`);
35 | }
36 |
--------------------------------------------------------------------------------
/website/src/manifests/getManifest.ts:
--------------------------------------------------------------------------------
1 | import manifest from './manifest.json';
2 | import manifest130 from './manifest-1.3.0.json';
3 | import manifest214 from './manifest-2.1.4.json';
4 |
5 | // There is a better way to do this but whatever.
6 | const versions = {
7 | '2.1.4': manifest214,
8 | '1.5.8': manifest130,
9 | };
10 |
11 | export const versionList = Object.keys(versions);
12 |
13 | export const getManifest = (tag?: string) => {
14 | return tag ? versions[tag] : manifest;
15 | };
16 |
--------------------------------------------------------------------------------
/website/src/manifests/manifest-1.3.0.json:
--------------------------------------------------------------------------------
1 | {
2 | "routes": [
3 | {
4 | "title": "Documentation",
5 | "heading": true,
6 | "routes": [
7 | {
8 | "title": "Getting Started",
9 | "path": "/docs/overview.md"
10 | },
11 | {
12 | "title": "Tutorial",
13 | "path": "/docs/tutorial.md"
14 | },
15 | {
16 | "title": "Resources",
17 | "path": "/docs/resources.md"
18 | },
19 |
20 | {
21 | "title": "Guides",
22 | "open": true,
23 | "routes": [
24 | {
25 | "title": "Validation",
26 | "path": "/docs/guides/validation.md"
27 | },
28 | {
29 | "title": "Arrays",
30 | "path": "/docs/guides/arrays.md"
31 | },
32 | {
33 | "title": "TypeScript",
34 | "path": "/docs/guides/typescript.md"
35 | },
36 | {
37 | "title": "React Native",
38 | "path": "/docs/guides/react-native.md"
39 | },
40 | {
41 | "title": "Form Submission",
42 | "path": "/docs/guides/form-submission.md"
43 | }
44 | ]
45 | }
46 | ]
47 | },
48 | {
49 | "title": "API Reference",
50 | "heading": true,
51 | "routes": [
52 | {
53 | "title": "",
54 | "path": "/docs/api/formik.md"
55 | },
56 | {
57 | "title": "withFormik()",
58 | "path": "/docs/api/withFormik.md"
59 | },
60 | {
61 | "title": "",
62 | "path": "/docs/api/field.md"
63 | },
64 | {
65 | "title": "",
66 | "path": "/docs/api/fieldarray.md"
67 | },
68 | {
69 | "title": "",
70 | "path": "/docs/api/form.md"
71 | },
72 | {
73 | "title": "",
74 | "path": "/docs/api/errormessage.md"
75 | },
76 | {
77 | "title": "connect()",
78 | "path": "/docs/api/connect.md"
79 | },
80 | {
81 | "title": "",
82 | "path": "/docs/api/fastfield.md"
83 | }
84 | ]
85 | }
86 | ]
87 | }
88 |
--------------------------------------------------------------------------------
/website/src/manifests/manifest-2.1.4.json:
--------------------------------------------------------------------------------
1 | {
2 | "routes": [
3 | {
4 | "title": "Documentation",
5 | "heading": true,
6 | "routes": [
7 | {
8 | "title": "Getting Started",
9 | "path": "/docs/overview.md"
10 | },
11 | {
12 | "title": "Tutorial",
13 | "path": "/docs/tutorial.md"
14 | },
15 | {
16 | "title": "Resources",
17 | "path": "/docs/resources.md"
18 | },
19 | {
20 | "title": "3rd-Party Bindings",
21 | "path": "/docs/3rd-party-bindings.md"
22 | },
23 | {
24 | "title": "Migrating from v1.x to v2.x",
25 | "path": "/docs/migrating-v2.md"
26 | },
27 | {
28 | "title": "Guides",
29 | "open": true,
30 | "routes": [
31 | {
32 | "title": "Validation",
33 | "path": "/docs/guides/validation.md"
34 | },
35 | {
36 | "title": "Arrays",
37 | "path": "/docs/guides/arrays.md"
38 | },
39 | {
40 | "title": "TypeScript",
41 | "path": "/docs/guides/typescript.md"
42 | },
43 | {
44 | "title": "React Native",
45 | "path": "/docs/guides/react-native.md"
46 | },
47 | {
48 | "title": "Form Submission",
49 | "path": "/docs/guides/form-submission.md"
50 | }
51 | ]
52 | }
53 | ]
54 | },
55 | {
56 | "title": "API Reference",
57 | "heading": true,
58 | "routes": [
59 | {
60 | "title": "connect()",
61 | "path": "/docs/api/connect.md"
62 | },
63 | {
64 | "title": "",
65 | "path": "/docs/api/errormessage.md"
66 | },
67 | {
68 | "title": "",
69 | "path": "/docs/api/fastfield.md"
70 | },
71 | {
72 | "title": "",
73 | "path": "/docs/api/field.md"
74 | },
75 | {
76 | "title": "",
77 | "path": "/docs/api/fieldarray.md"
78 | },
79 | {
80 | "title": "",
81 | "path": "/docs/api/form.md"
82 | },
83 | {
84 | "title": "",
85 | "path": "/docs/api/formik.md"
86 | },
87 | {
88 | "title": "useField()",
89 | "path": "/docs/api/useField.md"
90 | },
91 | {
92 | "title": "useFormik()",
93 | "path": "/docs/api/useFormik.md"
94 | },
95 | {
96 | "title": "useFormikContext()",
97 | "path": "/docs/api/useFormikContext.md"
98 | },
99 | {
100 | "title": "withFormik()",
101 | "path": "/docs/api/withFormik.md"
102 | }
103 | ]
104 | }
105 | ]
106 | }
107 |
--------------------------------------------------------------------------------
/website/src/pages/_app.js:
--------------------------------------------------------------------------------
1 | import '@docsearch/css';
2 | import '../styles/index.css';
3 | import { Analytics } from '@vercel/analytics/react';
4 |
5 | function MyApp({ Component, pageProps }) {
6 | return (
7 | <>
8 |
9 |
10 | >
11 | );
12 | }
13 |
14 | export default MyApp;
15 |
--------------------------------------------------------------------------------
/website/src/pages/_document.js:
--------------------------------------------------------------------------------
1 | import Document, { Html, Head, Main, NextScript } from 'next/document';
2 |
3 | class MyDocument extends Document {
4 | render() {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | );
14 | }
15 | }
16 |
17 | export default MyDocument;
18 |
--------------------------------------------------------------------------------
/website/src/siteConfig.tsx:
--------------------------------------------------------------------------------
1 | // List of projects/orgs using your project for the users page.
2 |
3 | export const siteConfig = {
4 | editUrl: 'https://github.com/formik/formik/edit/main',
5 | copyright: `Copyright © ${new Date().getFullYear()} Jared Palmer. All Rights Reserved.`,
6 | repoUrl: 'https://github.com/formik/formik',
7 | discordUrl: 'https://discord.com/invite/pJSg287',
8 | twitterUrl: 'https://twitter.com/formiumhq',
9 | algolia: {
10 | appId: 'BH4D9OD16A',
11 | apiKey: '32fabc38a054677ee9b24e69d699fbd0',
12 | indexName: 'formik',
13 | // algoliaOptions: {
14 | // facetFilters: ['version:VERSION'],
15 | // },
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/website/tailwind.config.js:
--------------------------------------------------------------------------------
1 | // tailwind.config.js
2 |
3 | const defaultTheme = require('tailwindcss/defaultTheme');
4 |
5 | module.exports = {
6 | content: ['./src/**/*.ts', './src/**/*.tsx'],
7 | theme: {
8 | extends: {
9 | colors: {
10 | ...defaultTheme.colors,
11 | },
12 | screens: {
13 | sm: '640px',
14 | md: '768px',
15 | lg: '1024px',
16 | xl: '1400px',
17 | },
18 | rotate: {
19 | ...defaultTheme.rotate,
20 | '-30': '-30deg',
21 | },
22 | container: {
23 | padding: '1rem',
24 | },
25 | },
26 | },
27 |
28 | plugins: [require('@tailwindcss/forms')],
29 | };
30 |
--------------------------------------------------------------------------------
/website/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": true,
12 | "allowUnreachableCode": false,
13 | "forceConsistentCasingInFileNames": true,
14 | "noFallthroughCasesInSwitch": true,
15 | "noImplicitAny": true,
16 | "noImplicitReturns": true,
17 | "noImplicitThis": true,
18 | "removeComments": true,
19 | "strictNullChecks": true,
20 | "importHelpers": true,
21 | "suppressImplicitAnyIndexErrors": true,
22 | "noEmit": true,
23 | "esModuleInterop": true,
24 | "module": "esnext",
25 | "moduleResolution": "node",
26 | "resolveJsonModule": true,
27 | "isolatedModules": true,
28 | "jsx": "preserve",
29 | "baseUrl": "src",
30 | "incremental": true
31 | },
32 | "exclude": [
33 | "node_modules"
34 | ],
35 | "include": [
36 | "next-env.d.ts",
37 | "src"
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------