diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 0000000..d602546 --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,16 @@ +# Dependencies +/node_modules + +# Production +/build + +# Generated files +.docusaurus +.cache-loader + +# Misc +.DS_Store + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/website/.prettierignore b/website/.prettierignore new file mode 100644 index 0000000..afa4a5d --- /dev/null +++ b/website/.prettierignore @@ -0,0 +1,2 @@ +node_modules +.docusaurus diff --git a/website/.prettierrc.js b/website/.prettierrc.js new file mode 100644 index 0000000..5d6db3f --- /dev/null +++ b/website/.prettierrc.js @@ -0,0 +1,21 @@ +module.exports = { + proseWrap: 'always', + singleQuote: true, + trailingComma: 'es5', + importOrder: [ + // React + '^react$', + // Docusaurus + '^@docusaurus/(.*)$', + '^@site/(.*)$', + '^@theme/(.*)$', + // Internals + '^pages/(.*)$', + '^components/(.*)$', + '^utils/(.*)$', + '^styles/(.*)$', + '^[./]', + ], + importOrderSeparation: true, + importOrderSortSpecifiers: true, +}; diff --git a/website/.vscode/settings.json b/website/.vscode/settings.json new file mode 100644 index 0000000..143f018 --- /dev/null +++ b/website/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "search.exclude": { + "**/node_modules": true, + "**/.docusaurus": true + }, + "files.exclude": { + "**/node_modules": true, + "**/.docusaurus": true + }, + "editor.formatOnSave": true +} diff --git a/website/README.md b/website/README.md new file mode 100644 index 0000000..eb6129d --- /dev/null +++ b/website/README.md @@ -0,0 +1,37 @@ +# Website + +This website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern +static website generator. + +## Installation + +```console +yarn install +``` + +## Local Development + +```console +yarn start +``` + +This command starts a local development server and open up a browser window. +Most changes are reflected live without having to restart the server. + +## Build + +```console +yarn build +``` + +This command generates static content into the `build` directory and can be +served using any static contents hosting service. + +## Deployment + +```console +GIT_USER= USE_SSH=true yarn deploy +``` + +If you are using GitHub pages for hosting, this command is a convenient way to +build the website and push to the `gh-pages` branch. diff --git a/website/babel.config.js b/website/babel.config.js new file mode 100644 index 0000000..e00595d --- /dev/null +++ b/website/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: [require.resolve('@docusaurus/core/lib/babel/preset')], +}; diff --git a/website/blog/2022-01-31-why-react-matchez.md b/website/blog/2022-01-31-why-react-matchez.md new file mode 100644 index 0000000..708bb4d --- /dev/null +++ b/website/blog/2022-01-31-why-react-matchez.md @@ -0,0 +1,51 @@ +--- +slug: why-react-matchez +title: Why react-matchez? +author: Matheus Albuquerque +image: https://avatars.githubusercontent.com/u/2644563?v=4 +author_url: https://www.ythecombinator.space +author_image_url: https://avatars.githubusercontent.com/u/2644563?v=4 +--- + +Unfortunately, JavaScript and TypeScript weren’t designed with pattern matching +in mind. Fortunately, there are some great initiatives to address it, e.g.: + +- [Daggy](https://github.com/fantasyland/daggy) gives you the ability to define + a type and values of this type (sum types) that you can then pattern match to + declare an action depending on the value of this type. + +- [ts-pattern](https://github.com/gvergnaud/ts-pattern) gives you exhaustive + pattern matching with great type inference; being 100% tailored to bring + declarative code branching to JavaScript/TypeScript—by the way, this library + was some heavy inspiration to our API. + +- Last but not least, there's even a + [TC39 proposal](https://github.com/tc39/proposal-pattern-matching) from 2017 + to add pattern matching to the EcmaScript specification. + +Even though there are some interesting efforts in bringing pattern matching at a +language level, what we lack are React/JSX abstractions for this. + +React itself shifted our mindsets from imperatively manipulating the DOM to +declaratively expressing what the DOM should look like for a given state. So +it's only fair we take this even further with declarative render branching. + +Unfortunately, most of the existing alternatives (like +[react-pattern-matching](https://github.com/joshblack/react-pattern-matching) or +[react-pattern-match](https://github.com/tkh44/react-pattern-match)) lack +features, have poor/zero typing, and are unmaintained. + +What we have, though, are domain-specific matching/branching solutions, for +example: + +- [react-router](https://github.com/remix-run/react-router): Declarative + matching for routes +- [react-device-detect](https://github.com/duskload/react-device-detect): + Declarative matching for device type +- [react-matches](https://github.com/souporserious/react-matches): Declarative + matching for media queries + +> **react-matchez** then comes as a first-class-React, generic, strongly-typed, +> solution that you can use to build your own domain-specific matching +> solutions—and reduce drastically the `if`/`else`/`switch` boilerplate from +> your components. diff --git a/website/docs/CHANGELOG.md b/website/docs/CHANGELOG.md new file mode 100644 index 0000000..686bcfc --- /dev/null +++ b/website/docs/CHANGELOG.md @@ -0,0 +1,4 @@ +--- +id: changelog +title: Changelog +--- diff --git a/website/docs/advanced_usage.md b/website/docs/advanced_usage.md new file mode 100644 index 0000000..c7043f0 --- /dev/null +++ b/website/docs/advanced_usage.md @@ -0,0 +1,93 @@ +--- +id: advanced-usage +title: Advanced Usage +sidebar_label: Advanced Usage +--- + +## Using the original components + providing type parameters + +This approach is ideal when you have multiple `Match`—or `Switch`—occurrences +within your component. + +The downside is that you'll have to manually pass type parameters to all of your +cases (`With`, `When`, etc.). + +Here's an example: + +```tsx +import { Match, Otherwise, With } from 'react-matchez'; + +// First shape to be branched +type FirstShape = + | { + type: 'ok'; + data: { type: 'text'; content?: string } | { type: 'img'; src?: string }; + } + | { type: 'cancel'; error?: Error }; + +const firstResult: FirstShape = { type: 'ok', data: { type: 'img' } }; + +// Second shape to be branched +type SecondShape = + | { type: 'text'; content?: string } + | { type: 'img'; extension?: string }; + +const secondResult: SecondShape = { type: 'img', extension: 'jpg' }; + +// Your component +const Component = () => { + return ( + <> + + type="cancel">Cancel + type="ok" data={{ type: 'text' }}>OK - Text + type="ok" data={{ type: 'img' }}>OK - Image + + Fallback + + + + type="img">Image - Any + type="img" extension="jpg">Image - JPG + type="img" extension="png">Image - PNG + + Fallback + + + ); +}; +``` + +## Using with React.Suspense + React.lazy() + +This library plays really well with Suspense/lazy in scenarios of +feature/browser/OS detection. + +Combine the three of them and you'll have your users downloading **only the +actual component bundle** that matches your condition. + +Here's an example: + +```tsx +const supportsSensor = () => Boolean(window.AmbientLightSensor); + +const AmbientLight = React.lazy(() => import('./AmbientLight')); +const Fallback = React.lazy(() => import('./Fallback')); + +const MyComponent = () => { + const { Match, When, Otherwise } = usePatternMatch(); + + return ( + + + + + + + + + + + ); +}; +``` diff --git a/website/docs/api/_category_.yml b/website/docs/api/_category_.yml new file mode 100644 index 0000000..24a4602 --- /dev/null +++ b/website/docs/api/_category_.yml @@ -0,0 +1 @@ +label: "API" \ No newline at end of file diff --git a/website/docs/api/functions/Exact.md b/website/docs/api/functions/Exact.md new file mode 100644 index 0000000..59b5b1b --- /dev/null +++ b/website/docs/api/functions/Exact.md @@ -0,0 +1,29 @@ +--- +id: "Exact" +title: "Function: Exact" +sidebar_label: "Exact" +sidebar_position: 0 +custom_edit_url: null +--- + +▸ **Exact**<`Shape`\>(`props`): `Element` + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Shape` | extends `Object` | + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `props` | [`ExactProps`](../types/ExactProps)<`Shape`\> | + +#### Returns + +`Element` + +#### Defined in + +[src/components/Exact/Exact.tsx:13](https://github.com/ythecombinator/react-matchez/blob/7c6b6bd/src/components/Exact/Exact.tsx#L13) diff --git a/website/docs/api/functions/Match.md b/website/docs/api/functions/Match.md new file mode 100644 index 0000000..4bd63aa --- /dev/null +++ b/website/docs/api/functions/Match.md @@ -0,0 +1,29 @@ +--- +id: "Match" +title: "Function: Match" +sidebar_label: "Match" +sidebar_position: 0 +custom_edit_url: null +--- + +▸ **Match**<`Shape`\>(`props`): `FunctionComponentElement`<{}\> + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Shape` | extends `Object` | + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `props` | [`MatchProps`](../interfaces/MatchProps)<`Shape`\> | + +#### Returns + +`FunctionComponentElement`<{}\> + +#### Defined in + +[src/components/Match/Match.tsx:36](https://github.com/ythecombinator/react-matchez/blob/7c6b6bd/src/components/Match/Match.tsx#L36) diff --git a/website/docs/api/functions/Otherwise.md b/website/docs/api/functions/Otherwise.md new file mode 100644 index 0000000..69ed526 --- /dev/null +++ b/website/docs/api/functions/Otherwise.md @@ -0,0 +1,23 @@ +--- +id: "Otherwise" +title: "Function: Otherwise" +sidebar_label: "Otherwise" +sidebar_position: 0 +custom_edit_url: null +--- + +▸ **Otherwise**(`props`): `Element` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `props` | [`OtherwiseProps`](../interfaces/OtherwiseProps) | + +#### Returns + +`Element` + +#### Defined in + +[src/components/Otherwise/Otherwise.tsx:11](https://github.com/ythecombinator/react-matchez/blob/7c6b6bd/src/components/Otherwise/Otherwise.tsx#L11) diff --git a/website/docs/api/functions/Switch.md b/website/docs/api/functions/Switch.md new file mode 100644 index 0000000..9462cb2 --- /dev/null +++ b/website/docs/api/functions/Switch.md @@ -0,0 +1,29 @@ +--- +id: "Switch" +title: "Function: Switch" +sidebar_label: "Switch" +sidebar_position: 0 +custom_edit_url: null +--- + +▸ **Switch**<`Shape`\>(`props`): `Element` + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Shape` | extends `Object` | + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `props` | [`SwitchProps`](../types/SwitchProps)<`Shape`\> | + +#### Returns + +`Element` + +#### Defined in + +[src/components/Switch/Switch.tsx:10](https://github.com/ythecombinator/react-matchez/blob/7c6b6bd/src/components/Switch/Switch.tsx#L10) diff --git a/website/docs/api/functions/When.md b/website/docs/api/functions/When.md new file mode 100644 index 0000000..daee67e --- /dev/null +++ b/website/docs/api/functions/When.md @@ -0,0 +1,29 @@ +--- +id: "When" +title: "Function: When" +sidebar_label: "When" +sidebar_position: 0 +custom_edit_url: null +--- + +▸ **When**<`Shape`\>(`props`): `Element` + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Shape` | extends `Object` | + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `props` | [`WhenProps`](../interfaces/WhenProps)<`Shape`\> | + +#### Returns + +`Element` + +#### Defined in + +[src/components/When/When.tsx:15](https://github.com/ythecombinator/react-matchez/blob/7c6b6bd/src/components/When/When.tsx#L15) diff --git a/website/docs/api/functions/With.md b/website/docs/api/functions/With.md new file mode 100644 index 0000000..b911b4c --- /dev/null +++ b/website/docs/api/functions/With.md @@ -0,0 +1,29 @@ +--- +id: "With" +title: "Function: With" +sidebar_label: "With" +sidebar_position: 0 +custom_edit_url: null +--- + +▸ **With**<`Shape`\>(`props`): `Element` + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Shape` | extends `Object` | + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `props` | [`WithProps`](../types/WithProps)<`Shape`\> | + +#### Returns + +`Element` + +#### Defined in + +[src/components/With/With.tsx:13](https://github.com/ythecombinator/react-matchez/blob/7c6b6bd/src/components/With/With.tsx#L13) diff --git a/website/docs/api/functions/_category_.yml b/website/docs/api/functions/_category_.yml new file mode 100644 index 0000000..139c448 --- /dev/null +++ b/website/docs/api/functions/_category_.yml @@ -0,0 +1,2 @@ +label: "Functions" +position: 7 \ No newline at end of file diff --git a/website/docs/api/functions/getPatternMatch.md b/website/docs/api/functions/getPatternMatch.md new file mode 100644 index 0000000..34dba56 --- /dev/null +++ b/website/docs/api/functions/getPatternMatch.md @@ -0,0 +1,38 @@ +--- +id: "getPatternMatch" +title: "Function: getPatternMatch" +sidebar_label: "getPatternMatch" +sidebar_position: 0 +custom_edit_url: null +--- + +▸ `Const` **getPatternMatch**<`Shape`\>(`value?`): `Object` + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Shape` | extends `Object` | + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `value?` | `Shape` | + +#### Returns + +`Object` + +| Name | Type | +| :------ | :------ | +| `Exact` | `ComponentType`<[`ExactProps`](../types/ExactProps)<`Shape`\>\> | +| `Match` | `ComponentType`<[`MatchProps`](../interfaces/MatchProps)<`Shape`\>\> | +| `Otherwise` | (`props`: [`OtherwiseProps`](../interfaces/OtherwiseProps)) => `Element` | +| `Switch` | `ComponentType`<[`SwitchProps`](../types/SwitchProps)<`Shape`\>\> | +| `When` | `ComponentType`<[`WhenProps`](../interfaces/WhenProps)<`Shape`\>\> | +| `With` | `ComponentType`<[`WithProps`](../types/WithProps)<`Shape`\>\> | + +#### Defined in + +[src/utils/pattern-match.ts:17](https://github.com/ythecombinator/react-matchez/blob/7c6b6bd/src/utils/pattern-match.ts#L17) diff --git a/website/docs/api/functions/not.md b/website/docs/api/functions/not.md new file mode 100644 index 0000000..416c0f8 --- /dev/null +++ b/website/docs/api/functions/not.md @@ -0,0 +1,29 @@ +--- +id: "not" +title: "Function: not" +sidebar_label: "not" +sidebar_position: 0 +custom_edit_url: null +--- + +▸ `Const` **not**<`a`\>(`pattern`): `NotPattern`<`a`\> + +#### Type parameters + +| Name | +| :------ | +| `a` | + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `pattern` | `Pattern`<`a`\> | + +#### Returns + +`NotPattern`<`a`\> + +#### Defined in + +node_modules/ts-pattern/lib/guards.d.ts:3 diff --git a/website/docs/api/functions/usePatternMatch.md b/website/docs/api/functions/usePatternMatch.md new file mode 100644 index 0000000..e2abdb0 --- /dev/null +++ b/website/docs/api/functions/usePatternMatch.md @@ -0,0 +1,38 @@ +--- +id: "usePatternMatch" +title: "Function: usePatternMatch" +sidebar_label: "usePatternMatch" +sidebar_position: 0 +custom_edit_url: null +--- + +▸ `Const` **usePatternMatch**<`Shape`\>(`value?`): `Object` + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Shape` | extends `Object` | + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `value?` | `Shape` | + +#### Returns + +`Object` + +| Name | Type | +| :------ | :------ | +| `Exact` | `ComponentType`<[`ExactProps`](../types/ExactProps)<`Shape`\>\> | +| `Match` | `ComponentType`<[`MatchProps`](../interfaces/MatchProps)<`Shape`\>\> | +| `Otherwise` | (`props`: [`OtherwiseProps`](../interfaces/OtherwiseProps)) => `Element` | +| `Switch` | `ComponentType`<[`SwitchProps`](../types/SwitchProps)<`Shape`\>\> | +| `When` | `ComponentType`<[`WhenProps`](../interfaces/WhenProps)<`Shape`\>\> | +| `With` | `ComponentType`<[`WithProps`](../types/WithProps)<`Shape`\>\> | + +#### Defined in + +[src/utils/pattern-match.ts:44](https://github.com/ythecombinator/react-matchez/blob/7c6b6bd/src/utils/pattern-match.ts#L44) diff --git a/website/docs/api/index.md b/website/docs/api/index.md new file mode 100644 index 0000000..2b4f105 --- /dev/null +++ b/website/docs/api/index.md @@ -0,0 +1,20 @@ +--- +id: "index" +title: "API" +slug: "/api/" +sidebar_label: "Readme" +sidebar_position: 0 +custom_edit_url: null +--- + +--- +id: introduction_api +title: Test +sidebar_label: Test +--- + +In this section, you will find all the possible configurations and types related +to react-matchez. + +For a general overview of the library, it's a better idea to head to the +[docs](introduction.md) section. diff --git a/website/docs/api/interfaces/MatchProps.md b/website/docs/api/interfaces/MatchProps.md new file mode 100644 index 0000000..c6036a8 --- /dev/null +++ b/website/docs/api/interfaces/MatchProps.md @@ -0,0 +1,49 @@ +--- +id: "MatchProps" +title: "Interface: MatchProps" +sidebar_label: "MatchProps" +sidebar_position: 0 +custom_edit_url: null +--- + +## Type parameters + +| Name | Type | +| :------ | :------ | +| `Shape` | extends `Object` | + +## Properties + +### children + +• **children**: `_MatchChildren`<`Shape`\> + +The patterns the `value` prop should match. Can be represented as `With`, `When`, and `Otherwise`. + +#### Defined in + +[src/components/Match/Match.tsx:21](https://github.com/ythecombinator/react-matchez/blob/7c6b6bd/src/components/Match/Match.tsx#L21) + +___ + +### firstMatch + +• `Optional` **firstMatch**: `boolean` + +Indicates whether anything that matches should render or only the first match. + +#### Defined in + +[src/components/Match/Match.tsx:23](https://github.com/ythecombinator/react-matchez/blob/7c6b6bd/src/components/Match/Match.tsx#L23) + +___ + +### value + +• `Optional` **value**: `Shape` + +Entry point to create a pattern-matching expression. + +#### Defined in + +[src/components/Match/Match.tsx:19](https://github.com/ythecombinator/react-matchez/blob/7c6b6bd/src/components/Match/Match.tsx#L19) diff --git a/website/docs/api/interfaces/OtherwiseProps.md b/website/docs/api/interfaces/OtherwiseProps.md new file mode 100644 index 0000000..da8c4a9 --- /dev/null +++ b/website/docs/api/interfaces/OtherwiseProps.md @@ -0,0 +1,19 @@ +--- +id: "OtherwiseProps" +title: "Interface: OtherwiseProps" +sidebar_label: "OtherwiseProps" +sidebar_position: 0 +custom_edit_url: null +--- + +## Properties + +### children + +• **children**: `ReactNode` + +Any node to be rendered when nothing matches `With` and `When` siblings. + +#### Defined in + +[src/components/Otherwise/Otherwise.tsx:8](https://github.com/ythecombinator/react-matchez/blob/7c6b6bd/src/components/Otherwise/Otherwise.tsx#L8) diff --git a/website/docs/api/interfaces/WhenProps.md b/website/docs/api/interfaces/WhenProps.md new file mode 100644 index 0000000..b0250c0 --- /dev/null +++ b/website/docs/api/interfaces/WhenProps.md @@ -0,0 +1,49 @@ +--- +id: "WhenProps" +title: "Interface: WhenProps" +sidebar_label: "WhenProps" +sidebar_position: 0 +custom_edit_url: null +--- + +## Type parameters + +| Name | Type | +| :------ | :------ | +| `Shape` | extends `Object` | + +## Properties + +### children + +• **children**: `ReactNode` + +Any node to be rendered when the predicate matches. + +#### Defined in + +[src/components/When/When.tsx:8](https://github.com/ythecombinator/react-matchez/blob/7c6b6bd/src/components/When/When.tsx#L8) + +## Methods + +### predicate + +▸ **predicate**(`value?`): `boolean` + +Condition to be satisfied for the `children` to be rendered. +If the `value` prop was passed to its `Match` parent, then this predicate +will expose it as its first parameter. + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `value?` | `Shape` | + +#### Returns + +`boolean` + +#### Defined in + +[src/components/When/When.tsx:12](https://github.com/ythecombinator/react-matchez/blob/7c6b6bd/src/components/When/When.tsx#L12) diff --git a/website/docs/api/interfaces/_category_.yml b/website/docs/api/interfaces/_category_.yml new file mode 100644 index 0000000..43bec88 --- /dev/null +++ b/website/docs/api/interfaces/_category_.yml @@ -0,0 +1,2 @@ +label: "Interfaces" +position: 4 \ No newline at end of file diff --git a/website/docs/api/modules.md b/website/docs/api/modules.md new file mode 100644 index 0000000..2019c37 --- /dev/null +++ b/website/docs/api/modules.md @@ -0,0 +1,31 @@ +--- +id: "modules" +title: "API" +sidebar_label: "Exports" +sidebar_position: 0.5 +custom_edit_url: null +--- + +## Interfaces + +- [MatchProps](interfaces/MatchProps) +- [OtherwiseProps](interfaces/OtherwiseProps) +- [WhenProps](interfaces/WhenProps) + +## Type aliases + +- [ExactProps](types/ExactProps) +- [SwitchProps](types/SwitchProps) +- [WithProps](types/WithProps) + +## Functions + +- [Exact](functions/Exact) +- [Match](functions/Match) +- [Otherwise](functions/Otherwise) +- [Switch](functions/Switch) +- [When](functions/When) +- [With](functions/With) +- [getPatternMatch](functions/getPatternMatch) +- [not](functions/not) +- [usePatternMatch](functions/usePatternMatch) diff --git a/website/docs/api/types/ExactProps.md b/website/docs/api/types/ExactProps.md new file mode 100644 index 0000000..c66618b --- /dev/null +++ b/website/docs/api/types/ExactProps.md @@ -0,0 +1,19 @@ +--- +id: "ExactProps" +title: "Type alias: ExactProps" +sidebar_label: "ExactProps" +sidebar_position: 0 +custom_edit_url: null +--- + +Ƭ **ExactProps**<`Shape`\>: `DeepPatternUnion`<`Shape`, ``true``\> & { `children`: `ReactNode` } + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Shape` | extends `Object` | + +#### Defined in + +[src/components/Exact/Exact.tsx:7](https://github.com/ythecombinator/react-matchez/blob/7c6b6bd/src/components/Exact/Exact.tsx#L7) diff --git a/website/docs/api/types/SwitchProps.md b/website/docs/api/types/SwitchProps.md new file mode 100644 index 0000000..68cebbf --- /dev/null +++ b/website/docs/api/types/SwitchProps.md @@ -0,0 +1,19 @@ +--- +id: "SwitchProps" +title: "Type alias: SwitchProps" +sidebar_label: "SwitchProps" +sidebar_position: 0 +custom_edit_url: null +--- + +Ƭ **SwitchProps**<`Shape`\>: `Omit`<[`MatchProps`](../interfaces/MatchProps)<`Shape`\>, ``"firstMatch"``\> + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Shape` | extends `Object` | + +#### Defined in + +[src/components/Switch/Switch.tsx:5](https://github.com/ythecombinator/react-matchez/blob/7c6b6bd/src/components/Switch/Switch.tsx#L5) diff --git a/website/docs/api/types/WithProps.md b/website/docs/api/types/WithProps.md new file mode 100644 index 0000000..fa8365b --- /dev/null +++ b/website/docs/api/types/WithProps.md @@ -0,0 +1,19 @@ +--- +id: "WithProps" +title: "Type alias: WithProps" +sidebar_label: "WithProps" +sidebar_position: 0 +custom_edit_url: null +--- + +Ƭ **WithProps**<`Shape`\>: `DeepPatternUnion`<`Shape`, ``false``\> & { `children`: `ReactNode` } + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Shape` | extends `Object` | + +#### Defined in + +[src/components/With/With.tsx:7](https://github.com/ythecombinator/react-matchez/blob/7c6b6bd/src/components/With/With.tsx#L7) diff --git a/website/docs/api/types/_category_.yml b/website/docs/api/types/_category_.yml new file mode 100644 index 0000000..f80828f --- /dev/null +++ b/website/docs/api/types/_category_.yml @@ -0,0 +1,2 @@ +label: "Type aliases" +position: 5 \ No newline at end of file diff --git a/website/docs/introduction.md b/website/docs/introduction.md new file mode 100644 index 0000000..93cf6e3 --- /dev/null +++ b/website/docs/introduction.md @@ -0,0 +1,52 @@ +--- +id: introduction +title: Introduction +sidebar_label: Introduction +slug: / +--- + +

