├── .changeset
├── README.md
└── config.json
├── .eslintrc.json
├── .github
├── CODEOWNERS
└── workflows
│ ├── ci.yml
│ └── release.yml
├── .gitignore
├── .husky
└── pre-commit
├── .ladle
├── config.mjs
├── head.html
└── vite-plugin-node.js
├── .lintstagedrc
├── .prettierignore
├── .prettierrc
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── commitlint.config.js
├── examples
├── __tests__
│ ├── basic.spec.ts
│ ├── custom-prop.spec.ts
│ ├── live-code-only.spec.ts
│ ├── modal.spec.ts
│ ├── state-hook.spec.ts
│ ├── theming.spec.ts
│ ├── typescript.spec.ts
│ └── view.spec.ts
├── advanced.stories.tsx
├── basic.tsx
├── const.ts
├── custom-prop.tsx
├── layout
│ └── index.tsx
├── live-code-only.tsx
├── modal.tsx
├── showcase-components
│ ├── button.tsx
│ ├── input.tsx
│ ├── modal.tsx
│ ├── rating.tsx
│ └── theme-provider.tsx
├── state-hook.tsx
├── test.stories.tsx
├── theming.tsx
├── typescript.tsx
├── use-view.stories.tsx
├── view.stories.tsx
└── view.tsx
├── package.json
├── playwright.config.ts
├── pnpm-lock.yaml
├── src
├── __tests__
│ ├── ast.test.ts
│ ├── code-generator.test.ts
│ └── utils.test.ts
├── actions.ts
├── ast.ts
├── code-generator.ts
├── const.ts
├── index.ts
├── light-theme.ts
├── reducer.ts
├── snippets
│ ├── __tests__
│ │ └── vscode-snippet.test.ts
│ └── vscode-snippet.ts
├── types.ts
├── ui
│ ├── action-buttons.tsx
│ ├── compiler.tsx
│ ├── editor.tsx
│ ├── error.tsx
│ ├── knob.tsx
│ ├── knobs.tsx
│ ├── placeholder.tsx
│ └── view.tsx
├── use-view.ts
└── utils.ts
├── tsconfig.es.json
├── tsconfig.json
├── tsconfig.lib.json
└── vite.config.js
/.changeset/README.md:
--------------------------------------------------------------------------------
1 | # Changesets
2 |
3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4 | with multi-package repos, or single-package repos to help you version and publish your code. You can
5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6 |
7 | We have a quick list of common questions to get you started engaging with this project in
8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
9 |
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
3 | "changelog": ["@changesets/changelog-github", { "repo": "uber/react-view" }],
4 | "commit": false,
5 | "fixed": [],
6 | "linked": [],
7 | "access": "public",
8 | "baseBranch": "master",
9 | "updateInternalDependencies": "patch",
10 | "ignore": []
11 | }
12 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "extends": [
4 | "plugin:react/recommended",
5 | "plugin:@typescript-eslint/recommended",
6 | "plugin:prettier/recommended"
7 | ],
8 | "rules": {
9 | "@typescript-eslint/no-explicit-any": "off",
10 | "@typescript-eslint/ban-types": "off",
11 | "@typescript-eslint/ban-ts-comment": "off",
12 | "@typescript-eslint/explicit-module-boundary-types": "off",
13 | "react/prop-types": "off",
14 | "react/react-in-jsx-scope": "off"
15 | },
16 | "settings": {
17 | "react": {
18 | "version": "detect"
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Learn how to add code owners here:
2 | # https://help.github.com/en/articles/about-code-owners
3 | * @chasestarr @tajo
4 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: ["master"]
6 | pull_request:
7 | types: [opened, synchronize]
8 |
9 | jobs:
10 | build:
11 | name: Build and Test
12 | runs-on: ${{ matrix.os }}
13 | timeout-minutes: 15
14 | env:
15 | CI: true
16 | strategy:
17 | matrix:
18 | os: [ubuntu-latest]
19 | node-version: [18.x, 20.x]
20 | steps:
21 | - name: Check out code
22 | uses: actions/checkout@v3
23 | with:
24 | fetch-depth: 2
25 |
26 | - uses: pnpm/action-setup@v2.2.4
27 | with:
28 | version: 8.7.1
29 |
30 | - name: Setup Node.js environment
31 | uses: actions/setup-node@v3
32 | with:
33 | node-version: ${{ matrix.node-version }}
34 | cache: "pnpm"
35 |
36 | - name: Install dependencies
37 | run: pnpm install --frozen-lockfile
38 |
39 | - name: Install playwright
40 | run: pnpm exec playwright install --with-deps
41 |
42 | - name: Eslint
43 | run: pnpm lint
44 |
45 | - name: Typescript
46 | run: pnpm typecheck
47 |
48 | - name: Build
49 | run: pnpm build
50 |
51 | - name: Unit tests
52 | run: pnpm test
53 |
54 | - name: E2e tests
55 | run: pnpm test:e2e
56 |
57 | - uses: actions/upload-artifact@v3
58 | if: always()
59 | with:
60 | name: playwright-report
61 | path: playwright-report/
62 | retention-days: 30
63 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | concurrency: ${{ github.workflow }}-${{ github.ref }}
9 |
10 | jobs:
11 | release:
12 | name: Release
13 | runs-on: ubuntu-latest
14 | permissions:
15 | contents: write
16 | pull-requests: write
17 | id-token: write
18 | steps:
19 | - name: Checkout Repo
20 | uses: actions/checkout@v3
21 | with:
22 | # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
23 | fetch-depth: 0
24 |
25 | - uses: pnpm/action-setup@v2.2.4
26 | with:
27 | version: 8.7.1
28 |
29 | - name: Setup Node.js environment
30 | uses: actions/setup-node@v3
31 | with:
32 | node-version: 20.5
33 | cache: "pnpm"
34 |
35 | - name: Install dependencies
36 | run: pnpm install --frozen-lockfile
37 |
38 | - name: Build
39 | run: pnpm build
40 |
41 | - name: Creating .npmrc
42 | run: |
43 | cat << EOF > "$HOME/.npmrc"
44 | email=vojtech+ladle@miksu.cz
45 | //registry.npmjs.org/:_authToken=$NPM_TOKEN
46 | EOF
47 | env:
48 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
49 |
50 | - name: Create Release Pull Request or Publish to npm
51 | id: changesets
52 | uses: changesets/action@v1
53 | with:
54 | publish: pnpm changeset publish
55 | env:
56 | GITHUB_TOKEN: ${{ secrets.GIT_DEPLOY_KEY }}
57 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
58 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | *.log
4 | .vscode
5 | .idea
6 | dist
7 | build
8 | compiled
9 | docs
10 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | pnpm exec lint-staged
5 |
--------------------------------------------------------------------------------
/.ladle/config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('@ladle/react').UserConfig} */
2 | export default {
3 | stories: "examples/**/*.stories.{js,jsx,ts,tsx,mdx}",
4 | };
5 |
--------------------------------------------------------------------------------
/.ladle/head.html:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/.ladle/vite-plugin-node.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 |
3 | import { builtinModules } from "node:module";
4 | import nodeLibsBrowser from "node-libs-browser";
5 |
6 | function NodeBuiltinsPolyfillPlugin() {
7 | return {
8 | name: "vite:node-builtins-polyfill",
9 | config() {
10 | const aliasEntries = [];
11 | for (let moduleName of builtinModules) {
12 | const polyfillPath = nodeLibsBrowser[moduleName];
13 | if (polyfillPath) {
14 | aliasEntries.push({
15 | // eslint-disable-next-line
16 | find: new RegExp(`^${moduleName}\/?$`), // handle "string_decoder/" import
17 | replacement: polyfillPath,
18 | });
19 | }
20 | }
21 |
22 | return {
23 | resolve: {
24 | alias: aliasEntries,
25 | },
26 | };
27 | },
28 | };
29 | }
30 |
31 | export default NodeBuiltinsPolyfillPlugin;
32 |
--------------------------------------------------------------------------------
/.lintstagedrc:
--------------------------------------------------------------------------------
1 | {
2 | "*.{js,ts,tsx,css,scss,postcss,md,json}": [
3 | "prettier --write --plugin-search-dir=.",
4 | "prettier --check --plugin-search-dir=."
5 | ],
6 | "*.{js,ts,tsx}": "eslint"
7 | }
8 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | pnpm-lock.yaml
2 | pnpm-workspace.yaml
3 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "singleQuote": false,
4 | "trailingComma": "all"
5 | }
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # react-view
2 |
3 | ## 3.0.1
4 |
5 | ### Patch Changes
6 |
7 | - [#105](https://github.com/uber/react-view/pull/105) [`9a6fd36`](https://github.com/uber/react-view/commit/9a6fd361380e6540edffe1e4068084a8b87c269a) Thanks [@taifen](https://github.com/taifen)! - Updated react-tiny-popover to use React 17 and 18
8 |
9 | ## 3.0.0
10 |
11 | ### Major Changes
12 |
13 | - [#102](https://github.com/uber/react-view/pull/102) [`89350f4`](https://github.com/uber/react-view/commit/89350f40c745aca38ef78e58efd8c4b6191b7788) Thanks [@tajo](https://github.com/tajo)! - No breaking changes really, just bumping all dependencies and modernizing the tooling. Live code that has export default gets wrapped by an additional IIEF, so you can have other variables defined at the root scope.
14 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | - Using welcoming and inclusive language
12 | - Being respectful of differing viewpoints and experiences
13 | - Gracefully accepting constructive criticism
14 | - Focusing on what is best for the community
15 | - Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | - Trolling, insulting/derogatory comments, and personal or political attacks
21 | - Public or private harassment
22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | - Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at vojtech@uber.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [https://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: https://contributor-covenant.org
46 | [version]: https://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Contributing to React View
2 |
3 | 1. Clone the repo locally and run yarn to install dependencies from npm. We use [volta](https://volta.sh/).
4 |
5 | ```sh
6 | git clone https://github.com/uber/react-view
7 | cd react-view
8 | pnpm install
9 | ```
10 |
11 | 2. You can test your changes inside of the Ladle dev server by running:
12 |
13 | ```sh
14 | pnpm ladle serve
15 | ```
16 |
17 | 3. When done, run all unit tests, e2e tests, typescript check and eslint via:
18 |
19 | ```sh
20 | pnpm typecheck
21 | pnpm lint
22 | pnpm test
23 |
24 | pnpm exec playwright install
25 | pnpm test:e2e:dev
26 | ```
27 |
28 | All features and bug fixes should be covered by unit or e2e tests.
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Uber Technologies, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = { extends: ["@commitlint/config-conventional"] };
2 |
--------------------------------------------------------------------------------
/examples/__tests__/basic.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Uber Technologies, Inc.
3 |
4 | This source code is licensed under the MIT license found in the
5 | LICENSE file in the root directory of this source tree.
6 | */
7 | import { urls } from "../const";
8 |
9 | import { test, expect } from "@playwright/test";
10 |
11 | test.describe("Basic knobs", () => {
12 | test.beforeEach(async ({ page }) => {
13 | await page.goto(urls.basic);
14 | await page.waitForSelector("[data-storyloaded]");
15 | await page.click('[data-testid="rv-reset"]');
16 | });
17 | test("should select size compact, update component and input", async ({
18 | page,
19 | }) => {
20 | const codeOutput = `import * as React from "react";
21 | import { Button, SIZE } from "your-button-component";
22 |
23 | export default () => {
24 | return (
25 |
31 | );
32 | }`;
33 |
34 | await page.click("#size_compact");
35 | const fontSize = await page.$eval(
36 | "#example-btn",
37 | (e) => (e as any).style["font-size"],
38 | );
39 | expect(fontSize).toBe("14px");
40 | const editorTextarea = await page.$('[data-testid="rv-editor"] textarea');
41 | const text = await page.evaluate((el: any) => el.value, editorTextarea);
42 | expect(text).toBe(codeOutput);
43 | });
44 |
45 | test("should check disabled, update component and input", async ({
46 | page,
47 | }) => {
48 | const codeOutput = `import * as React from "react";
49 | import { Button } from "your-button-component";
50 |
51 | export default () => {
52 | return (
53 |
56 | );
57 | }`;
58 | await page.click("#disabled");
59 | const isDisabled = await page.$eval(
60 | "#example-btn",
61 | (e) => (e as any).disabled,
62 | );
63 | expect(isDisabled).toBeTruthy();
64 | const editorTextarea = await page.$('[data-testid="rv-editor"] textarea');
65 | const text = await page.evaluate((el: any) => el.value, editorTextarea);
66 | expect(text).toBe(codeOutput);
67 | });
68 |
69 | test("should change the children knob, update component and code", async ({
70 | page,
71 | }) => {
72 | const childrenPropValue = "e2etest";
73 | const codeOutput = `import * as React from "react";
74 | import { Button } from "your-button-component";
75 |
76 | export default () => {
77 | return (
78 |
81 | );
82 | }`;
83 | const textareaSelector = '[data-testid="rv-knob-children"] textarea';
84 | await page.waitForSelector(textareaSelector);
85 | await page.fill(textareaSelector, childrenPropValue);
86 | await page.waitForTimeout(300); // waiting for debounce
87 | const exampleBtn = await page.$("#example-btn");
88 | await expect(exampleBtn!.textContent()).resolves.toBe(childrenPropValue);
89 | const editorTextarea = await page.$('[data-testid="rv-editor"] textarea');
90 | const text = await page.evaluate((el: any) => el.value, editorTextarea);
91 | expect(text).toBe(codeOutput);
92 | });
93 |
94 | test("should change the onClick knob, update component and code", async ({
95 | page,
96 | }) => {
97 | const onClickPropValue = `() => {document.querySelector('h1').innerText = "foo"}`;
98 | const codeOutput = `import * as React from "react";
99 | import { Button } from "your-button-component";
100 |
101 | export default () => {
102 | return (
103 |
110 | );
111 | }`;
112 | await page
113 | .locator('[data-testid="rv-knob-onClick"] textarea')
114 | .fill(onClickPropValue);
115 | await page.waitForTimeout(300); // waiting for debounce
116 | await page.click("#example-btn");
117 | const text = await page.evaluate(() => {
118 | const h1 = document.querySelector("h1");
119 | return h1 ? h1.innerText : "";
120 | });
121 | expect(text).toBe("foo");
122 | const editorTextarea = await page.$('[data-testid="rv-editor"] textarea');
123 | const editorText = await page.evaluate(
124 | (el: any) => el.value,
125 | editorTextarea,
126 | );
127 | expect(editorText).toBe(codeOutput);
128 | });
129 | });
130 |
131 | test.describe("Basic actions", () => {
132 | test.beforeEach(async ({ page }) => {
133 | await page.goto(urls.basic);
134 | await page.waitForSelector("[data-storyloaded]");
135 | await page.click('[data-testid="rv-reset"]');
136 | });
137 |
138 | test("should format the code snippet", async ({ page }) => {
139 | const formattedCode = `import * as React from "react";
140 | import { Button } from "your-button-component";
141 |
142 | export default () => {
143 | return (
144 |
145 | );
146 | }`;
147 | const messyCode = ` import * as React from "react";
148 | import { Button } from "your-button-component";
149 |
150 | export default () => {
151 | return (
152 |
154 | );
155 | }`;
156 | await page.locator('[data-testid="rv-editor"] textarea').fill(messyCode);
157 | // for (let i = 0; i < 232; i++) {
158 | // await page.keyboard.press("Delete");
159 | // }
160 | // await page.keyboard.type(messyCode);
161 | await page.waitForTimeout(300); // waiting for debounce
162 | await page.click('[data-testid="rv-format"]');
163 | const editorTextarea = await page.$('[data-testid="rv-editor"] textarea');
164 | const text = await page.evaluate((el: any) => el.value, editorTextarea);
165 | expect(text).toBe(formattedCode);
166 | });
167 | });
168 |
169 | test.describe("Basic editor", () => {
170 | test.beforeEach(async ({ page }) => {
171 | await page.goto(urls.basic);
172 | await page.waitForSelector("[data-storyloaded]");
173 | await page.click('[data-testid="rv-reset"]');
174 | });
175 |
176 | test("should edit the code and update the knob and component", async ({
177 | page,
178 | }) => {
179 | const newCode = `import * as React from "react";
180 | import { Button } from "your-button-component";
181 |
182 | export default () => {
183 | return (
184 |
185 | );
186 | }`;
187 | await page.locator('[data-testid="rv-editor"] textarea').fill(newCode);
188 | await page.waitForTimeout(300); // waiting for debounce
189 | const isButtonDisabled = await page.$eval(
190 | "#example-btn",
191 | (e) => (e as any).disabled,
192 | );
193 | expect(isButtonDisabled).toBeTruthy();
194 | const isDisabledChecked = await page.$eval(
195 | "#disabled",
196 | (el) => (el as any).checked,
197 | );
198 | expect(isDisabledChecked).toBeTruthy();
199 | });
200 | });
201 |
--------------------------------------------------------------------------------
/examples/__tests__/custom-prop.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Uber Technologies, Inc.
3 |
4 | This source code is licensed under the MIT license found in the
5 | LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import { test, expect } from "@playwright/test";
9 | import { urls } from "../const";
10 |
11 | test.describe("Basic knobs", () => {
12 | test.beforeEach(async ({ page }) => {
13 | await page.goto(urls.customProps);
14 | await page.waitForSelector("[data-storyloaded]");
15 | await page.click('[data-testid="rv-reset"]');
16 | });
17 |
18 | test("should output initial code", async ({ page }) => {
19 | const codeOutput = `import * as React from "react";
20 | import { Rating } from "your-rating-component";
21 |
22 | export default () => {
23 | const [value, setValue] = React.useState(3);
24 | return (
25 | setValue(value)}
28 | />
29 | );
30 | }`;
31 | const editorTextarea = await page.$('[data-testid="rv-editor"] textarea');
32 | const text = await page.evaluate((el: any) => el.value, editorTextarea);
33 | expect(text).toBe(codeOutput);
34 | });
35 |
36 | test("should select 4 hearts and update the slider and code", async ({
37 | page,
38 | }) => {
39 | const codeOutput = `import * as React from "react";
40 | import { Rating } from "your-rating-component";
41 |
42 | export default () => {
43 | const [value, setValue] = React.useState(4);
44 | return (
45 | setValue(value)}
48 | />
49 | );
50 | }`;
51 | await page.click("#heart-4");
52 | await page.waitForTimeout(300); // debounce time
53 | const inputValue = await page.$eval("input", (e) => (e as any).value);
54 | expect(inputValue).toBe("4");
55 | const editorTextarea = await page.$('[data-testid="rv-editor"] textarea');
56 | const text = await page.evaluate((el: any) => el.value, editorTextarea);
57 | expect(text).toBe(codeOutput);
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/examples/__tests__/live-code-only.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Uber Technologies, Inc.
3 |
4 | This source code is licensed under the MIT license found in the
5 | LICENSE file in the root directory of this source tree.
6 | */
7 | import { test, expect } from "@playwright/test";
8 | import { urls } from "../const";
9 |
10 | test.describe("Live Code Only", () => {
11 | test("should compile the code and render component", async ({ page }) => {
12 | await page.goto(urls.liveCodeOnly);
13 | await page.waitForSelector("[data-storyloaded]");
14 | const inputCode = ``;
15 | await page.locator("textarea").first().fill(inputCode);
16 | await page.waitForTimeout(300); // waiting for debounce
17 | const button = await page.$("button");
18 | expect(await button!.textContent()).toBe("Hey");
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/examples/__tests__/modal.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Uber Technologies, Inc.
3 |
4 | This source code is licensed under the MIT license found in the
5 | LICENSE file in the root directory of this source tree.
6 | */
7 | import { test, expect } from "@playwright/test";
8 | import { urls } from "../const";
9 |
10 | test.describe("Modal", () => {
11 | test("open, close and open the modal", async ({ page }) => {
12 | await page.goto(urls.modal);
13 | await page.waitForSelector("[data-storyloaded]");
14 | await (await page.$("#show"))?.click();
15 | await page.waitForTimeout(300); // waiting for debounce
16 | expect((await page.$("#close-modal")) !== null).toBeTruthy();
17 | (await page.$("#close-modal"))?.click();
18 | await page.waitForTimeout(500); // waiting for debounce
19 | expect((await page.$("#close-modal")) !== null).toBeFalsy();
20 | await (await page.$("#show"))?.click();
21 | await page.waitForTimeout(300); // waiting for debounce
22 | expect((await page.$("#close-modal")) !== null).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/examples/__tests__/state-hook.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Uber Technologies, Inc.
3 |
4 | This source code is licensed under the MIT license found in the
5 | LICENSE file in the root directory of this source tree.
6 | */
7 | import { test, expect } from "@playwright/test";
8 | import { urls } from "../const";
9 |
10 | test.describe("State hook", () => {
11 | test.beforeEach(async ({ page }) => {
12 | await page.goto(urls.stateHook);
13 | await page.waitForSelector("[data-storyloaded]");
14 | await page.click('[data-testid="rv-reset"]');
15 | });
16 |
17 | test("should update the input and sync the knob and code", async ({
18 | page,
19 | }) => {
20 | const codeOutput = `import * as React from "react";
21 | import { Input } from "your-input-component";
22 |
23 | export default () => {
24 | const [value, setValue] = React.useState("HelloFoo");
25 | return (
26 | setValue(e.target.value)}
29 | />
30 | );
31 | }`;
32 |
33 | await page.locator("#example-input").fill("HelloFoo");
34 | await page.waitForTimeout(300); // waiting for debounce
35 |
36 | const valueKnob = await page.$('[data-testid="rv-knob-value"] textarea');
37 | const valueText = await page.evaluate((el: any) => el.value, valueKnob);
38 | expect(valueText).toBe("HelloFoo");
39 |
40 | const editorTextarea = await page.$('[data-testid="rv-editor"] textarea');
41 | const text = await page.evaluate((el: any) => el.value, editorTextarea);
42 | expect(text).toBe(codeOutput);
43 | });
44 |
45 | test("should update the value knob and sync with component and code", async ({
46 | page,
47 | }) => {
48 | const codeOutput = `import * as React from "react";
49 | import { Input } from "your-input-component";
50 |
51 | export default () => {
52 | const [value, setValue] = React.useState("HelloFoo");
53 | return (
54 | setValue(e.target.value)}
57 | />
58 | );
59 | }`;
60 |
61 | await page
62 | .locator('[data-testid="rv-knob-value"] textarea')
63 | .fill("HelloFoo");
64 | await page.waitForTimeout(300); // waiting for debounce
65 |
66 | const input = await page.$("#example-input");
67 | const inputValue = await page.evaluate((el: any) => el.value, input);
68 | expect(inputValue).toBe("HelloFoo");
69 |
70 | const editorTextarea = await page.$('[data-testid="rv-editor"] textarea');
71 | const text = await page.evaluate((el: any) => el.value, editorTextarea);
72 | expect(text).toBe(codeOutput);
73 | });
74 |
75 | test("should respect the default boolean value, uncheck editable and update component and input", async ({
76 | page,
77 | }) => {
78 | const initialCode = `import * as React from "react";
79 | import { Input } from "your-input-component";
80 |
81 | export default () => {
82 | const [value, setValue] = React.useState("Hello");
83 | return (
84 | setValue(e.target.value)}
87 | />
88 | );
89 | }`;
90 | const resultCode = `import * as React from "react";
91 | import { Input } from "your-input-component";
92 |
93 | export default () => {
94 | const [value, setValue] = React.useState("Hello");
95 | return (
96 | setValue(e.target.value)}
99 | editable={false}
100 | />
101 | );
102 | }`;
103 | const initialEditor = await page.evaluate(
104 | (el: any) => el.value,
105 | await page.$('[data-testid="rv-editor"] textarea'),
106 | );
107 | expect(initialEditor).toBe(initialCode);
108 |
109 | await page.click("#editable");
110 | const isDisabled = await page.$eval(
111 | "#example-input",
112 | (e: any) => (e as any).disabled,
113 | );
114 | expect(isDisabled).toBeTruthy();
115 | const resultEditor = await page.evaluate(
116 | (el: any) => el.value,
117 | await page.$('[data-testid="rv-editor"] textarea'),
118 | );
119 | expect(resultEditor).toBe(resultCode);
120 | });
121 | });
122 |
--------------------------------------------------------------------------------
/examples/__tests__/theming.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Uber Technologies, Inc.
3 |
4 | This source code is licensed under the MIT license found in the
5 | LICENSE file in the root directory of this source tree.
6 | */
7 | import { test, expect } from "@playwright/test";
8 | import { urls } from "../const";
9 |
10 | const initialCode = `import * as React from "react";
11 | import { Button } from "your-button-component";
12 |
13 | export default () => {
14 | return ;
15 | }`;
16 |
17 | test.describe("Theming", () => {
18 | test.beforeEach(async ({ page }) => {
19 | await page.goto(urls.theming);
20 | await page.waitForSelector("[data-storyloaded]");
21 | });
22 |
23 | test("should change the theme, add provider and update the component", async ({
24 | page,
25 | }) => {
26 | await page.click('[data-testid="rv-reset"]');
27 | const hotpinkCode = `import * as React from "react";
28 | import { Button } from "your-button-component";
29 | import { ThemeProvider } from "your-component-library";
30 |
31 | export default () => {
32 | return (
33 |
38 |
39 |
40 | );
41 | }`;
42 | const initialEditor = await page.evaluate(
43 | (el: any) => el.value,
44 | await page.$('[data-testid="rv-editor"] textarea'),
45 | );
46 | expect(initialEditor).toBe(initialCode);
47 | await page.locator('[data-testid="background"] textarea').fill("hotpink");
48 | await page.waitForTimeout(600); // waiting for debounce
49 |
50 | const exampleBtn = await page.$("#example-btn");
51 | expect(await exampleBtn!.evaluate((e: any) => e.style["background"])).toBe(
52 | "hotpink",
53 | );
54 | const editorTextarea = await page.$('[data-testid="rv-editor"] textarea');
55 | const text = await page.evaluate((el: any) => el.value, editorTextarea);
56 | expect(text).toBe(hotpinkCode);
57 | });
58 |
59 | test("should reset provider values and get the initial state of code and component", async ({
60 | page,
61 | }) => {
62 | await page.click('[data-testid="rv-reset"]');
63 | const editor = await page.evaluate(
64 | (el: any) => el.value,
65 | await page.$('[data-testid="rv-editor"] textarea'),
66 | );
67 | expect(editor).toBe(initialCode);
68 | const background = await page.$eval(
69 | "#example-btn",
70 | (e) => (e as any).style["background"],
71 | );
72 | expect(background).toBe("rgb(39, 110, 241)");
73 | });
74 | });
75 |
--------------------------------------------------------------------------------
/examples/__tests__/typescript.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Uber Technologies, Inc.
3 |
4 | This source code is licensed under the MIT license found in the
5 | LICENSE file in the root directory of this source tree.
6 | */
7 | import { test, expect } from "@playwright/test";
8 | import { urls } from "../const";
9 |
10 | test.describe("Typescript", () => {
11 | test("should compile the code and render component", async ({ page }) => {
12 | await page.goto(urls.liveCodeOnly);
13 | await page.waitForSelector("[data-storyloaded]");
14 | const inputCode = `() => {
15 | const num1: number = 13;
16 | const num2: number = 4;
17 | return num1 * num2;
18 | }`;
19 |
20 | await page.locator("textarea").first().fill(inputCode);
21 | await page.waitForTimeout(300); // waiting for debounce
22 | expect(await page.textContent("body")).toContain("52");
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/examples/__tests__/view.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Uber Technologies, Inc.
3 |
4 | This source code is licensed under the MIT license found in the
5 | LICENSE file in the root directory of this source tree.
6 | */
7 | import { test, expect } from "@playwright/test";
8 | import { urls } from "../const";
9 |
10 | test.describe("View", () => {
11 | test.beforeEach(async ({ page }) => {
12 | await page.goto(urls.view);
13 | await page.waitForSelector("[data-storyloaded]");
14 | });
15 |
16 | test('should render the button with "Hello" label', async ({ page }) => {
17 | expect(await page.textContent("button")).toContain("Hello");
18 | });
19 |
20 | test("should generate the correct code snippet", async ({ page }) => {
21 | const codeOutput = `import * as React from "react";
22 | import { Button } from "your-button-component";
23 |
24 | export default () => {
25 | return (
26 |
27 | );
28 | }`;
29 | const text = await page.evaluate(
30 | (el: any) => el.value,
31 | await page.$('[data-testid="rv-editor"] textarea'),
32 | );
33 | expect(text).toBe(codeOutput);
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/examples/advanced.stories.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Uber Technologies, Inc.
3 |
4 | This source code is licensed under the MIT license found in the
5 | LICENSE file in the root directory of this source tree.
6 | */
7 | import * as React from "react";
8 | import Theming from "./theming";
9 | import CustomProp from "./custom-prop";
10 |
11 | export default {
12 | title: "Advanced",
13 | };
14 |
15 | export const customProp = () => {
16 | return ;
17 | };
18 |
19 | export const theming = () => {
20 | return ;
21 | };
22 |
--------------------------------------------------------------------------------
/examples/basic.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Uber Technologies, Inc.
3 |
4 | This source code is licensed under the MIT license found in the
5 | LICENSE file in the root directory of this source tree.
6 | */
7 | import * as React from "react";
8 | import { Layout, H1, H2, P, Code, Inline } from "./layout/";
9 | import { Button, SIZE } from "./showcase-components/button";
10 |
11 | import {
12 | useView,
13 | Compiler,
14 | Knobs,
15 | Editor,
16 | Error,
17 | ActionButtons,
18 | Placeholder,
19 | PropTypes,
20 | } from "../src";
21 |
22 | const Basic = () => {
23 | const params = useView({
24 | componentName: "Button",
25 | props: {
26 | children: {
27 | value: "Hello",
28 | type: PropTypes.ReactNode,
29 | description: `Visible label.`,
30 | },
31 | size: {
32 | value: "SIZE.default",
33 | defaultValue: "SIZE.default",
34 | options: SIZE,
35 | type: PropTypes.Enum,
36 | description: "Defines the size of the button.",
37 | imports: {
38 | "your-button-component": {
39 | named: ["SIZE"],
40 | },
41 | },
42 | },
43 | onClick: {
44 | value: '() => alert("click")',
45 | type: PropTypes.Function,
46 | description: `Function called when button is clicked.`,
47 | },
48 | disabled: {
49 | value: false,
50 | type: PropTypes.Boolean,
51 | description: "Indicates that the button is disabled",
52 | },
53 | },
54 | scope: {
55 | Button,
56 | SIZE,
57 | },
58 | imports: {
59 | "your-button-component": {
60 | named: ["Button"],
61 | },
62 | },
63 | });
64 |
65 | return (
66 |
67 |
Basic example of useView
68 |
69 | This is our main{" "}
70 | hook based API.
71 | React View strictly separates the UI components from everything else so
72 | you can completely customize every aspect of the playground. If you want
73 | to start as quickly as possible, try the{" "}
74 | View component instead.
75 |
76 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | This is a basic example that demonstrates all basic features of React
88 | View. At the top, you can see the rendered component, followed by
89 | the middle section with knobs that lets you explore all component
90 | props, the edittable code snippet and finally some{" "}
91 | action buttons.
92 |
166 | useView expects a configuration describing your component and
167 | returns a data-structure that nicely fits into multiple UI components
168 | such as Compiler, Error, Knobs, Editor and Action Buttons. That gives
169 | you the maximum flexibility since you can swap any of these components
170 | for your own.
171 |
172 |
173 |
174 | Note that you never have to specify the code snippet since the code is
175 | auto-generated based on the rest of useView configuration and internal
176 | state.
177 |
178 |
179 |
180 | The biggest part of configuration is a list of props.
181 | You also have to explicitly define the scope (in this
182 | case, importing the Button and passing it through). On the other hand,
183 | the imports setting is completely optional. The imports
184 | appear at the top of auto-generated code. That can be nice for your
185 | users since they will be always able to copy paste a fully working
186 | example.{" "}
187 |
188 |
189 | );
190 | };
191 |
192 | export default Basic;
193 |
--------------------------------------------------------------------------------
/examples/const.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Uber Technologies, Inc.
3 |
4 | This source code is licensed under the MIT license found in the
5 | LICENSE file in the root directory of this source tree.
6 | */
7 | export const urls = {
8 | basic: "/?mode=preview&story=useview--basic",
9 | stateHook: "/?mode=preview&story=useview--state-hook",
10 | liveCodeOnly: "/?mode=preview&story=useview--live-code-only",
11 | typescript: "/?mode=preview&story=useview--typescript",
12 | view: "/?mode=preview&story=view--view",
13 | customProps: "/?mode=preview&story=advanced--custom-prop",
14 | theming: "/?mode=preview&story=advanced--theming",
15 | modal: "/?mode=preview&story=tests--modal",
16 | };
17 |
--------------------------------------------------------------------------------
/examples/custom-prop.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Uber Technologies, Inc.
3 |
4 | This source code is licensed under the MIT license found in the
5 | LICENSE file in the root directory of this source tree.
6 | */
7 | import * as React from "react";
8 | import template from "@babel/template";
9 |
10 | import { Layout, H1, P, Inline } from "./layout/";
11 | import { Rating } from "./showcase-components/rating";
12 |
13 | import {
14 | useView,
15 | Compiler,
16 | Knobs,
17 | Editor,
18 | Error,
19 | ActionButtons,
20 | Placeholder,
21 | PropTypes,
22 | useValueDebounce,
23 | } from "../src";
24 |
25 | export const customProps = {
26 | value: {
27 | // define how to convert value into an AST tree
28 | generate: (value: number) => {
29 | return (template.ast(String(value), { plugins: ["jsx"] }) as any)
30 | .expression;
31 | },
32 | // define how to convert the JSX attribute value into value
33 | parse: (code: string) => {
34 | return parseInt(code, 10);
35 | },
36 | },
37 | };
38 |
39 | // custom knob component
40 | const Slider: React.FC<{
41 | value: number;
42 | set: (val: number, propName: string) => void;
43 | }> = ({ value, set }) => {
44 | // debouncing the knob value so it's always interactive
45 | const [rangeValue, setRangeValue] = useValueDebounce(value, (val) =>
46 | set(val, "value"),
47 | );
48 | return (
49 |
50 |
78 |
79 | );
80 | };
81 |
82 | const StateHook = () => {
83 | const params = useView({
84 | componentName: "Rating",
85 | props: {
86 | value: {
87 | value: 3,
88 | // mark the prop as type custom so it's not processed by react-view
89 | type: PropTypes.Custom,
90 | description: `Rating value.`,
91 | stateful: true,
92 | },
93 | onChange: {
94 | value: "value => setValue(value)",
95 | type: PropTypes.Function,
96 | description: `Function called when rating value is changed.`,
97 | propHook: {
98 | what: "value",
99 | into: "value",
100 | },
101 | },
102 | },
103 | scope: {
104 | Rating,
105 | },
106 | imports: {
107 | "your-rating-component": {
108 | named: ["Rating"],
109 | },
110 | },
111 | customProps,
112 | });
113 |
114 | return (
115 |
116 |
Custom Props and Knobs
117 |
118 | React View supports many basic prop types out of the box.
119 | Obviously, any prop value can always be edited through an input (or a
120 | tiny code editor). That is no different than writing an actual code.
121 | Boring.
122 |
123 |
124 |
125 | However, many prop types can be more accessible with a specialized UI
126 |
127 | . For example, boolean is always translated into a
128 | checkbox and enum into an input radio or select (if we
129 | have too many options). Those are much nicer and faster to use than
130 | inputs.
131 |
132 |
133 | But what if you want to add something custom that we do not support yet?
134 | There is a customProp API that you can use. You can
135 | control both the knob and also the internal representation of the value.
136 | In this example, we add a pretty simple custom knob. We want to
137 | represent the value with an input slider.
138 |
139 |
144 |
145 |
149 |
150 |
151 |
152 |
153 |
154 | However, you can go much further. For example, our Base Web component
155 | library has this concept of{" "}
156 |
157 | overrides
158 |
159 | . It is a fairly complicated prop that exists on each component and lets
160 | you to customize every aspect of our components. So we have created a
161 | whole sub-playground to just better control the value of this single
162 | prop. Check the{" "}
163 |
164 | Style Overrides tab on the Button page
165 |
166 | .{" "}
167 |
168 |
169 | This is an advanced and very flexible API. For example, you have
170 | to be familiar with the concept of{" "}
171 | AST to
172 | use it. Check the source code of this page or main README for more
173 | details. We will add more docs over time.
174 |
175 |
176 | );
177 | };
178 |
179 | export default StateHook;
180 |
--------------------------------------------------------------------------------
/examples/layout/index.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Uber Technologies, Inc.
3 |
4 | This source code is licensed under the MIT license found in the
5 | LICENSE file in the root directory of this source tree.
6 | */
7 | import * as React from "react";
8 | import { Highlight } from "prism-react-renderer";
9 | import lightTheme from "../../src/light-theme";
10 |
11 | export const Layout: React.FC<{ children: React.ReactNode }> = ({
12 | children,
13 | }) => (
14 |
33 | The useView hook can be also used as a live editor only (no prop knobs
34 | or code generation). In this mode, it is very similar to{" "}
35 | react-live.{" "}
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | You can create your UI or re-use components from react-view (Editor,
44 | Error...). Optionally you can also add the action buttons:
45 |
46 |
47 |
48 | This time you do not need to configure a list of props. There are
49 | no knobs. However, since no code is auto-generated, you should probably
50 | set the intialCode so the user sees something besides
51 | an empty box.
52 |
81 | Note: All import statements in the editor are always taken out
82 | before compilation. They do not do anything. Our compiler does
83 | not understand modules (we do not have a bundler in our flow). So feel
84 | free to add them if beneficial for your users. All dependencies need to
85 | be passed through the scope prop (React is included
86 | automatically).
87 |
88 |
Accepted Code
89 |
90 | The compiler can also handle a React element or class (but we do
91 | not really use those anymore, do we?).
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | ...or pretty much anything that{" "}
100 |
101 | could be executed after the return statement of JS
102 | function.
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | );
112 | };
113 |
114 | export default CodeOnly;
115 |
--------------------------------------------------------------------------------
/examples/modal.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Uber Technologies, Inc.
3 |
4 | This source code is licensed under the MIT license found in the
5 | LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import * as React from "react";
9 | import { View, PropTypes } from "../src/index";
10 | import { Layout, H1, P } from "./layout/";
11 | import Modal from "./showcase-components/modal";
12 |
13 | const ModalExample = () => (
14 |
15 |
Modal example
16 |
17 | This is story was created for an e2e test. Reproduces this{" "}
18 | bug report.
19 |
75 | Not all components are as simple as buttons. The most of React
76 | components have some sort of state. For example, inputs have the{" "}
77 | value state. By default, React View treats everything as{" "}
78 |
79 | controlled components
80 |
81 | . So when you specify the list of props in useView, you will get the
82 | output like this:
83 |
84 | {`
85 | `}
86 |
87 | And that works. You can still update the value by changing the value
88 | knob or editing the code directly.{" "}
89 |
90 | However, you would not be able to interact with the component itself
91 | {" "}
92 | since the value is hard-coded - the component is controlled. The code
93 | above is also not very realistic. How often do we create non editable
94 | inputs?
95 |
96 |
97 | Fortunately, React View has special{" "}
98 |
99 | propHook
100 | {" "}
101 | and{" "}
102 |
103 | stateful
104 | {" "}
105 | settings so you can achieve full interactivity:
106 |
107 |
112 |
113 |
114 |
115 |
116 |
117 |
118 | The example above has its own internal value state (using{" "}
119 |
120 | React.useState
121 |
122 | ) and the value knob is now translated into its initial internal state.
123 | Now you can interact with the component itself and{" "}
124 | everything is still synchronized. Moreover, the code snippet now
125 | also better demonstrates the real-world usage.
126 |
203 | There are just two changes that we have to make compared to the{" "}
204 | basic example. First, we have
205 | to detach the value prop into an internal state. We simply add the{" "}
206 |
207 | stateful flag
208 |
209 | :
210 |
223 | At this point,{" "}
224 | the value is detached and rendered input is fully interactive.
225 | However, the changes are not synchronized with the rest of the
226 | playground. We need to give React View a slight hint:{" "}
227 |
240 | We have added the{" "}
241 |
242 | propHook.what
243 | {" "}
244 | and{" "}
245 |
246 | propHook.into
247 | {" "}
248 | in the onChange prop. We are telling React View{" "}
249 | what value it should use and into what
250 | stateful prop it should go. Note that this setting also depends on the
251 | initial value of onChange prop since React View
252 | secretly adds an instrumentation call into the body of{" "}
253 | e > setValue(e.target.value) function.
254 |
255 |
defaultValue
256 |
257 | Props can have a defaultValue. That is useful for an{" "}
258 | enum so the code generator knows when to skip the
259 | default option. Sometimes you can also have a boolean{" "}
260 | prop that treats undefined the opposite way to{" "}
261 | false. In the example above, this inverted behavior is
262 | demonstrated with the prop editable:
263 |
251 | Component libraries often have some theming system. It usually uses the{" "}
252 | React.Context and
253 | Provider/Consumer APIs. How that works? There is a list of global
254 | values (colors, fonts, spacing) propagated through the context and each
255 | component consumes these values through a consumer attached to that
256 | context.
257 |
258 |
259 | Often, you can also override these context values by adding an
260 | additional nested provider. Since this is a way how components can be
261 | visually adjusted,{" "}
262 | React View has a support for this Consumer/Provider pattern:
263 |
264 |
269 |
270 |
271 |
272 |
273 |
277 |
278 |
279 | The ThemeEditor is a custom built UI and utilizes the{" "}
280 | provider setting. You can see the default values that
281 | our Button component consumes. If you change any of
282 | those, the code generator will wrap the component with the{" "}
283 | ThemeProvider component and also add related imports.
284 |
285 |
286 | This is an advanced and very flexible API. For example, you have
287 | to be familiar with the concept of{" "}
288 | AST to
289 | use it. Check the source code of this page or main README for more
290 | details. We will add more docs over time.
291 |
292 |
293 | );
294 | };
295 |
296 | export default Theming;
297 |
--------------------------------------------------------------------------------
/examples/typescript.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Uber Technologies, Inc.
3 |
4 | This source code is licensed under the MIT license found in the
5 | LICENSE file in the root directory of this source tree.
6 | */
7 | import * as React from "react";
8 | import { Layout, H1, H2, P, Code, CompilerBox } from "./layout/";
9 |
10 | //@ts-ignore
11 | import presetTypescript from "@babel/preset-typescript";
12 |
13 | import { useView, Compiler, Editor, Error } from "../src/";
14 |
15 | const initialCode = `() => {
16 | const text: string = "Typed";
17 | return
26 | We use babel to compile the code that user passes to the editor. By
27 | default, we apply only{" "}
28 |
29 | @babel/preset-react
30 | {" "}
31 | but you can import and use additional babel plugins. Do you want to
32 | support Flow? Add{" "}
33 | @babel/preset-flow instead.
34 |
35 |
36 | In this case, we have added{" "}
37 |
38 | @babel/preset-typescript
39 | {" "}
40 | to support TypeScript. This preset strips out all the TypeScript code
41 | but it does not validate types.
42 |
43 |
44 |
45 |
46 |
47 |
48 |
Usage
49 |
50 | The compiler component accepts an optional array of presets. You should
51 | also set the editor language to TSX to get a proper syntax highlighting.
52 |
80 | Note: If you use react-view in the full mode (with the
81 | code-generation), we currently generate only plain JavaScript. The
82 | reason is that our examples simply do not need any type annotations
83 | since both TypeScript and Flow can infer 100% of them. So you can take
84 | the same output and copy paste it into a JS, TS or Flow codebase without
85 | any changes.
86 |
87 |
88 | );
89 | };
90 |
91 | export default TypescriptCodeOnly;
92 |
--------------------------------------------------------------------------------
/examples/use-view.stories.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Uber Technologies, Inc.
3 |
4 | This source code is licensed under the MIT license found in the
5 | LICENSE file in the root directory of this source tree.
6 | */
7 | import * as React from "react";
8 | import Basic from "./basic";
9 | import StateHook from "./state-hook";
10 | import LiveCodeOnly from "./live-code-only";
11 | import Typescript from "./typescript";
12 |
13 | export default {
14 | title: "useView",
15 | };
16 |
17 | export const basic = () => {
18 | return ;
19 | };
20 |
21 | export const stateHook = () => {
22 | return ;
23 | };
24 |
25 | export const liveCodeOnly = () => {
26 | return ;
27 | };
28 |
29 | export const typescript = () => {
30 | return ;
31 | };
32 |
--------------------------------------------------------------------------------
/examples/view.stories.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Uber Technologies, Inc.
3 |
4 | This source code is licensed under the MIT license found in the
5 | LICENSE file in the root directory of this source tree.
6 | */
7 | import * as React from "react";
8 | import View from "./view";
9 |
10 | export default {
11 | title: "View",
12 | };
13 |
14 | export const view = () => {
15 | return ;
16 | };
17 |
--------------------------------------------------------------------------------
/examples/view.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) Uber Technologies, Inc.
3 |
4 | This source code is licensed under the MIT license found in the
5 | LICENSE file in the root directory of this source tree.
6 | */
7 | import * as React from "react";
8 | import { Layout, H1, P, H2, Code, Inline } from "./layout";
9 |
10 | import { Button, SIZE } from "./showcase-components/button";
11 |
12 | import { View, PropTypes } from "../src";
13 |
14 | const ViewExample = () => (
15 |
16 |
View Component
17 |
18 | A single component that does it all. It is a tiny wrapper around
19 | the useView hook and composes
20 | all UI components into one thing. This might be an ideal solution if you
21 | do not want to visually tweak anything and just get started as quickly as
22 | possible.
23 |