+ +

+ +## Overview + +- Typesafe, with helpful type inference +- Expressive API +- Supports predicates and not patterns for complex cases +- Tiny bundle footprint (`~7.4 kB`) + +## Status + +_react-matchez_ is in the early stages of development, and there might be a few +breaking changes here as I figure out how to: + +- address some pattern matching principles (e.g. exhaustiveness) +- improve the overall type-safety +- simplify its API +- reduce its bundle size + +## Documentation + +- [Overview](#overview) +- [Status](#status) +- [Documentation](#documentation) +- [Inspirations](#inspirations) + +## Inspirations + +This library has been heavily inspired by: + +- [ts-pattern](https://github.com/gvergnaud/ts-pattern): A great library by + [Gabriel Vergnaud](https://github.com/gvergnaud) that not only inspired the + core and the APIs of `react-matchez` but is also used by our internals. + +- [Daggy](https://github.com/fantasyland/daggy): A great library from the + [Fantasy Land](https://github.com/fantasyland) universe that brings sum types + to JavaScript. Using its `taggedSum` and `cata` methods, you can get really a + really interesting taste of pattern matching. + +- [ECMAScript pattern matching proposal](https://github.com/tc39/proposal-pattern-matching): + This proposal briefly covers what the JSX syntax would look like. Even though + `react-matchez` didn't quite use it as an inspiration, it was still an + interesting reference. diff --git a/website/docs/introduction_api.md b/website/docs/introduction_api.md new file mode 100644 index 0000000..4fedd8e --- /dev/null +++ b/website/docs/introduction_api.md @@ -0,0 +1,5 @@ +In this section, you will find all the possible configurations and types related +to react-matchez. + +For a general overview of the library, it's a better idea to head to the +[docs](introduction.md) section. diff --git a/website/docs/overview/components/Match.md b/website/docs/overview/components/Match.md new file mode 100644 index 0000000..7ae0558 --- /dev/null +++ b/website/docs/overview/components/Match.md @@ -0,0 +1,23 @@ +--- +id: match +title: Match +sidebar_label: Match +--- + +## Overview + +- This component is the wrapper for the matching cases. +- The only required prop is its `children`. +- Valid `children` for it are only: `With`, `When`, and `Otherwise`. +- If you plan on having `With` cases, then you need to pass a `value` prop. +- If you use the `otherwise` prop, then don't use the `Otherwise` component as + children. + +## Props + +| Name | Type | Default value | Description | +| --------------------- | ------------------------- | ------------- | --------------------------------------------------------------------------------------------------------- | +| value | Shape | | Entry point to create a pattern-matching expression. | +| children _(required)_ | \_MatchChildren<Shape> | | The patterns the `value` prop should match. Can be represented as `With`, `When`, and `Otherwise`. | +| otherwise | Element | | A default value to be used if nothing matches. If used, then the `Otherwise` component should be omitted. | +| firstMatch | boolean | | Indicates whether anything that matches should render or only the first match. | diff --git a/website/docs/overview/components/Otherwise.md b/website/docs/overview/components/Otherwise.md new file mode 100644 index 0000000..200bfd0 --- /dev/null +++ b/website/docs/overview/components/Otherwise.md @@ -0,0 +1,20 @@ +--- +id: otherwise +title: Otherwise +sidebar_label: Otherwise +--- + +### Overview + +- This component represents your default/fallback state; meaning its children + represent what's going to be rendered when nothing matches your previous + `When`/`With` assertions. +- Shouldn't be used if you've already passed the `otherwise` prop to its `Match` + parent. +- Shouldn't be used more than once within one `Match` parent. + +### Props + +| Name | Type | Default value | Description | +| --------------------- | --------- | ------------- | ------------------------------------------------------------------------ | +| children _(required)_ | ReactNode | | Any node to be rendered when nothing matches `With` and `When` siblings. | diff --git a/website/docs/overview/components/When.md b/website/docs/overview/components/When.md new file mode 100644 index 0000000..b9b5388 --- /dev/null +++ b/website/docs/overview/components/When.md @@ -0,0 +1,23 @@ +--- +id: when +title: When +sidebar_label: When +--- + +### Overview + +- This component represents a **condition** to be satisfied. +- Its `children` will be rendered if the `predicate` function returns a truthy + value. +- If you pass the `value` prop to its `Match` parent, it's going to be available + as the first parameter of your `predicate` function. +- It takes one type parameter: + - `Shape`: It's the shape of the `value` prop to its `Match` parent. Useful + for type inference. + +### Props + +| Name | Type | Default value | Description | +| ---------------------- | -------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| children _(required)_ | ReactNode | | Any node to be rendered when the predicate matches. | +| predicate _(required)_ | (value?: Shape) => boolean | | Condition to be satisfied for the `children` to be rendered. If the `value` prop was passed to its `Match` parent, then this predicate will expose it as its first parameter. | diff --git a/website/docs/overview/components/With.md b/website/docs/overview/components/With.md new file mode 100644 index 0000000..33fb7e2 --- /dev/null +++ b/website/docs/overview/components/With.md @@ -0,0 +1,23 @@ +--- +id: with +title: With +sidebar_label: With +--- + +## Overview + +- This component represents a **shape** to be matched. +- Its props are the pattern: the shape of value you expect for this branch. +- Its `children` will be rendered if the shape defined within its other props + match the `value` prop passed to its `Match` parent. +- It takes two type parameters: + - `Shape`: It's the shape of the `value` prop to its `Match` parent. Useful + for type inference. + - `Strict`: Indicates whether you want the compiler to make all the `Shape` + properties deeply `required` in order to match. + +## Props + +| Name | Type | Default value | Description | +| --------------------- | --------- | ------------- | --------------------------------------------------------------------------------------------- | +| children _(required)_ | ReactNode | | Any node to be rendered when its other props match the `value` defined on its `Match` parent. | diff --git a/website/docs/overview/utils/getPatternMatch.md b/website/docs/overview/utils/getPatternMatch.md new file mode 100644 index 0000000..601f550 --- /dev/null +++ b/website/docs/overview/utils/getPatternMatch.md @@ -0,0 +1,17 @@ +--- +id: get_pattern_match +title: getPatternMatch +sidebar_label: getPatternMatch +--- + +## Overview + +- This function is a typing helper and returns typed versions of the components + API—so that you don't have to manually type `With`, `When`, etc. each time you + use them. +- It takes either: + - Two **regular parameters**: `value` and `strict`. This alternative is ideal + when you want the `Shape` to be inferred. + - Two **type parameters**: `Shape` and `Strict`. This alternative is ideal + when you have an `interface`/`type` for your `Shape` and you just want to + pass them. diff --git a/website/docs/overview/utils/not.md b/website/docs/overview/utils/not.md new file mode 100644 index 0000000..2bedba7 --- /dev/null +++ b/website/docs/overview/utils/not.md @@ -0,0 +1,12 @@ +--- +id: not +title: not +sidebar_label: not +--- + +### Overview + +- It enables you to match on everything but a specific value. +- It takes a pattern and returns its opposite. +- It's a re-export of `ts-pattern`'s + [`not` function](https://github.com/gvergnaud/ts-pattern#notpattern). diff --git a/website/docs/overview/utils/usePatternMatch.md b/website/docs/overview/utils/usePatternMatch.md new file mode 100644 index 0000000..b141ac1 --- /dev/null +++ b/website/docs/overview/utils/usePatternMatch.md @@ -0,0 +1,9 @@ +--- +id: use_pattern_match +title: usePatternMatch +sidebar_label: usePatternMatch +--- + +## Overview + +Just like `getPatternMatch`, but as a React hook. diff --git a/website/docs/quickstart.md b/website/docs/quickstart.md new file mode 100644 index 0000000..5fcc75f --- /dev/null +++ b/website/docs/quickstart.md @@ -0,0 +1,49 @@ +--- +id: quickstart +title: Quickstart +sidebar_label: Quickstart +--- + +1. Add it to your project: + +```sh +npm install react-matchez +yarn add react-matchez +``` + +2. Define what is the shape of the object you wanna branch on: + +```typescript +export type Data = + | { type: 'text'; content?: string } + | { type: 'img'; src?: string }; + +export type Result = + | { type: 'ok'; data: Data } + | { type: 'cancel'; error?: Error }; + +export const result: Result = { type: 'ok', data: { type: 'img' } }; +``` + +3. Start using `getPatternMatch` (or `usePatternMatch`): + +```tsx +import { getPatternMatch } from 'react-matchez'; + +const { Match, With, Otherwise } = getPatternMatch(); + +const Component = () => { + return ( + + Cancel + + OK - Text + + + OK - Image + + Fallback + + ); +}; +``` diff --git a/website/docs/rationale.md b/website/docs/rationale.md new file mode 100644 index 0000000..a15d73c --- /dev/null +++ b/website/docs/rationale.md @@ -0,0 +1,61 @@ +--- +id: rationale +title: Rationale +sidebar_label: Rationale +--- + +## Why Pattern Matching + +Pattern matching consists of specifying patterns to which some data should +conform, then checking to see if it does, and de-constructing the data according +to those patterns. + +This is implemented out of the box in languages like Haskell, Rust, and Elixir +and has proven to be more powerful and less verbose than imperative alternatives +(`if`/`else`/`switch` statements), especially when branching on complex data +structures. + +## Why react-matchez + +Unfortunately, JavaScript and TypeScript weren’t designed with pattern matching +in mind. Fortunately, there are some great initiatives to address it, e.g.: + +- [Daggy](https://github.com/fantasyland/daggy) gives you the ability to define + a type and values of this type (sum types) that you can then pattern match to + declare an action depending on the value of this type. + +- [ts-pattern](https://github.com/gvergnaud/ts-pattern) gives you exhaustive + pattern matching with great type inference; being 100% tailored to bring + declarative code branching to JavaScript/TypeScript—by the way, this library + was some heavy inspiration to our API. + +- Last but not least, there's even a + [TC39 proposal](https://github.com/tc39/proposal-pattern-matching) from 2017 + to add pattern matching to the EcmaScript specification. + +Even though there are some interesting efforts in bringing pattern matching at a +language level, what we lack are React/JSX abstractions for this. + +React itself shifted our mindsets from imperatively manipulating the DOM to +declaratively expressing what the DOM should look like for a given state. So +it's only fair we take this even further with declarative render branching. + +Unfortunately, most of the existing alternatives (like +[react-pattern-matching](https://github.com/joshblack/react-pattern-matching) or +[react-pattern-match](https://github.com/tkh44/react-pattern-match)) lack +features, have poor/zero typing, and are unmaintained. + +What we have, though, are domain-specific matching/branching solutions, for +example: + +- [react-router](https://github.com/remix-run/react-router): Declarative + matching for routes +- [react-device-detect](https://github.com/duskload/react-device-detect): + Declarative matching for device type +- [react-matches](https://github.com/souporserious/react-matches): Declarative + matching for media queries + +> **react-matchez** then comes as a first-class-React, generic, strongly-typed, +> solution that you can use to build your own domain-specific matching +> solutions—and reduce drastically the `if`/`else`/`switch` boilerplate from +> your components. diff --git a/website/docs/roadmap.md b/website/docs/roadmap.md new file mode 100644 index 0000000..b33d7a9 --- /dev/null +++ b/website/docs/roadmap.md @@ -0,0 +1,16 @@ +--- +id: roadmap +title: Roadmap +sidebar_label: Roadmap +--- + +# Roadmap + +- [ ] Publish to npm +- [ ] Add unit tests +- [ ] Document recipes + common use cases +- [ ] Restrict the type of `Match`'s children to be only `With`, `When` and + `Otherwise` +- [ ] Support exhaustive matching +- [ ] Support wildcard patterns +- [ ] Support extraction of pieces of the input value (aka _selectors_) diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js new file mode 100644 index 0000000..e7549a4 --- /dev/null +++ b/website/docusaurus.config.js @@ -0,0 +1,113 @@ +const { resolve } = require('path'); + +module.exports = { + title: 'react-matchez', + tagline: 'Declarative, typed, pattern matching for React', + url: 'https://github.com/ythecombinator/react-match', + baseUrl: '/', + onBrokenLinks: 'throw', + onBrokenMarkdownLinks: 'warn', + favicon: 'img/favicon.ico', + organizationName: 'ythecombinator', + projectName: 'react-matchez', + plugins: [ + // Enables TailwindCSS + postcss + 'docusaurus-tailwindcss', + // Enables absolute imports + function () { + return { + name: 'docusaurus-module-path', + configureWebpack() { + return { + resolve: { + modules: [resolve(__dirname, 'src'), 'node_modules'], + }, + }; + }, + }; + }, + [ + 'docusaurus-plugin-typedoc', + { + entryPoints: ['../src/index.ts'], + tsconfig: '../tsconfig.json', + watch: process.env.TYPEDOC_WATCH, + }, + ], + ], + themeConfig: { + announcementBar: { + id: 'not-production-ready', + content: '🚨 react-matchez is not production-ready at this point.', + backgroundColor: '#f44336', + textColor: 'white', + isCloseable: true, + }, + colorMode: { + defaultMode: 'dark', + respectPrefersColorScheme: false, + }, + navbar: { + // title: "react-matchəz", + logo: { + alt: 'Logo', + src: 'img/logo.svg', + }, + items: [ + { + to: 'docs', + activeBaseRegex: 'docs(/)?$', + label: 'Docs', + position: 'right', + }, + { + to: 'docs/api', + label: 'API', + position: 'right', + }, + { + to: 'blog', + label: 'Blog', + position: 'right', + }, + { + to: 'sadsadassd', + label: 'Demo', + className: + 'btn mr-2 px-6 py-2 text-white font-bold uppercase bg-blue-600 hover:text-white hover:bg-blue-700', + position: 'right', + }, + { + href: 'https://github.com/ythecombinator/react-match', + className: 'mr-2 header-github-link', + position: 'right', + }, + ], + }, + footer: { + copyright: `MIT © ${new Date().getFullYear()} - Matheus Albuquerque`, + }, + prism: { + theme: require('prism-react-renderer/themes/vsDark'), + }, + }, + presets: [ + [ + '@docusaurus/preset-classic', + { + docs: { + sidebarPath: require.resolve('./sidebars.js'), + }, + blog: { + showReadingTime: true, + }, + theme: { + customCss: [require.resolve('./src/styles/custom.css')], + }, + gtag: { + trackingID: '', + }, + }, + ], + ], +}; diff --git a/website/package.json b/website/package.json new file mode 100644 index 0000000..153396a --- /dev/null +++ b/website/package.json @@ -0,0 +1,65 @@ +{ + "name": "website", + "version": "0.0.0", + "private": true, + "scripts": { + "docusaurus": "docusaurus", + "start": "yarn run build:changelog && TYPEDOC_WATCH=true docusaurus start", + "build": "yarn run build:changelog && TYPEDOC_WATCH=false docusaurus build", + "build:changelog": "echo '---' > ./docs/CHANGELOG.md && echo 'id: changelog' >> ./docs/CHANGELOG.md && echo 'title: Changelog' >> ./docs/CHANGELOG.md && echo '---' >> ./docs/CHANGELOG.md && cat ../CHANGELOG.md >> ./docs/CHANGELOG.md ", + "swizzle": "docusaurus swizzle --danger", + "deploy": "docusaurus deploy", + "serve": "docusaurus serve", + "clear": "docusaurus clear" + }, + "engines": { + "node": ">=14" + }, + "dependencies": { + "@docusaurus/core": "2.0.0-beta.14", + "@docusaurus/plugin-google-gtag": "2.0.0-beta.14", + "@docusaurus/plugin-sitemap": "2.0.0-beta.14", + "@docusaurus/preset-classic": "2.0.0-beta.14", + "@emotion/react": "^11.7.1", + "autoprefixer": "^10.4.2", + "clsx": "^1.1.1", + "docusaurus-plugin-typedoc": "^0.16.7", + "docusaurus-tailwindcss": "^0.1.0", + "postprocessing": "^6.22.3", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-syntax-highlighter": "^15.4.5", + "tailwindcss": "^3.0.15", + "tailwindcss-classnames": "^3.0.2", + "three": "^0.131.3" + }, + "devDependencies": { + "@docusaurus/module-type-aliases": "2.0.0-beta.14", + "@trivago/prettier-plugin-sort-imports": "^3.1.1", + "@tsconfig/docusaurus": "^1.0.4", + "@types/react": "^17.0.38", + "@types/react-helmet": "^6.1.5", + "@types/react-router-dom": "^5.3.3", + "@types/react-syntax-highlighter": "^13.5.2", + "postcss": "^8.4.5", + "postcss-import": "^14.0.2", + "postcss-loader": "^6.2.1", + "postcss-preset-env": "^7.2.3", + "prettier": "2.5.1", + "typedoc": "^0.22.11", + "typedoc-plugin-markdown": "^3.11.12", + "typescript": "^4.5.5" + }, + "browserslist": { + "production": [ + ">0.5%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/website/sidebars.js b/website/sidebars.js new file mode 100644 index 0000000..22f5d9d --- /dev/null +++ b/website/sidebars.js @@ -0,0 +1,26 @@ +module.exports = { + docsSidebar: [ + 'introduction', + 'quickstart', + 'advanced-usage', + 'rationale', + { + type: 'category', + label: 'Overview', + items: [ + { + type: 'autogenerated', + dirName: 'overview', + }, + ], + }, + 'roadmap', + 'changelog', + ], + apiSidebar: [ + { + type: 'autogenerated', + dirName: 'api', + }, + ], +}; diff --git a/website/src/components/AnimationShader/AnimationShader.tsx b/website/src/components/AnimationShader/AnimationShader.tsx new file mode 100644 index 0000000..5969caf --- /dev/null +++ b/website/src/components/AnimationShader/AnimationShader.tsx @@ -0,0 +1,102 @@ +import { EffectComposer } from 'postprocessing'; +import * as THREE from 'three'; + +import React, { FunctionComponent, useEffect, useRef } from 'react'; +import { useCallback } from 'react'; + +import { + SceneState, + TIME_DILATION, + initScene, + updateSceneSize, +} from 'utils/shader'; + +export const AnimationShader: FunctionComponent = () => { + const scrollRef = useRef(0); + const canvasRef = useRef(null); + const sceneStateRef = useRef(null); + const clockRef = useRef(new THREE.Clock()); + const requestRef = useRef(-1); + + const render = useCallback( + (meshes: THREE.Mesh[], composer: EffectComposer) => () => { + meshes.forEach((mesh) => { + mesh.material.uniforms.u_time.value = + clockRef.current.getElapsedTime() * TIME_DILATION; + mesh.position.y = mesh.initialPositionY + scrollRef.current / 140; + mesh.rotation.x = scrollRef.current / 2000; + }); + + composer.render(); + requestRef.current = requestAnimationFrame(render(meshes, composer)); + }, + [] + ); + + const handleResize = useCallback(() => { + if (sceneStateRef.current && canvasRef.current) { + const width = window.innerWidth, + height = window.innerHeight; + canvasRef.current.width = width; + updateSceneSize(sceneStateRef.current, width, height); + } + }, []); + + useEffect(() => { + const listener = () => { + if (typeof window !== 'undefined') + scrollRef.current = window?.scrollY ?? 0; + }; + + listener(); + + if (typeof window !== 'undefined') { + window.addEventListener('scroll', listener); + } + + return () => { + if (typeof window !== 'undefined') { + window.removeEventListener('scroll', listener); + } + }; + }, [window]); + + useEffect(() => { + if (!canvasRef.current) return; + + const width = canvasRef.current.offsetWidth, + height = canvasRef.current.offsetHeight; + + sceneStateRef.current = initScene(canvasRef.current, width, height); + + const { meshes, composer } = sceneStateRef.current; + render(meshes, composer)(); + + return () => cancelAnimationFrame(requestRef.current); + }, [canvasRef.current]); + + useEffect(() => { + handleResize(); + + if (typeof window !== 'undefined') + window.addEventListener('resize', handleResize); + return () => { + if (typeof window !== 'undefined') + window.removeEventListener('resize', handleResize); + }; + }, [window]); + + return ( + + ); +}; diff --git a/website/src/components/AnimationShader/index.ts b/website/src/components/AnimationShader/index.ts new file mode 100644 index 0000000..1212fbd --- /dev/null +++ b/website/src/components/AnimationShader/index.ts @@ -0,0 +1 @@ +export { AnimationShader as default } from './AnimationShader'; diff --git a/website/src/components/Button/Button.styles.ts b/website/src/components/Button/Button.styles.ts new file mode 100644 index 0000000..f0839bc --- /dev/null +++ b/website/src/components/Button/Button.styles.ts @@ -0,0 +1,35 @@ +import clsx from 'clsx'; +import { + TArg, + backgroundColor, + classnames, + fontWeight, + margin, + padding, + textColor, + textTransform, + width, +} from 'tailwindcss-classnames'; + +const baseButton = clsx( + 'btn', + classnames( + margin('mb-4', 'sm:mb-0', 'sm:ml-4'), + padding('px-12', 'py-4'), + width('w-full', 'sm:w-auto'), + fontWeight('font-bold'), + textTransform('uppercase') + ) +) as TArg; + +export const primaryButton = classnames( + baseButton, + textColor('text-white', 'hover:text-white'), + backgroundColor('bg-blue-600', 'hover:bg-blue-700') +); + +export const secondaryButton = classnames( + baseButton, + textColor('text-white', 'hover:text-white'), + backgroundColor('bg-black', 'hover:bg-gray-900') +); diff --git a/website/src/components/Button/Button.tsx b/website/src/components/Button/Button.tsx new file mode 100644 index 0000000..bad10f8 --- /dev/null +++ b/website/src/components/Button/Button.tsx @@ -0,0 +1,26 @@ +import React, { AnchorHTMLAttributes, FunctionComponent } from 'react'; + +import * as styles from './Button.styles'; + +export interface ButtonProps extends AnchorHTMLAttributes { + variant?: 'primary' | 'secondary'; +} + +const classNameVariantMapping: Record< + Required['variant'], + string +> = { + primary: styles.primaryButton, + secondary: styles.secondaryButton, +}; + +export const Button: FunctionComponent = (props) => { + const { variant = 'primary', children, ...otherProps } = props; + return ( + + {children} + + ); +}; + +export default Button; diff --git a/website/src/components/Button/index.ts b/website/src/components/Button/index.ts new file mode 100644 index 0000000..ed4d2ca --- /dev/null +++ b/website/src/components/Button/index.ts @@ -0,0 +1,2 @@ +export { Button as default } from './Button'; +export type { ButtonProps } from './Button'; diff --git a/website/src/components/Card/Card.styles.ts b/website/src/components/Card/Card.styles.ts new file mode 100644 index 0000000..5d306fa --- /dev/null +++ b/website/src/components/Card/Card.styles.ts @@ -0,0 +1,29 @@ +import { + alignItems, + backgroundColor, + borderRadius, + boxShadow, + classnames, + display, + flexDirection, + fontSize, + padding, + position, + textAlign, +} from 'tailwindcss-classnames'; + +export const container = classnames( + position('relative'), + display('flex'), + flexDirection('flex-col'), + alignItems('items-center'), + padding('p-6'), + backgroundColor('bg-white', 'dark:bg-gray-900'), + borderRadius('rounded'), + boxShadow('shadow-2xl') +); + +export const paragraph = classnames( + fontSize('text-sm'), + textAlign('text-center') +); diff --git a/website/src/components/Card/Card.tsx b/website/src/components/Card/Card.tsx new file mode 100644 index 0000000..e43039f --- /dev/null +++ b/website/src/components/Card/Card.tsx @@ -0,0 +1,23 @@ +import React, { FunctionComponent } from 'react'; + +import Typography from 'components/Typography'; + +import * as styles from './Card.styles'; + +export interface CardProps { + title: string; + description: string; +} + +export const Card: FunctionComponent = (props) => { + const { title, description } = props; + + return ( +
+
+ {title} + {description} +
+
+ ); +}; diff --git a/website/src/components/Card/index.ts b/website/src/components/Card/index.ts new file mode 100644 index 0000000..dd1ae25 --- /dev/null +++ b/website/src/components/Card/index.ts @@ -0,0 +1,2 @@ +export { Card as default } from './Card'; +export type { CardProps } from './Card'; diff --git a/website/src/components/Separator/Separator.styles.ts b/website/src/components/Separator/Separator.styles.ts new file mode 100644 index 0000000..811e643 --- /dev/null +++ b/website/src/components/Separator/Separator.styles.ts @@ -0,0 +1,24 @@ +import clsx from 'clsx'; +import { + backgroundColor, + classnames, + height, + inset, + margin, + padding, + translate, + width, +} from 'tailwindcss-classnames'; + +export const container = clsx( + 'transform', + classnames( + margin('mt-24', 'mb-24', 'm-auto'), + backgroundColor('bg-gray-200'), + translate('translate-y-1/2'), + inset('left-0', 'right-0', 'bottom-0'), + height('h-20'), + padding('p-px'), + width('w-px') + ) +); diff --git a/website/src/components/Separator/Separator.tsx b/website/src/components/Separator/Separator.tsx new file mode 100644 index 0000000..3248c87 --- /dev/null +++ b/website/src/components/Separator/Separator.tsx @@ -0,0 +1,7 @@ +import React, { FunctionComponent } from 'react'; + +import * as styles from './Separator.styles'; + +export const Separator: FunctionComponent = () => ( +
+); diff --git a/website/src/components/Separator/index.ts b/website/src/components/Separator/index.ts new file mode 100644 index 0000000..da8f76f --- /dev/null +++ b/website/src/components/Separator/index.ts @@ -0,0 +1 @@ +export { Separator as default } from './Separator'; diff --git a/website/src/components/SyntaxHighlighter/SyntaxHighlighter.styles.ts b/website/src/components/SyntaxHighlighter/SyntaxHighlighter.styles.ts new file mode 100644 index 0000000..16c9144 --- /dev/null +++ b/website/src/components/SyntaxHighlighter/SyntaxHighlighter.styles.ts @@ -0,0 +1,3 @@ +import { boxShadow, classnames } from 'tailwindcss-classnames'; + +export const container = classnames(boxShadow('shadow-2xl')); diff --git a/website/src/components/SyntaxHighlighter/SyntaxHighlighter.tsx b/website/src/components/SyntaxHighlighter/SyntaxHighlighter.tsx new file mode 100644 index 0000000..c8319d6 --- /dev/null +++ b/website/src/components/SyntaxHighlighter/SyntaxHighlighter.tsx @@ -0,0 +1,37 @@ +import { PrismLight as PrismSyntaxHighlighter } from 'react-syntax-highlighter'; +import tsx from 'react-syntax-highlighter/dist/cjs/languages/prism/tsx'; +import { dracula, prism } from 'react-syntax-highlighter/dist/esm/styles/prism'; + +import React, { FunctionComponent } from 'react'; + +import useThemeContext from '@theme/hooks/useThemeContext'; + +import * as styles from './SyntaxHighlighter.styles'; + +export interface SyntaxHighlighterProps { + children: string; +} + +PrismSyntaxHighlighter.registerLanguage('tsx', tsx); + +export const SyntaxHighlighter: FunctionComponent = ( + props +) => { + const { children } = props; + const { isDarkTheme } = useThemeContext(); + + return ( + + {children} + + ); +}; + +export default SyntaxHighlighter; diff --git a/website/src/components/SyntaxHighlighter/index.ts b/website/src/components/SyntaxHighlighter/index.ts new file mode 100644 index 0000000..a129516 --- /dev/null +++ b/website/src/components/SyntaxHighlighter/index.ts @@ -0,0 +1,2 @@ +export { SyntaxHighlighter as default } from './SyntaxHighlighter'; +export type { SyntaxHighlighterProps } from './SyntaxHighlighter'; diff --git a/website/src/components/Typography/Typography.styles.ts b/website/src/components/Typography/Typography.styles.ts new file mode 100644 index 0000000..ca0f11e --- /dev/null +++ b/website/src/components/Typography/Typography.styles.ts @@ -0,0 +1,27 @@ +import clsx from 'clsx'; +import { + classnames, + fontSize, + fontWeight, + letterSpacing, + lineHeight, + margin, + textColor, +} from 'tailwindcss-classnames'; + +export const p = classnames( + textColor('text-gray-600', 'dark:text-gray-200'), + fontSize('text-xl') +); + +export const h2 = clsx('h2', classnames(margin('m-0'))); + +export const h3 = clsx('h3', classnames(margin('m-0'))); + +export const h4 = classnames( + margin('mb-1'), + fontSize('text-xl'), + fontWeight('font-bold'), + letterSpacing('tracking-tight'), + lineHeight('leading-snug') +); diff --git a/website/src/components/Typography/Typography.tsx b/website/src/components/Typography/Typography.tsx new file mode 100644 index 0000000..eaea94b --- /dev/null +++ b/website/src/components/Typography/Typography.tsx @@ -0,0 +1,86 @@ +import clsx from 'clsx'; + +import React, { FunctionComponent, HTMLAttributes } from 'react'; + +import * as styles from './Typography.styles'; + +// ---------------------------------------------------------------------- +// +// p +// +// ---------------------------------------------------------------------- + +export interface ParagraphProps extends HTMLAttributes {} + +const P: FunctionComponent = (props) => { + const { children, className, ...otherProps } = props; + + return ( +

+ {children} +

+ ); +}; + +// ---------------------------------------------------------------------- +// +// h +// +// ---------------------------------------------------------------------- + +export interface HeadingProps extends HTMLAttributes {} + +// ---------------------------------------------------------------------- +// +// h2 +// +// ---------------------------------------------------------------------- + +const H2: FunctionComponent = (props) => { + const { children, className, ...otherProps } = props; + + return ( +

+ {children} +

+ ); +}; + +// ---------------------------------------------------------------------- +// +// h3 +// +// ---------------------------------------------------------------------- + +const H3: FunctionComponent = (props) => { + const { children, className, ...otherProps } = props; + + return ( +

+ {children} +

+ ); +}; + +// ---------------------------------------------------------------------- +// +// h4 +// +// ---------------------------------------------------------------------- + +const H4: FunctionComponent = (props) => { + const { children, className, ...otherProps } = props; + + return ( +

+ {children} +

+ ); +}; + +export const Typography = { + p: P, + h2: H2, + h3: H3, + h4: H4, +}; diff --git a/website/src/components/Typography/index.ts b/website/src/components/Typography/index.ts new file mode 100644 index 0000000..352247c --- /dev/null +++ b/website/src/components/Typography/index.ts @@ -0,0 +1 @@ +export { Typography as default } from './Typography'; diff --git a/website/src/pages/Home/Home.styles.ts b/website/src/pages/Home/Home.styles.ts new file mode 100644 index 0000000..5824d1f --- /dev/null +++ b/website/src/pages/Home/Home.styles.ts @@ -0,0 +1,18 @@ +import { + classnames, + display, + flexDirection, + flexGrow, + minHeight, + overflow, +} from 'tailwindcss-classnames'; + +export const main = classnames(flexGrow('grow')); + +export const container = classnames( + display('flex'), + flexGrow('grow'), + flexDirection('flex-col'), + minHeight('min-h-screen'), + overflow('overflow-hidden') +); diff --git a/website/src/pages/Home/Home.tsx b/website/src/pages/Home/Home.tsx new file mode 100644 index 0000000..abccf0e --- /dev/null +++ b/website/src/pages/Home/Home.tsx @@ -0,0 +1,47 @@ +import React, { FunctionComponent, useEffect } from 'react'; + +import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; + +import Layout from '@theme/Layout'; + +import Separator from 'components/Separator'; + +import { updateDarkModeClass, useDarkModeObserver } from 'utils/dark-mode'; + +import 'styles/tailwind.css'; + +import * as styles from './Home.styles'; +import Footer from './_Footer'; +import Hero from './_Hero'; +import Introduction from './_Introduction'; +import PrimaryFeatures from './_PrimaryFeatures'; +import SecondaryFeatures from './_SecondaryFeatures'; + +export const Home: FunctionComponent = () => { + useEffect(() => { + if (ExecutionEnvironment.canUseDOM) { + updateDarkModeClass(); + } + }, [ExecutionEnvironment.canUseDOM]); + + useDarkModeObserver(); + + return ( + +
+
+ + + + + + + +
+
+
+
+ ); +}; + +export default Home; diff --git a/website/src/pages/Home/_Footer/_Footer.styles.ts b/website/src/pages/Home/_Footer/_Footer.styles.ts new file mode 100644 index 0000000..865e813 --- /dev/null +++ b/website/src/pages/Home/_Footer/_Footer.styles.ts @@ -0,0 +1,33 @@ +import { + classnames, + display, + justifyContent, + margin, + maxWidth, + padding, + position, + textAlign, +} from 'tailwindcss-classnames'; + +export const buttonsContainer = classnames( + display('flex'), + justifyContent('justify-center') +); + +export const textContainer = classnames( + textAlign('text-center'), + padding('pb-12', 'md:pb-16'), + margin('mx-auto'), + maxWidth('max-w-3xl') +); + +export const innerContainer = classnames(padding('py-12', 'md:py-20')); + +export const outerContainer = classnames( + textAlign('text-center'), + padding('px-4', 'sm:px-6'), + margin('mx-auto', 'mb-16'), + maxWidth('max-w-6xl') +); + +export const section = classnames(position('relative')); diff --git a/website/src/pages/Home/_Footer/_Footer.tsx b/website/src/pages/Home/_Footer/_Footer.tsx new file mode 100644 index 0000000..2cd0435 --- /dev/null +++ b/website/src/pages/Home/_Footer/_Footer.tsx @@ -0,0 +1,41 @@ +import React, { FunctionComponent } from 'react'; + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +import Button from 'components/Button'; +import Typography from 'components/Typography'; + +import * as styles from './_Footer.styles'; + +export const Footer: FunctionComponent = () => { + return ( +
+
+
+
+ Write better and safer code + + Pattern matching lets you express complex conditions in a single, + compact expression. Your code becomes shorter and more readable. + Compiler-backed exactness checking ensures you thought of every + possible property. + +
+ +
+ + +
+
+
+
+ ); +}; diff --git a/website/src/pages/Home/_Footer/index.ts b/website/src/pages/Home/_Footer/index.ts new file mode 100644 index 0000000..f5e7276 --- /dev/null +++ b/website/src/pages/Home/_Footer/index.ts @@ -0,0 +1 @@ +export { Footer as default } from './_Footer'; diff --git a/website/src/pages/Home/_Hero/_Hero.styles.ts b/website/src/pages/Home/_Hero/_Hero.styles.ts new file mode 100644 index 0000000..5bb49b8 --- /dev/null +++ b/website/src/pages/Home/_Hero/_Hero.styles.ts @@ -0,0 +1,89 @@ +import { + backgroundClip, + borderColor, + borderRadius, + boxShadow, + classnames, + display, + fontSize, + fontWeight, + gridTemplateColumns, + justifyContent, + letterSpacing, + lineHeight, + margin, + maxWidth, + padding, + position, + textAlign, + textColor, +} from 'tailwindcss-classnames'; + +// ---------------------------------------------------------------------- +// +// title +// +// ---------------------------------------------------------------------- + +export const titleOuterContainer = classnames( + padding('sm:px-6', 'px-16'), + margin('my-4', 'xl:my-16') +); + +export const titleInnerContainer = classnames( + textAlign('text-center', 'xl:text-right') +); + +export const titleHeading = classnames( + display('block'), + fontSize('text-5xl', 'md:text-6xl'), + fontWeight('font-extrabold'), + letterSpacing('tracking-tight'), + lineHeight('leading-none') +); + +export const titleButtonsContainer = classnames( + margin('mt-8', 'mb-16'), + display('flex'), + justifyContent('justify-center', 'xl:justify-end') +); + +export const titleSpan = classnames(display('block')); + +export const titleSpanStyled = classnames( + fontWeight('font-extrabold'), + fontSize('text-8xl', 'md:text-9xl'), + textColor('text-transparent'), + backgroundClip('bg-clip-text') +); + +// ---------------------------------------------------------------------- +// +// video +// +// ---------------------------------------------------------------------- + +export const videoContainer = classnames( + padding('px-8', 'sm:px-16', 'md:px-24', 'xl:px-4'), + maxWidth('xl:max-w-3xl') +); + +export const video = classnames( + borderRadius('rounded-xl'), + borderColor('border-gray-200'), + boxShadow('shadow-2xl') +); + +// ---------------------------------------------------------------------- +// +// others +// +// ---------------------------------------------------------------------- + +export const section = classnames(position('relative')); + +export const container = classnames( + display('xl:grid'), + gridTemplateColumns('xl:grid-cols-2'), + margin('xl:my-24', 'md:my-16', 'my-8') +); diff --git a/website/src/pages/Home/_Hero/_Hero.tsx b/website/src/pages/Home/_Hero/_Hero.tsx new file mode 100644 index 0000000..ac19977 --- /dev/null +++ b/website/src/pages/Home/_Hero/_Hero.tsx @@ -0,0 +1,88 @@ +import React, { FunctionComponent, Suspense, lazy } from 'react'; + +import BrowserOnly from '@docusaurus/BrowserOnly'; +import useBaseUrl from '@docusaurus/useBaseUrl'; + +import heroVideo from '@site/static/img/hero.mp4'; + +import Button from 'components/Button'; + +import * as styles from './_Hero.styles'; + +const LazyAnimationShader = lazy(() => import('components/AnimationShader')); + +export const Hero: FunctionComponent = () => { + const fallback = ( + + ); + + return ( +
+ + {() => ( + + + + )} + + +
+ {/* Title */} +
+
+

+ Declarative, typed, + + + pattern matching + + + for React +

+
+ + +
+
+
+ {/* Video */} +
+ +
+
+
+ ); +}; diff --git a/website/src/pages/Home/_Hero/index.ts b/website/src/pages/Home/_Hero/index.ts new file mode 100644 index 0000000..4e6a7c5 --- /dev/null +++ b/website/src/pages/Home/_Hero/index.ts @@ -0,0 +1 @@ +export { Hero as default } from './_Hero'; diff --git a/website/src/pages/Home/_Introduction/_Introduction.styles.ts b/website/src/pages/Home/_Introduction/_Introduction.styles.ts new file mode 100644 index 0000000..c9740b7 --- /dev/null +++ b/website/src/pages/Home/_Introduction/_Introduction.styles.ts @@ -0,0 +1,55 @@ +import { + alignItems, + classnames, + display, + flexDirection, + flexWrap, + gap, + gridTemplateColumns, + justifyContent, + margin, + maxWidth, + padding, + position, + space, + textAlign, +} from 'tailwindcss-classnames'; + +export const logoContainer = classnames( + display('flex'), + flexDirection('flex-row'), + space('space-x-2', 'md:space-x-8'), + justifyContent('justify-center'), + alignItems('items-center') +); + +export const iframeContainer = classnames( + display('flex'), + justifyContent('justify-center'), + alignItems('items-center'), + flexWrap('flex-wrap'), + margin('mt-6') +); + +export const textContainer = classnames( + textAlign('text-center'), + padding('pb-36', 'md:pb-40'), + margin('mx-auto'), + maxWidth('max-w-3xl') +); + +export const innerContainer = classnames( + position('relative'), + display('flex'), + justifyContent('justify-center'), + margin('mb-12') +); + +export const outerContainer = classnames( + position('relative'), + padding('px-4', 'sm:px-6'), + margin('mx-auto'), + maxWidth('max-w-6xl') +); + +export const section = classnames(position('relative')); diff --git a/website/src/pages/Home/_Introduction/_Introduction.tsx b/website/src/pages/Home/_Introduction/_Introduction.tsx new file mode 100644 index 0000000..f8341ef --- /dev/null +++ b/website/src/pages/Home/_Introduction/_Introduction.tsx @@ -0,0 +1,40 @@ +import React, { FunctionComponent } from 'react'; + +import Logo from '@site/static/img/logo.svg'; + +import Typography from 'components/Typography'; + +import * as styles from './_Introduction.styles'; + +export const Introduction: FunctionComponent = () => { + return ( +
+
+
+
+ +
+
+ +
+ No more if/else/switch + + react-matchez is a first-class-React, generic,{' '} + strongly-typed, solution that you can use to build your own + domain-specific matching solutions—and reduce drastically the{' '} + if/else/switch boilerplate from your components. + +
+ +
+
+
+
+ ); +}; diff --git a/website/src/pages/Home/_Introduction/index.ts b/website/src/pages/Home/_Introduction/index.ts new file mode 100644 index 0000000..dc32df2 --- /dev/null +++ b/website/src/pages/Home/_Introduction/index.ts @@ -0,0 +1 @@ +export { Introduction as default } from './_Introduction'; diff --git a/website/src/pages/Home/_PrimaryFeature/_PrimaryFeature.styles.ts b/website/src/pages/Home/_PrimaryFeature/_PrimaryFeature.styles.ts new file mode 100644 index 0000000..59efe57 --- /dev/null +++ b/website/src/pages/Home/_PrimaryFeature/_PrimaryFeature.styles.ts @@ -0,0 +1,82 @@ +import clsx from 'clsx'; +import { + alignItems, + borderColor, + borderRadius, + boxShadow, + classnames, + display, + flexDirection, + fontFamily, + gap, + gridColumn, + gridTemplateColumns, + margin, + maxWidth, + padding, + position, + width, +} from 'tailwindcss-classnames'; + +export const outerContainer = classnames( + position('relative'), + maxWidth('max-w-6xl'), + margin('mx-auto'), + padding('px-4', 'sm:px-6') +); + +export const innerContainer = classnames(padding('pt-12', 'md:pt-20')); + +export const sectionsContainer = classnames( + display('md:grid'), + gridTemplateColumns('md:grid-cols-12'), + gap('md:gap-6') +); + +export const titleContainer = classnames( + display('flex'), + alignItems('items-center'), + margin('mb-3') +); + +export const paragraphContainer = classnames( + padding('xl:pr-16', 'lg:pr-12', 'md:pr-4'), + margin('mb-8') +); + +export const firstSection = classnames( + display('flex'), + alignItems('items-center'), + maxWidth('max-w-xl', 'md:max-w-none'), + width('md:w-full'), + margin('mx-auto', 'md:mt-6'), + gridColumn('md:col-span-7', 'lg:col-span-6') +); + +export const secondSection = classnames( + display('flex'), + alignItems('items-center'), + maxWidth('max-w-xl', 'md:max-w-none'), + width('md:w-full'), + margin('mx-auto', 'mb-8', 'md:mb-0'), + gridColumn('md:col-span-5', 'lg:col-span-6') +); + +// Specific types + +export const featureVideoContainer = classnames(padding('p-4')); + +export const featureVideo = classnames( + borderRadius('rounded-xl'), + boxShadow('shadow-2xl'), + borderColor('border-gray-200') +); + +export const featureCodeBlockContainer = clsx( + 'custom-code-block', + classnames( + position('relative'), + flexDirection('flex-col'), + fontFamily('font-mono') + ) +); diff --git a/website/src/pages/Home/_PrimaryFeature/_PrimaryFeature.tsx b/website/src/pages/Home/_PrimaryFeature/_PrimaryFeature.tsx new file mode 100644 index 0000000..13acf56 --- /dev/null +++ b/website/src/pages/Home/_PrimaryFeature/_PrimaryFeature.tsx @@ -0,0 +1,99 @@ +import React, { FunctionComponent } from 'react'; + +import SyntaxHighlighter from 'components/SyntaxHighlighter'; +import Typography from 'components/Typography'; + +import { usePatternMatch } from '../../../../../dist'; +import * as styles from './_PrimaryFeature.styles'; + +interface PrimaryFeatureCodeSnippetProps { + snippet: string; +} + +interface PrimaryFeatureVideoProps { + video: string; +} + +export type PrimaryFeatureProps = + | ({ + title: string; + type: 'snippet'; + } & PrimaryFeatureCodeSnippetProps) + | ({ + title: string; + type: 'video'; + } & PrimaryFeatureVideoProps); + +const PrimaryFeatureCodeSnippet: FunctionComponent< + PrimaryFeatureCodeSnippetProps +> = (props) => { + const { snippet } = props; + + return ( +
+ {snippet} +
+ ); +}; + +const PrimaryFeatureVideo: FunctionComponent = ( + props +) => { + const { video } = props; + + return ( +
+ +
+ ); +}; + +export const PrimaryFeature: FunctionComponent = ( + props +) => { + const { title, children, ...otherProps } = props; + const { Match, With, Otherwise } = usePatternMatch(props); + + return ( +
+
+
+
+
+
+ {title} +
+ {children} +
+
+
+ + + + + + + + Fallback. + +
+
+
+
+ ); +}; diff --git a/website/src/pages/Home/_PrimaryFeature/index.ts b/website/src/pages/Home/_PrimaryFeature/index.ts new file mode 100644 index 0000000..03630c3 --- /dev/null +++ b/website/src/pages/Home/_PrimaryFeature/index.ts @@ -0,0 +1 @@ +export { PrimaryFeature as default } from './_PrimaryFeature'; diff --git a/website/src/pages/Home/_PrimaryFeatures/_PrimaryFeatures.styles.ts b/website/src/pages/Home/_PrimaryFeatures/_PrimaryFeatures.styles.ts new file mode 100644 index 0000000..9b01698 --- /dev/null +++ b/website/src/pages/Home/_PrimaryFeatures/_PrimaryFeatures.styles.ts @@ -0,0 +1,3 @@ +import { classnames, position } from 'tailwindcss-classnames'; + +export const section = classnames(position('relative')); diff --git a/website/src/pages/Home/_PrimaryFeatures/_PrimaryFeatures.tsx b/website/src/pages/Home/_PrimaryFeatures/_PrimaryFeatures.tsx new file mode 100644 index 0000000..5678215 --- /dev/null +++ b/website/src/pages/Home/_PrimaryFeatures/_PrimaryFeatures.tsx @@ -0,0 +1,117 @@ +import React, { FunctionComponent } from 'react'; + +import heroVideo from '@site/static/img/hero.mp4'; + +import Typography from 'components/Typography'; + +import PrimaryFeature from '../_PrimaryFeature'; +import * as styles from './_PrimaryFeatures.styles'; + +const snippets = [ + `export type Data = + | { type: "text"; content?: string } + | { type: "img"; extension?: string }; + +export default function MyComponent() { + const response = useApiRequest(); + + const { Switch, Exact, With, Otherwise } = usePatternMatch(); + + return ( + {/* Renders ONLY the first match */} + + {/* Matches ANY image */} + Image + {/* Compiler fails ("extension" not provided) */} + Image + {/* Matches ONLY .jpg images */} + + Image + + {/* Matches ONLY .png images */} + + Image + + Default + + ); +}`, + `const supportsSensor = () => Boolean(window.AmbientLightSensor); + +const AmbientLight = React.lazy(() => import("./AmbientLight")); + +const Fallback = React.lazy(() => import("./Fallback")); + +export default function MyComponent() { + const { Match, When, Otherwise } = usePatternMatch(); + + return ( + + + + + + + + + + + ); +}`, +]; + +const videos = [heroVideo]; + +export const PrimaryFeatures: FunctionComponent = () => { + return ( +
+ + + Matching patterns isn't a new idea in the React world. We have plenty + of domain-specific matching/branching solutions, like react-router + (declarative matching for routes) and react-device-detect (declarative + matching for device type). + + + Besides the key core Match, With, and When components, react-matchez + also provides you with extra helpers like Switch and Exact, inspired + by react-router. + + + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua. + + + Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris + nisi ut aliquip ex ea commodo consequat. + + + + + + react-matchez plays really well with Suspense/lazy in scenarios + of feature/browser/OS detection. + + + Combine the three of them and you'll have your users downloading{' '} + only the actual component bundle that matches your condition. + + +
+ ); +}; diff --git a/website/src/pages/Home/_PrimaryFeatures/index.ts b/website/src/pages/Home/_PrimaryFeatures/index.ts new file mode 100644 index 0000000..58671df --- /dev/null +++ b/website/src/pages/Home/_PrimaryFeatures/index.ts @@ -0,0 +1 @@ +export { PrimaryFeatures as default } from './_PrimaryFeatures'; diff --git a/website/src/pages/Home/_SecondaryFeatures/_SecondaryFeatures.styles.ts b/website/src/pages/Home/_SecondaryFeatures/_SecondaryFeatures.styles.ts new file mode 100644 index 0000000..aa3eec2 --- /dev/null +++ b/website/src/pages/Home/_SecondaryFeatures/_SecondaryFeatures.styles.ts @@ -0,0 +1,39 @@ +import { + alignItems, + classnames, + display, + gap, + gridTemplateColumns, + margin, + maxWidth, + padding, + position, + textAlign, +} from 'tailwindcss-classnames'; + +export const featuresContainer = classnames( + maxWidth('max-w-sm', 'md:max-w-2xl', 'lg:max-w-none'), + margin('mx-auto'), + display('grid'), + gap('gap-6'), + gridTemplateColumns('md:grid-cols-2', 'lg:grid-cols-3'), + alignItems('items-start') +); + +export const textContainer = classnames( + textAlign('text-center'), + padding('pb-12', 'md:pb-20'), + margin('mx-auto'), + maxWidth('max-w-3xl') +); + +export const innerContainer = classnames(padding('py-12', 'md:py-20')); + +export const outerContainer = classnames( + position('relative'), + padding('px-4', 'sm:px-6'), + margin('mx-auto'), + maxWidth('max-w-6xl') +); + +export const section = classnames(position('relative')); diff --git a/website/src/pages/Home/_SecondaryFeatures/_SecondaryFeatures.tsx b/website/src/pages/Home/_SecondaryFeatures/_SecondaryFeatures.tsx new file mode 100644 index 0000000..cfc376b --- /dev/null +++ b/website/src/pages/Home/_SecondaryFeatures/_SecondaryFeatures.tsx @@ -0,0 +1,57 @@ +import React, { FunctionComponent } from 'react'; + +import Card from 'components/Card'; +import Typography from 'components/Typography'; + +import * as styles from './_SecondaryFeatures.styles'; + +const features = [ + { + title: 'Works on any data structure', + description: + 'Nested objects, arrays, tuples, Sets, Maps and all primitive types.', + }, + { + title: 'Expressive, typesafe, API', + description: + 'Helpful type inference + support for predicates and not patterns for more complex scenarios.', + }, + { + title: 'Exact matching support', + description: + 'Enforces that you are matching every possible part of your shape.', + }, + { + title: '~7.4 kB', + description: + "A small bundle footprint that doesn't impact a lot your final bundle size.", + }, +]; + +export const SecondaryFeatures: FunctionComponent = () => { + return ( +
+
+
+
+ Full set of features + + Everything you need to bring declarative code branching to your + apps. + +
+ +
+ {features.map((feature) => ( + + ))} +
+
+
+
+ ); +}; diff --git a/website/src/pages/Home/_SecondaryFeatures/index.ts b/website/src/pages/Home/_SecondaryFeatures/index.ts new file mode 100644 index 0000000..b2d12fb --- /dev/null +++ b/website/src/pages/Home/_SecondaryFeatures/index.ts @@ -0,0 +1 @@ +export { SecondaryFeatures as default } from './_SecondaryFeatures'; diff --git a/website/src/pages/Home/index.ts b/website/src/pages/Home/index.ts new file mode 100644 index 0000000..af9c1c6 --- /dev/null +++ b/website/src/pages/Home/index.ts @@ -0,0 +1 @@ +export { Home } from './Home'; diff --git a/website/src/pages/index.tsx b/website/src/pages/index.tsx new file mode 100644 index 0000000..5bba973 --- /dev/null +++ b/website/src/pages/index.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +import { Home } from './Home'; + +export default Home; diff --git a/website/src/styles/custom.css b/website/src/styles/custom.css new file mode 100644 index 0000000..be03659 --- /dev/null +++ b/website/src/styles/custom.css @@ -0,0 +1,271 @@ +/* stylelint-disable docusaurus/copyright-header */ +/** + * Any CSS included here will be global. The classic template + * bundles Infima by default. Infima is a CSS framework designed to + * work well for content-centric websites. + */ +/* cyrillic-ext */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmWUlfCRc4AMP6lbBP.woff2) + format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, + U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmWUlfABc4AMP6lbBP.woff2) + format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmWUlfCBc4AMP6lbBP.woff2) + format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmWUlfBxc4AMP6lbBP.woff2) + format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmWUlfCxc4AMP6lbBP.woff2) + format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, + U+01AF-01B0, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmWUlfChc4AMP6lbBP.woff2) + format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, + U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmWUlfBBc4AMP6lQ.woff2) + format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} + +/* cyrillic-ext */ +@font-face { + font-family: 'Rubik'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(https://fonts.gstatic.com/s/rubik/v14/iJWKBXyIfDnIV7nMrXyw023e1Ik.woff2) + format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, + U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Rubik'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(https://fonts.gstatic.com/s/rubik/v14/iJWKBXyIfDnIV7nFrXyw023e1Ik.woff2) + format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* hebrew */ +@font-face { + font-family: 'Rubik'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(https://fonts.gstatic.com/s/rubik/v14/iJWKBXyIfDnIV7nDrXyw023e1Ik.woff2) + format('woff2'); + unicode-range: U+0590-05FF, U+20AA, U+25CC, U+FB1D-FB4F; +} +/* latin-ext */ +@font-face { + font-family: 'Rubik'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(https://fonts.gstatic.com/s/rubik/v14/iJWKBXyIfDnIV7nPrXyw023e1Ik.woff2) + format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, + U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Rubik'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(https://fonts.gstatic.com/s/rubik/v14/iJWKBXyIfDnIV7nBrXyw023e.woff2) + format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} +/* cyrillic-ext */ +@font-face { + font-family: 'Rubik'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(https://fonts.gstatic.com/s/rubik/v14/iJWKBXyIfDnIV7nMrXyw023e1Ik.woff2) + format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, + U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Rubik'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(https://fonts.gstatic.com/s/rubik/v14/iJWKBXyIfDnIV7nFrXyw023e1Ik.woff2) + format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* hebrew */ +@font-face { + font-family: 'Rubik'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(https://fonts.gstatic.com/s/rubik/v14/iJWKBXyIfDnIV7nDrXyw023e1Ik.woff2) + format('woff2'); + unicode-range: U+0590-05FF, U+20AA, U+25CC, U+FB1D-FB4F; +} +/* latin-ext */ +@font-face { + font-family: 'Rubik'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(https://fonts.gstatic.com/s/rubik/v14/iJWKBXyIfDnIV7nPrXyw023e1Ik.woff2) + format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, + U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Rubik'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(https://fonts.gstatic.com/s/rubik/v14/iJWKBXyIfDnIV7nBrXyw023e.woff2) + format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} + +/* You can override the default Infima variables here. */ +:root { + --ifm-color-primary: #ff8008; + --ifm-color-primary-dark: #ee7300; + --ifm-color-primary-darker: #d46700; + --ifm-color-primary-darkest: #bb5b00; + --ifm-color-primary-light: #ff8d22; + --ifm-color-primary-lighter: #ff9a3b; + --ifm-color-primary-lightest: #ffa755; + --ifm-code-font-size: 95%; + --ifm-font-weight-bold: 500; + --ifm-font-family-base: 'Rubik', 'Segoe UI', Roboto, Ubuntu, Cantarell, + 'Noto Sans', sans-serif, system-ui, 'Segoe UI', Helvetica, Arial, sans-serif, + 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; + --ifm-heading-font-family: Roboto, 'Noto Sans', sans-serif, Helvetica, Arial, + 'Segoe UI', Roboto, Ubuntu, Cantarell, 'Noto Sans', sans-serif, system-ui, + 'Segoe UI', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', + 'Segoe UI Symbol'; + /*--ifm-code-background: 95%;*/ +} + +.font-mono code { + width: 100%; +} + +.prism-code .token.keyword { + /*font-weight: 600;*/ +} + +.docusaurus-highlight-code-line { + /*background-color: rgb(72, 77, 91);*/ + /*display: block;*/ + /*margin: 0 calc(-1 * var(--ifm-pre-padding));*/ + /*padding: 0 var(--ifm-pre-padding);*/ +} + +button a { + text-decoration: none; +} + +a.btn { + text-decoration: none; +} + +.custom-code-block pre { + overflow: inherit !important; +} + +/* Tailwind does not compute this properly on buttons */ +.font-bold { + font-weight: 600 !important; +} + +h1, +h2, +h3 { + font-weight: 700; +} + +.header-github-link:hover { + opacity: 0.6; +} + +.header-github-link:before { + content: ''; + width: 24px; + height: 24px; + display: flex; + background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") + no-repeat; +} + +.footer { + z-index: 0; +} + +html[data-theme='dark'] .header-github-link:before { + background: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23fff' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") + no-repeat; +} diff --git a/website/src/styles/patterns.css b/website/src/styles/patterns.css new file mode 100644 index 0000000..ae231d3 --- /dev/null +++ b/website/src/styles/patterns.css @@ -0,0 +1,38 @@ +.h1 { + @apply text-4xl font-extrabold leading-tight tracking-tighter; +} + +.h2 { + @apply text-3xl font-extrabold leading-tight tracking-tighter; +} + +.h3 { + @apply text-3xl font-bold leading-tight; +} + +.h4 { + @apply text-2xl font-bold leading-snug tracking-tight; +} + +@screen md { + .h1 { + @apply text-5xl; + } + + .h2 { + @apply text-4xl; + } +} + +.btn, +.btn-sm { + @apply font-medium inline-flex items-center justify-center border border-transparent rounded leading-snug transition duration-150 ease-in-out; +} + +.btn { + @apply px-8 py-3 shadow-lg; +} + +.btn-sm { + @apply px-4 py-2 shadow; +} diff --git a/website/src/styles/tailwind.css b/website/src/styles/tailwind.css new file mode 100644 index 0000000..8e9ba28 --- /dev/null +++ b/website/src/styles/tailwind.css @@ -0,0 +1,5 @@ +@import './patterns.css'; + +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/website/src/types/assets.d.ts b/website/src/types/assets.d.ts new file mode 100644 index 0000000..15e0d6d --- /dev/null +++ b/website/src/types/assets.d.ts @@ -0,0 +1 @@ +declare module '*.mp4'; diff --git a/website/src/utils/dark-mode.ts b/website/src/utils/dark-mode.ts new file mode 100644 index 0000000..4b9c5d8 --- /dev/null +++ b/website/src/utils/dark-mode.ts @@ -0,0 +1,43 @@ +import { useEffect } from 'react'; + +import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; + +export const updateDarkModeClass = () => { + if (!document) return; + + if ( + document.documentElement?.dataset?.theme === 'dark' && + !document.documentElement.classList.contains('dark') + ) { + document.documentElement.classList.add('dark'); + } else { + document.documentElement.classList.remove('dark'); + } +}; + +export const useDarkModeObserver = () => { + useEffect(() => { + if (!ExecutionEnvironment.canUseDOM) return; + + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if ( + mutation.type == 'attributes' && + mutation.attributeName === 'data-theme' + ) { + updateDarkModeClass(); + } + }); + }); + + observer.observe(document.documentElement, { + attributes: true, + childList: false, + subtree: false, + }); + + return () => { + observer.disconnect(); + }; + }, [ExecutionEnvironment.canUseDOM]); +}; diff --git a/website/src/utils/shader.ts b/website/src/utils/shader.ts new file mode 100644 index 0000000..d87d08a --- /dev/null +++ b/website/src/utils/shader.ts @@ -0,0 +1,914 @@ +import { EffectComposer, RenderPass } from 'postprocessing'; +import * as THREE from 'three'; +import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils'; + +export const CAMERA_FACTOR = 180; +export const TIME_DILATION = 1 / 3; +export const BRIGHT = true; +export const DISPLACEMENT_RADIO = 1.0 / 9.0; +export const SPHERE_RADIUS = 15; + +const red = new THREE.Color(1.0, 0.0, 0.0); +const magenta = new THREE.Color(0.97, 0.34, 0.45); +const cyan = new THREE.Color(0.53, 0.96, 1); +const blue = new THREE.Color(0.46, 0.32, 0.87); +const yellow = new THREE.Color(1, 0.69, 0.0); +export const COLORS = { red, magenta, cyan, blue, yellow }; + +export interface SceneState { + renderer: THREE.WebGLRenderer; + composer: EffectComposer; + camera: THREE.OrthographicCamera; + scene: THREE.Scene; + meshes: THREE.Mesh[]; +} + +// ---------------------------------------------------------------------- +// +// Builders. +// +// ---------------------------------------------------------------------- + +export const buildVertexShader = () => { + return ` + precision highp float; + + struct Layer { + float is_active; + vec3 color; + vec3 sin; + vec3 cos; + vec3 time_dilation; + vec3 coef; + vec3 constant; + }; + + uniform float u_time; + + uniform int u_layers_count; + uniform Layer u_layers[16]; + uniform vec2 u_resolution; + uniform vec3 u_base_color; + uniform float u_sphere_radius; + uniform float u_displacement_ratio; + uniform float u_spread; + + varying vec2 vUv; + varying vec3 vNormal; + varying vec3 v_position; + varying vec3 v_color; + varying float v_displacement_amount; + + vec3 mod289(vec3 x) + { + return x - floor(x * (1.0 / 289.0)) * 289.0; + } + + vec4 mod289(vec4 x) + { + return x - floor(x * (1.0 / 289.0)) * 289.0; + } + + vec4 permute(vec4 x) + { + return mod289(((x*34.0)+1.0)*x); + } + + vec4 taylorInvSqrt(vec4 r) + { + return 1.79284291400159 - 0.85373472095314 * r; + } + + vec3 fade(vec3 t) { + return t*t*t*(t*(t*6.0-15.0)+10.0); + } + + float snoise(vec3 v) + { + const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; + const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); + + // First corner + vec3 i = floor(v + dot(v, C.yyy) ); + vec3 x0 = v - i + dot(i, C.xxx) ; + + // Other corners + vec3 g = step(x0.yzx, x0.xyz); + vec3 l = 1.0 - g; + vec3 i1 = min( g.xyz, l.zxy ); + vec3 i2 = max( g.xyz, l.zxy ); + + // x0 = x0 - 0.0 + 0.0 * C.xxx; + // x1 = x0 - i1 + 1.0 * C.xxx; + // x2 = x0 - i2 + 2.0 * C.xxx; + // x3 = x0 - 1.0 + 3.0 * C.xxx; + vec3 x1 = x0 - i1 + C.xxx; + vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y + vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y + + // Permutations + i = mod289(i); + vec4 p = permute( permute( permute( + i.z + vec4(0.0, i1.z, i2.z, 1.0 )) + + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) + + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); + + // Gradients: 7x7 points over a square, mapped onto an octahedron. + // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) + float n_ = 0.142857142857; // 1.0/7.0 + vec3 ns = n_ * D.wyz - D.xzx; + + vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7) + + vec4 x_ = floor(j * ns.z); + vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N) + + vec4 x = x_ *ns.x + ns.yyyy; + vec4 y = y_ *ns.x + ns.yyyy; + vec4 h = 1.0 - abs(x) - abs(y); + + vec4 b0 = vec4( x.xy, y.xy ); + vec4 b1 = vec4( x.zw, y.zw ); + + //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0; + //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0; + vec4 s0 = floor(b0)*2.0 + 1.0; + vec4 s1 = floor(b1)*2.0 + 1.0; + vec4 sh = -step(h, vec4(0.0)); + + vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ; + vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ; + + vec3 p0 = vec3(a0.xy,h.x); + vec3 p1 = vec3(a0.zw,h.y); + vec3 p2 = vec3(a1.xy,h.z); + vec3 p3 = vec3(a1.zw,h.w); + + //Normalise gradients + vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); + p0 *= norm.x; + p1 *= norm.y; + p2 *= norm.z; + p3 *= norm.w; + + // Mix final noise value + vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); + m = m * m; + return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), + dot(p2,x2), dot(p3,x3) ) ); + } + // Classic Perlin noise + float cnoise(vec3 P) + { + vec3 Pi0 = floor(P); // Integer part for indexing + vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1 + Pi0 = mod289(Pi0); + Pi1 = mod289(Pi1); + vec3 Pf0 = fract(P); // Fractional part for interpolation + vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 + vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); + vec4 iy = vec4(Pi0.yy, Pi1.yy); + vec4 iz0 = Pi0.zzzz; + vec4 iz1 = Pi1.zzzz; + + vec4 ixy = permute(permute(ix) + iy); + vec4 ixy0 = permute(ixy + iz0); + vec4 ixy1 = permute(ixy + iz1); + + vec4 gx0 = ixy0 * (1.0 / 7.0); + vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5; + gx0 = fract(gx0); + vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); + vec4 sz0 = step(gz0, vec4(0.0)); + gx0 -= sz0 * (step(0.0, gx0) - 0.5); + gy0 -= sz0 * (step(0.0, gy0) - 0.5); + + vec4 gx1 = ixy1 * (1.0 / 7.0); + vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5; + gx1 = fract(gx1); + vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); + vec4 sz1 = step(gz1, vec4(0.0)); + gx1 -= sz1 * (step(0.0, gx1) - 0.5); + gy1 -= sz1 * (step(0.0, gy1) - 0.5); + + vec3 g000 = vec3(gx0.x,gy0.x,gz0.x); + vec3 g100 = vec3(gx0.y,gy0.y,gz0.y); + vec3 g010 = vec3(gx0.z,gy0.z,gz0.z); + vec3 g110 = vec3(gx0.w,gy0.w,gz0.w); + vec3 g001 = vec3(gx1.x,gy1.x,gz1.x); + vec3 g101 = vec3(gx1.y,gy1.y,gz1.y); + vec3 g011 = vec3(gx1.z,gy1.z,gz1.z); + vec3 g111 = vec3(gx1.w,gy1.w,gz1.w); + + vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); + g000 *= norm0.x; + g010 *= norm0.y; + g100 *= norm0.z; + g110 *= norm0.w; + vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); + g001 *= norm1.x; + g011 *= norm1.y; + g101 *= norm1.z; + g111 *= norm1.w; + + float n000 = dot(g000, Pf0); + float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); + float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); + float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); + float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); + float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); + float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); + float n111 = dot(g111, Pf1); + + vec3 fade_xyz = fade(Pf0); + vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); + vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); + float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); + return 2.2 * n_xyz; + } + + // YUV to RGB matrix + mat3 yuv2rgb = mat3(1.0, 0.0, 1.13983, + 1.0, -0.39465, -0.58060, + 1.0, 2.03211, 0.0); + + // RGB to YUV matrix + mat3 rgb2yuv = mat3(0.2126, 0.7152, 0.0722, + -0.09991, -0.33609, 0.43600, + 0.615, -0.5586, -0.05639); + + + vec3 blendNormal(vec3 base, vec3 blend) { + return blend; + } + + vec3 blendNormal(vec3 base, vec3 blend, float opacity) { + return (blendNormal(base, blend) * opacity + base * (1.0 - opacity)); + } + + vec3 getColor(){ + + // vec3 black = vec3(.0,.0,.0); + // vec3 white = vec3(1.0,1.0,1.0); + + float value1 = sin(u_time); + + vec3 st = v_position / u_sphere_radius * v_displacement_amount; + + vec3 color; + + vec3 color_0 = u_layers[0].color; + color = u_base_color; + color -= (1.0-st.z) / 3.0; + + + for (int i = 0; i < u_layers_count; i++) { + // if(u_layers[i].is_active == 1.0){ + vec3 nColor = u_layers[i].color; + vec3 constant = u_layers[i].constant; + + float x = 0.0; + if(u_layers[i].sin.x == 1.){ + x = sin(u_time * u_layers[i].time_dilation.x); + } + if(u_layers[i].cos.x == 1.){ + x = cos(u_time * u_layers[i].time_dilation.x); + } + x *= u_layers[i].coef.x; + x += u_layers[i].constant.x; + + float y = 0.0; + if(u_layers[i].sin.y == 1.){ + y = sin(u_time * u_layers[i].time_dilation.y); + } + if(u_layers[i].cos.y == 1.){ + y = cos(u_time * u_layers[i].time_dilation.y); + } + y *= u_layers[i].coef.y; + y += u_layers[i].constant.y; + + float z = 0.0; + if(u_layers[i].sin.z == 1.){ + z = sin(u_time * u_layers[i].time_dilation.z); + } + if(u_layers[i].cos.z == 1.){ + z = cos(u_time * u_layers[i].time_dilation.z); + } + z *= u_layers[i].coef.z; + z += u_layers[i].constant.z; + + float amount = + pow( + smoothstep( 0.0, .9, + 1.05 - + distance(st, normalize(vec3(x, y, z) ) ) ) + , u_spread); + color = blendNormal(color, nColor, amount); + // } + } + return color; + } + + void main() { + + vUv = uv; + + float s = 2.45; + float r = u_time * 0.25; + + vNormal = normal; + v_position = position; + v_displacement_amount = cnoise(s * normal + r) * u_displacement_ratio + 1.0; + + // float s2 = 1.35; + // float r2 = u_time * 0.15; + // v_displacement_amount = snoise(s2 * normal + r2) * u_displacement_ratio + 1.0; + + vec3 newPosition = position * v_displacement_amount ; + + v_color = getColor(); + // v_color = czm_saturation(v_color, 1.0); + + gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 ); + } +`; +}; + +export const buildFragmentShader = () => { + return ` + precision highp float; + + uniform float u_time; + + varying float v_displacement_amount; + varying vec3 v_position; + varying vec3 v_color; + + vec3 czm_saturation(vec3 rgb, float adjustment) { + const vec3 W = vec3(0.2125, 0.7154, 0.0721); + vec3 intensity = vec3(dot(rgb, W)); + return mix(intensity, rgb, adjustment); + } + + void main(){ + vec3 color = v_color; + color.gb -= (sin(v_position.z + v_displacement_amount) + sin(u_time)) * 0.05; + color = czm_saturation(color, 1.2); + gl_FragColor = vec4(color,1.0); + } +`; +}; + +export const buildNightGeometry = (radius: number, complexity: number) => { + const vertices: { pos: number[]; norm: number[]; uv: number[] }[] = []; + + const dodecahedron = new THREE.DodecahedronGeometry(radius, complexity); + + for (let i = 0; i < dodecahedron.attributes.position.count; i++) { + const x = dodecahedron.attributes.position.array[i * 3]; + const y = dodecahedron.attributes.position.array[i * 3 + 1]; + const z = dodecahedron.attributes.position.array[i * 3 + 2]; + const pos = [x, y, z]; + const norm = [ + dodecahedron.attributes.normal.array[i * 3], + dodecahedron.attributes.normal.array[i * 3 + 1], + dodecahedron.attributes.normal.array[i * 3 + 2], + ]; + const uv = [ + dodecahedron.attributes.uv.array[i * 2], + dodecahedron.attributes.uv.array[i * 2 + 1], + ]; + vertices.push({ pos, norm, uv }); + } + + const positions: number[] = []; + const normals: number[] = []; + const uvs: number[] = []; + for (const vertex of vertices) { + positions.push(...vertex.pos); + normals.push(...vertex.norm); + uvs.push(...vertex.uv); + } + + const geometry = new THREE.BufferGeometry(); + const positionNumComponents = 3; + geometry.setAttribute( + 'position', + new THREE.BufferAttribute( + new Float32Array(positions), + positionNumComponents + ) + ); + + const merged = BufferGeometryUtils.mergeVertices(geometry); + merged.computeVertexNormals(); + return merged; +}; + +export const buildMainLayers = ( + cyan: THREE.Color, + yellow: THREE.Color, + blue: THREE.Color, + magenta: THREE.Color, + red: THREE.Color +) => { + const layers: any[] = []; + + layers.push({ + is_active: 1, + color: cyan, + sin: new THREE.Vector3(0, 1, 0), + cos: new THREE.Vector3(0, 0, 0), + time_dilation: new THREE.Vector3(0.7, 0.9, 0.8), + coef: new THREE.Vector3(1, 0, 0), + constant: new THREE.Vector3(0, -0.8, 1), + }); + + layers.push({ + is_active: 1, + color: blue, + sin: new THREE.Vector3(0, 0, 1), + cos: new THREE.Vector3(0, 1, 1), + time_dilation: new THREE.Vector3(0.7, 0.4, 0.5), + coef: new THREE.Vector3(0.3, -0.5, 1), + constant: new THREE.Vector3(0, -0.4, 0.5), + }); + + layers.push({ + is_active: 1, + color: magenta, + sin: new THREE.Vector3(0, 1, 0), + cos: new THREE.Vector3(1, 0, 1), + time_dilation: new THREE.Vector3(0.4, 0.6, 1.1), + coef: new THREE.Vector3(0.1, 0.8, 0.5), + constant: new THREE.Vector3(0, -0.5, 0.7), + }); + layers.push({ + is_active: 1, + color: cyan, + sin: new THREE.Vector3(1, 0, 0), + cos: new THREE.Vector3(0, 0, 0), + time_dilation: new THREE.Vector3(1, 0.9, 1), + coef: new THREE.Vector3(1, 0, 0), + constant: new THREE.Vector3(0, -1, -0.7), + }); + layers.push({ + is_active: 1, + color: blue, + sin: new THREE.Vector3(1, 0, 0), + cos: new THREE.Vector3(0, 0, 0), + time_dilation: new THREE.Vector3(0.9, 0.8, 0.7), + coef: new THREE.Vector3(0.5, 0, 0.4), + constant: new THREE.Vector3(0, -0.9, 0.3), + }); + + layers.push({ + is_active: 1, + color: cyan, + sin: new THREE.Vector3(0, 1, 0), + cos: new THREE.Vector3(1, 0, 1), + time_dilation: new THREE.Vector3(0.4, 0.9, 0.3), + coef: new THREE.Vector3(0.3, 1, -0.3), + constant: new THREE.Vector3(0.3, -1.1, 0.5), + }); + + layers.push({ + is_active: 1, + color: magenta, + sin: new THREE.Vector3(0, 1, 0), + cos: new THREE.Vector3(1, 0, 0), + time_dilation: new THREE.Vector3(0.3, 0.8, 1), + coef: new THREE.Vector3(0.3, 1, -0.3), + constant: new THREE.Vector3(0, -0.7, 0.5), + }); + layers.push({ + is_active: 1, + color: yellow, + sin: new THREE.Vector3(1, 0, 0), + cos: new THREE.Vector3(0, 0, 0), + time_dilation: new THREE.Vector3(1, 1, 1), + coef: new THREE.Vector3(1, 0, 0), + constant: new THREE.Vector3(0, -0.9, 0), + }); + + layers.push({ + is_active: 1, + color: cyan, + sin: new THREE.Vector3(1, 0, 0), + cos: new THREE.Vector3(0, 0, 0), + time_dilation: new THREE.Vector3(0.9, 1, 1), + coef: new THREE.Vector3(-1, 0, 0), + constant: new THREE.Vector3(0, -1, 0.2), + }); + layers.push({ + is_active: 1, + color: blue, + sin: new THREE.Vector3(1, 0, 0), + cos: new THREE.Vector3(0, 0, 0), + time_dilation: new THREE.Vector3(1.1, 1, 1), + coef: new THREE.Vector3(1, 0, 0), + constant: new THREE.Vector3(0, -1, -0.2), + }); + layers.push({ + is_active: 1, + color: blue, + sin: new THREE.Vector3(1, 0, 0), + cos: new THREE.Vector3(0, 0, 0), + time_dilation: new THREE.Vector3(0.7, 1, 1), + coef: new THREE.Vector3(0.3, 0, 0.3), + constant: new THREE.Vector3(0, -0.8, 0.2), + }); + + layers.push({ + is_active: 1, + color: COLORS.red, + sin: new THREE.Vector3(1, 0, 1), + cos: new THREE.Vector3(0, 0, 0), + time_dilation: new THREE.Vector3(0.9, 1, 1), + coef: new THREE.Vector3(1, 0, 0.9), + constant: new THREE.Vector3(0, -1, 0.2), + }); + + layers.push({ + is_active: 1, + color: COLORS.blue, + sin: new THREE.Vector3(1, 1, 0), + cos: new THREE.Vector3(0, 0, 1), + time_dilation: new THREE.Vector3(0.7, 0.6, 1), + coef: new THREE.Vector3(0.3, 0.3, 0.7), + constant: new THREE.Vector3(0, -0.8, -0.3), + }); + + layers.push({ + is_active: 1, + color: COLORS.blue, + sin: new THREE.Vector3(1, 1, 0), + cos: new THREE.Vector3(0, 0, 1), + time_dilation: new THREE.Vector3(0.7, 0.6, 1), + coef: new THREE.Vector3(-0.4, 0.8, 0.5), + constant: new THREE.Vector3(0, -0.8, 0), + }); + + layers.push({ + is_active: 1, + color: COLORS.yellow, + sin: new THREE.Vector3(1, 1, 1), + cos: new THREE.Vector3(0, 0, 0), + time_dilation: new THREE.Vector3(0.8, 0.8, 0.8), + coef: new THREE.Vector3(-0.5, 0.8, 0.3), + constant: new THREE.Vector3(0.1, -0.5, 0.7), + }); + + layers.push({ + is_active: 1, + color: COLORS.cyan, + sin: new THREE.Vector3(1, 0, 0), + cos: new THREE.Vector3(0, 0, 0), + time_dilation: new THREE.Vector3(0.8, 0.7, 0.7), + coef: new THREE.Vector3(1, 0, 0), + constant: new THREE.Vector3(0, -1, 1), + }); + + return layers; +}; + +export const buildSmallLayers = ( + cyan: THREE.Color, + yellow: THREE.Color, + blue: THREE.Color, + magenta: THREE.Color, + red: THREE.Color +) => { + const layers: any[] = []; + layers.push({ + is_active: 1, + color: cyan, + sin: new THREE.Vector3(1, 0, 0), + cos: new THREE.Vector3(0, 0, 0), + time_dilation: new THREE.Vector3(0.8, 0.7, 0.7), + coef: new THREE.Vector3(1, 0, 0), + constant: new THREE.Vector3(0, 1, -1), + }); + layers.push({ + is_active: 1, + color: yellow, + sin: new THREE.Vector3(1, 0, 0), + cos: new THREE.Vector3(0, 1, 0), + time_dilation: new THREE.Vector3(1, 1, 1), + coef: new THREE.Vector3(1, 1, 0), + constant: new THREE.Vector3(0, -0.9, 0), + }); + + layers.push({ + is_active: 1, + color: blue, + sin: new THREE.Vector3(1, 0, 0), + cos: new THREE.Vector3(0, 0, 0), + time_dilation: new THREE.Vector3(1.1, 1, 1), + coef: new THREE.Vector3(1, 0, 0), + constant: new THREE.Vector3(0, -1, -0.2), + }); + layers.push({ + is_active: 1, + color: cyan, + sin: new THREE.Vector3(1, 1, 0), + cos: new THREE.Vector3(0, 0, 1), + time_dilation: new THREE.Vector3(0.7, 0.6, 1), + coef: new THREE.Vector3(0.3, 0.3, 0.7), + constant: new THREE.Vector3(-1.0, -0.8, -0.3), + }); + layers.push({ + is_active: 1, + color: blue, + sin: new THREE.Vector3(1, 0, 0), + cos: new THREE.Vector3(0, 0, 0), + time_dilation: new THREE.Vector3(0.7, 1, 1), + coef: new THREE.Vector3(-0.3, 0, 0.3), + constant: new THREE.Vector3(0, -0.8, 0.2), + }); + + layers.push({ + is_active: 1, + color: magenta, + sin: new THREE.Vector3(0, 1, 0), + cos: new THREE.Vector3(1, 0, 0), + time_dilation: new THREE.Vector3(0.3, 0.8, 1), + coef: new THREE.Vector3(0.3, 1, -0.3), + constant: new THREE.Vector3(0, 0.7, -0.5), + }); + + layers.push({ + is_active: 1, + color: cyan, + sin: new THREE.Vector3(1, 0, 0), + cos: new THREE.Vector3(0, 0, 0), + time_dilation: new THREE.Vector3(0.9, 1, 1), + coef: new THREE.Vector3(-1, 1, 0), + constant: new THREE.Vector3(0, 1, 0.2), + }); + + layers.push({ + is_active: 1, + color: blue, + sin: new THREE.Vector3(1, 1, 0), + cos: new THREE.Vector3(0, 0, 1), + time_dilation: new THREE.Vector3(0.7, 0.6, 1), + coef: new THREE.Vector3(-0.4, 0.8, 0.5), + constant: new THREE.Vector3(0, 0.8, 1.0), + }); + + layers.push({ + is_active: 1, + color: yellow, + sin: new THREE.Vector3(1, 1, 1), + cos: new THREE.Vector3(0, 0, 0), + time_dilation: new THREE.Vector3(0.8, 0.8, 0.8), + coef: new THREE.Vector3(-0.5, 0.8, 0.3), + constant: new THREE.Vector3(0.1, 0.5, -0.7), + }); + + layers.push({ + is_active: 1, + color: cyan, + sin: new THREE.Vector3(0, 1, 0), + cos: new THREE.Vector3(0, 0, 0), + time_dilation: new THREE.Vector3(0.7, 0.9, 0.8), + coef: new THREE.Vector3(1, 1, 0), + constant: new THREE.Vector3(0, 0.8, 1), + }); + + layers.push({ + is_active: 1, + color: blue, + sin: new THREE.Vector3(1, 0, 0), + cos: new THREE.Vector3(0, 1, 0), + time_dilation: new THREE.Vector3(0.7, 0.9, 1), + coef: new THREE.Vector3(-0.3, 1, 0.3), + constant: new THREE.Vector3(0, -0.2, 0.3), + }); + + layers.push({ + is_active: 1, + color: magenta, + sin: new THREE.Vector3(1, 1, 0), + cos: new THREE.Vector3(0, 0, 1), + time_dilation: new THREE.Vector3(0.4, 0.8, 1), + coef: new THREE.Vector3(0.3, -1, -0.5), + constant: new THREE.Vector3(0.2, 0.7, -0.5), + }); + + layers.push({ + is_active: 1, + color: cyan, + sin: new THREE.Vector3(0, 0, 1), + cos: new THREE.Vector3(0, 0, 0), + time_dilation: new THREE.Vector3(1, 1, 0.9), + coef: new THREE.Vector3(-1, 1, 0.6), + constant: new THREE.Vector3(0, -0.3, 0.1), + }); + + layers.push({ + is_active: 1, + color: blue, + sin: new THREE.Vector3(1, 1, 0), + cos: new THREE.Vector3(0, 0, 1), + time_dilation: new THREE.Vector3(0.9, 0.8, 1), + coef: new THREE.Vector3(0.4, -0.8, 0.9), + constant: new THREE.Vector3(0.4, 0.8, 1), + }); + + layers.push({ + is_active: 1, + color: yellow, + sin: new THREE.Vector3(1, 1, 1), + cos: new THREE.Vector3(0, 0, 0), + time_dilation: new THREE.Vector3(0.8, 0.8, 0.8), + coef: new THREE.Vector3(0.5, -0.7, 0.3), + constant: new THREE.Vector3(0.1, -0.5, 0.7), + }); + + layers.push({ + is_active: 1, + color: cyan, + sin: new THREE.Vector3(0, 1, 0), + cos: new THREE.Vector3(1, 0, 0), + time_dilation: new THREE.Vector3(0.7, 1.0, 0.8), + coef: new THREE.Vector3(1, 1, 0), + constant: new THREE.Vector3(0.3, 0.8, 0.5), + }); + return layers; +}; + +export const buildMaterial = ( + width: number, + height: number, + radius: number, + displacement: number, + layers: any[], + spread: number +) => { + const uniforms = { + u_time: { value: 0 }, + u_resolution: { value: new THREE.Vector2(width, height) }, + bright: { value: BRIGHT }, + u_sphere_radius: { value: radius }, + u_displacement_ratio: { value: displacement }, + u_base_color: { value: COLORS.red }, + u_layers: { value: layers }, + u_layers_count: { value: layers.length }, + u_spread: { value: spread }, + }; + + const material = new THREE.ShaderMaterial({ + uniforms: uniforms, + vertexShader: buildVertexShader(), + fragmentShader: buildFragmentShader(), + }); + + material.wireframe = true; + return material; +}; + +export const buildShape = ( + width: number, + height: number, + radius: number, + displacement: number, + positionX, + positionY, + spread +) => { + const layers = buildSmallLayers( + COLORS.cyan, + COLORS.yellow, + COLORS.blue, + COLORS.magenta, + COLORS.red + ); + + const material = buildMaterial( + width, + height, + radius, + displacement, + layers, + spread + ); + + const geometry = buildNightGeometry(radius, radius * 2); + const mesh = new THREE.Mesh(geometry, material); + mesh.initialPositionY = positionY; + mesh.position.x = positionX; + return mesh; +}; + +// ---------------------------------------------------------------------- +// +// Scene. +// +// ---------------------------------------------------------------------- + +export const initScene = ( + ref: HTMLCanvasElement, + width: number, + height: number +) => { + const renderer = new THREE.WebGLRenderer({ + alpha: true, + canvas: ref, + }); + + renderer.setSize(width, height); + + if (typeof window !== 'undefined') + renderer.setPixelRatio(window?.devicePixelRatio > 1 ? 1.5 : 1); + + const meshes: THREE.Mesh[] = []; + + const scene = new THREE.Scene(); + + const layers = buildMainLayers( + COLORS.cyan, + COLORS.yellow, + COLORS.blue, + COLORS.magenta, + COLORS.red + ); + + const material = buildMaterial( + width, + height, + SPHERE_RADIUS, + DISPLACEMENT_RADIO, + layers, + 6.0 + ); + + const geometry = buildNightGeometry(SPHERE_RADIUS, 18); + const mesh = new THREE.Mesh(geometry, material); + mesh.rotation.x = 0.2; + mesh.initialPositionY = 14; + scene.add(mesh); + meshes.push(mesh); + + const mesh2 = buildShape(width, height, 2, 1 / 2, 3, -10, 1.0); + scene.add(mesh2); + meshes.push(mesh2); + + const mesh3 = buildShape(width, height, 1, 1 / 2.5, -2, -15, 1.0); + scene.add(mesh3); + meshes.push(mesh3); + + const mesh4 = buildShape(width, height, 1.5, 1 / 2, -1, -25, 1.0); + scene.add(mesh4); + meshes.push(mesh4); + + const mesh5 = buildShape(width, height, 1, 1 / 2.5, -6.5, -35, 1.0); + scene.add(mesh5); + meshes.push(mesh5); + + const mesh6 = buildShape(width, height, 2.5, 1 / 2.5, 6.5, -45, 1.0); + scene.add(mesh6); + meshes.push(mesh6); + + const left = width / -CAMERA_FACTOR; + const right = width / CAMERA_FACTOR; + const top = height / CAMERA_FACTOR; + const bottom = height / -CAMERA_FACTOR; + const near = 1; + const far = 100; + const camera = new THREE.OrthographicCamera( + left, + right, + top, + bottom, + near, + far + ); + camera.position.z = 15; + + const composer = new EffectComposer(renderer); + const renderPass = new RenderPass(scene, camera); + composer.addPass(renderPass); + + return { + renderer, + composer, + camera, + scene, + meshes, + } as SceneState; +}; + +export const updateSceneSize = ( + state: SceneState, + width: number, + height: number +) => { + state.renderer.setSize(width, height); + state.camera.left = width / -CAMERA_FACTOR; + state.camera.right = width / CAMERA_FACTOR; + state.camera.top = height / CAMERA_FACTOR; + state.camera.bottom = height / -CAMERA_FACTOR; + state.camera.updateProjectionMatrix(); +}; diff --git a/website/static/img/android-chrome-192x192.png b/website/static/img/android-chrome-192x192.png new file mode 100644 index 0000000..3440487 Binary files /dev/null and b/website/static/img/android-chrome-192x192.png differ diff --git a/website/static/img/android-chrome-512x512.png b/website/static/img/android-chrome-512x512.png new file mode 100644 index 0000000..4518ec3 Binary files /dev/null and b/website/static/img/android-chrome-512x512.png differ diff --git a/website/static/img/apple-touch-icon.png b/website/static/img/apple-touch-icon.png new file mode 100644 index 0000000..a3e341a Binary files /dev/null and b/website/static/img/apple-touch-icon.png differ diff --git a/website/static/img/favicon-16x16.png b/website/static/img/favicon-16x16.png new file mode 100644 index 0000000..e94da26 Binary files /dev/null and b/website/static/img/favicon-16x16.png differ diff --git a/website/static/img/favicon-32x32.png b/website/static/img/favicon-32x32.png new file mode 100644 index 0000000..17fd7ef Binary files /dev/null and b/website/static/img/favicon-32x32.png differ diff --git a/website/static/img/favicon.ico b/website/static/img/favicon.ico new file mode 100644 index 0000000..f0ddad2 Binary files /dev/null and b/website/static/img/favicon.ico differ diff --git a/website/static/img/hero.mp4 b/website/static/img/hero.mp4 new file mode 100644 index 0000000..4d20e2f Binary files /dev/null and b/website/static/img/hero.mp4 differ diff --git a/website/static/img/logo.svg b/website/static/img/logo.svg new file mode 100644 index 0000000..d216701 --- /dev/null +++ b/website/static/img/logo.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/static/img/site.webmanifest b/website/static/img/site.webmanifest new file mode 100644 index 0000000..fa99de7 --- /dev/null +++ b/website/static/img/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/website/static/robots.txt b/website/static/robots.txt new file mode 100644 index 0000000..abe534c --- /dev/null +++ b/website/static/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: sitemap.xml diff --git a/website/tailwind.config.js b/website/tailwind.config.js new file mode 100644 index 0000000..f64e69b --- /dev/null +++ b/website/tailwind.config.js @@ -0,0 +1,160 @@ +module.exports = { + darkMode: 'class', + purge: ['./src/**/*.html', './src/**/*.js', './src/**/*.tsx'], + // important: '#tailwind', + corePlugins: { preflight: false }, + theme: { + extend: { + colors: { + gray: { + 100: '#FBFBFB', + 200: '#EAEAEA', + 300: '#DFDFDF', + 400: '#999999', + 500: '#7F7F7F', + 600: '#666666', + 700: '#4C4C4C', + 800: '#333333', + 900: '#191919', + }, + blue: { + 100: '#E6F0FD', + 200: '#CCE2FC', + 300: '#99C5FA', + 400: '#66A9F7', + 500: '#338CF5', + 600: '#0070F4', + 700: '#0064DA', + 800: '#0059C2', + 900: '#004391', + }, + }, + boxShadow: { + xs: '0 0 0 1px rgba(0, 0, 0, 0.16)', + sm: '0 1px 2px 0 rgba(0, 0, 0, 0.16)', + default: + '0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 1px 2px 0 rgba(0, 0, 0, 0.03)', + md: '0 4px 6px -1px rgba(0, 0, 0, 0.04), 0 2px 4px -1px rgba(0, 0, 0, 0.03)', + lg: '0 10px 15px -3px rgba(0, 0, 0, 0.04), 0 4px 6px -2px rgba(0, 0, 0, 0.02)', + xl: '0 20px 25px -5px rgba(0, 0, 0, 0.12), 0 10px 10px -5px rgba(0, 0, 0, 0.02)', + '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.15)', + inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.04)', + outline: '0 0 0 3px rgba(66, 153, 225, 0.5)', + none: 'none', + }, + spacing: { + '9/16': '56.25%', + '3/4': '75%', + '1/1': '100%', + }, + fontFamily: { + inter: ['Inter', 'sans-serif'], + }, + fontSize: { + xs: '0.75rem', + sm: '0.875rem', + base: '1rem', + lg: '1.125rem', + xl: '1.25rem', + '2xl': '1.5rem', + '3xl': '2rem', + '4xl': '2.625rem', + '5xl': '3.25rem', + '6xl': '5.5rem', + }, + inset: { + '1/2': '50%', + full: '100%', + }, + letterSpacing: { + tighter: '-0.02em', + tight: '-0.01em', + normal: '0', + wide: '0.01em', + wider: '0.02em', + widest: '0.4em', + }, + lineHeight: { + none: '1', + tighter: '1.125', + tight: '1.25', + snug: '1.375', + normal: '1.5', + relaxed: '1.625', + loose: '2', + 3: '.75rem', + 4: '1rem', + 5: '1.2rem', + 6: '1.5rem', + 7: '1.75rem', + 8: '2rem', + 9: '2.25rem', + 10: '2.5rem', + }, + minWidth: { + 10: '2.5rem', + 48: '12rem', + }, + opacity: { + 90: '0.9', + }, + scale: { + 98: '.98', + }, + animation: { + float: 'float 3s ease-in-out infinite', + }, + keyframes: { + float: { + '0%, 100%': { transform: 'translateY(0)' }, + '50%': { transform: 'translateY(-5%)' }, + }, + }, + customForms: (theme) => ({ + default: { + 'input, textarea, multiselect, select, checkbox, radio': { + backgroundColor: theme('colors.white'), + borderColor: theme('colors.gray.300'), + borderRadius: theme('borderRadius.default'), + '&:focus': { + outline: undefined, + boxShadow: undefined, + borderColor: theme('colors.gray.500'), + }, + }, + 'input, textarea, multiselect, select': { + backgroundColor: theme('colors.white'), + fontSize: undefined, + lineHeight: undefined, + paddingTop: theme('spacing.3'), + paddingRight: theme('spacing.4'), + paddingBottom: theme('spacing.3'), + paddingLeft: theme('spacing.4'), + }, + 'input, textarea': { + '&::placeholder': { + color: theme('colors.gray.500'), + }, + }, + select: { + paddingRight: theme('spacing.10'), + iconColor: theme('colors.gray.400'), + }, + 'checkbox, radio': { + color: theme('colors.gray.800'), + backgroundColor: theme('colors.white'), + borderRadius: theme('borderRadius.sm'), + }, + }, + }), + }, + }, + variants: { + backgroundColor: ['responsive', 'hover', 'dark'], + textColor: ['responsive', 'hover', 'dark'], + translate: ['responsive', 'hover', 'dark'], + boxShadow: ['responsive', 'hover', 'focus', 'focus-within', 'dark'], + opacity: ['responsive', 'hover', 'dark'], + }, + plugins: [], +}; diff --git a/website/tsconfig.json b/website/tsconfig.json new file mode 100644 index 0000000..bd682ca --- /dev/null +++ b/website/tsconfig.json @@ -0,0 +1,30 @@ +{ + "extends": "@tsconfig/docusaurus/tsconfig.json", + "compilerOptions": { + "baseUrl": "src", + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "noImplicitAny": false, + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "sourceMap": true, + "declaration": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "strictNullChecks": true, + "suppressImplicitAnyIndexErrors": true, + "jsxImportSource": "@emotion/react" + }, + "include": ["src"] +} diff --git a/website/typedoc-sidebar.js b/website/typedoc-sidebar.js new file mode 100644 index 0000000..ecaab57 --- /dev/null +++ b/website/typedoc-sidebar.js @@ -0,0 +1 @@ +module.exports = [{ type: 'autogenerated', dirName: 'api' }]; diff --git a/website/typedoc.json b/website/typedoc.json new file mode 100644 index 0000000..5ec00e4 --- /dev/null +++ b/website/typedoc.json @@ -0,0 +1,6 @@ +{ + "allReflectionsHaveOwnDocument": true, + "readme": "docs/introduction_api.md", + "name": "API", + "categorizeByGroup": true +}