Skip to content

An *accessible* webGL form component library for use with React Three Fiber

Notifications You must be signed in to change notification settings

JMBeresford/r3f-form

Repository files navigation

r3f-form

WARNING: UNDER HEAVY DEVELOPMENT

Each release will aim to be a fully functional and usable release, but breaking API changes WILL be likely for the forseeable future.

A webGL form component for use with React Three Fiber

image

This package aims to create a fully-functional and accessible <Form /> components that can be used within the @react-three/fiber ecosystem. Ultimately, the goal is to have fully functioning HTML <form>s -- with all viable input types -- rendered in webGL.

Current implementation binds webGL elements to the existing state and event systems of respective hidden HTML DOM elements. There is a heavy reliance on troika-three-text for text-rendering and selection/caret logic.

Note that r3f-form will only work within a React-Three-Fiber Canvas element. You must be using

as dependencies in your project to use r3f-form.

Install

npm install r3f-form
# or
yarn add r3f-form
# or
pnpm install r3f-form

How to use

Forms

In order to create a form, just wrap any relevant elements in a <Form>:

import { Form, Input, Label, Submit } from "r3f-form";

export function MyForm() {
  return (
    <Form>
      <Label text="username" />
      <Input name="username" />

      <Label text="password" />
      <Input name="password" type="password" />

      <Submit value="Login" />
    </Form>
  );
}

Note that each element in the form will require a name prop in order to be picked up in submission, just like in a normal DOM form element

The relevant inputs will be bound to respective DOM elements under the hood, and be rendered into the 3D scene like so:

image

You can define submission behavior just like with any old HTML <form>:

// redirect to a login script
<Form action="/login.php"></Form>;

// or handle it with a callback
const handleSubmit = (e: FormEvent) => {
  e.preventDefault();

  const data = new FormData(e.target);

  for (let [name, value] of data.entries()) {
    console.log(`${name}: ${value}`);
  }
};

<Form onSubmit={handleSubmit}></Form>;

Inputs

An editable text-box bound to an DOM <input> and represented in the webGL canvas.

type Props = {
  type?: "text" | "password";

  /** width of the container */
  width?: number;
  backgroundColor?: Color;
  selectionColor?: Color;
  backgroundOpacity?: number;

  /** [left/right , top/bottom] in THREE units, respectively
   *
   * note that height is implicitly defined by the capHeight of the rendered
   * text. The cap height is dependant on both the `textProps.font` being used and the
   * `textProps.fontSize` value
   */
  padding?: Vector2;
  cursorWidth?: number;

  /** 3D transformations */
  position: Vector3;
  rotation: Euler;
  scale: Vector3;

  // And ALL props available to DOM <input>s
};

Create a basic input field like so:

import { Input, Label } from "r3f-form";

export function MyInput() {
  return (
    <>
      <Label text="label" />
      <Input />
    </>
  );
}

image

You can access the value of the input via the onChange callback prop:

The callback is passed the ChangeEvent object from the underlying HTML <input>s change event on every change.

Read more about this event here

import { Input, Label } from "r3f-form";

export function MyInput() {
  const [username, setUsername] = React.useState("");
  // username will always contain the current value

  function handleChange(e) {
    setUsername(ev.target.value);
  }

  return (
    <>
      <Label text="Test Input" />
      <Input onChange={handleChange} />
    </>
  );
}

You can also create password inputs:

import { Input, Label } from "r3f-form";

export function MyPassword() {
  return (
    <>
      <Label text="Password" />
      <Input type="password" />
    </>
  );
}

image

Add custom padding to the text container:

import { Input, Label } from "r3f-form";

/*
 * padding: [horizontal padding, vertical padding] in THREE units
 */

export function MyInput() {
  return (
    <>
      <Label text="Label" />
      <Input padding={[0.05, 0.5]} />
    </>
  );
}

image


Textarea

type Props = {
  /** width of the container */
  width?: number;

  backgroundColor?: Color;
  backgroundOpacity?: number;
  selectionColor?: Color;

  /** [left/right , top/bottom] in THREE units, respectively
   *
   * note that height is implicitly defined by the capHeight of the rendered
   * text. The cap height is dependant on both the `textProps.font` being used
   * and the `textProps.fontSize` value
   */
  padding?: Vector2;
  cursorWidth?: number;

  /** 3D transformations */
  position: Vector3;
  rotation: Euler;
  scale: Vector3;

  // And ALL props available to DOM <textarea>s
};

Similar to the <Input /> component, you can also create a <Textarea /> like so:

import { Textarea, Label } from "r3f-form";

export function App() {
  return (
    <>
      <Label text="Default Textarea:" />
      <Textarea />
    </>
  );
}

image


Text

In order to configure the underlying troika-three-text instance that is responsible for rendering the actual text, you can use the <Text /> component.

There is a respective <InputText /> or <TextareaText /> component for both <Input />s and <Textarea />s.

For all configuration options, check out the troika docs.

Note that not all of troika's props are exposed on these <Text /> components

Change color and background opacity:

import { Input, Label, InputText } from "r3f-form";

export function App() {
  return (
    <>
      <Label text="Test Color/Opacity" />
      <Input backgroundOpacity={0.6} backgroundColor="black">
        <InputText color="red" />
      </Input>
    </>
  );
}

image

import { Input, Textarea, Label, InputText, TextareaText } from "r3f-form";

export function App() {
  return (
    <>
      <Label text="Test Color/Opacity" />
      <Input>
        <InputText color="#red" />
      </Input>

      <Label text="Test Textarea" />
      <Textarea>
        <TextareaText color="red" />
      </Textarea>
    </>
  );
}

Submit

Equivalent to a DOM <input type="submit" />, exposed as an independent component. By default it renders a button using the following props:

type Props = {
  value?: string;
  fontSize?: number;
  width?: number;
  height?: number;
  color?: Color;
  backgroundColor?: Color;

  /** 3D transformations */
  position: Vector3;
  rotation: Euler;
  scale: Vector3;

  // And ALL props available to DOM <input>s
};

Add a simple submit button to your forms like so:

<Form>
  <Label text="Username" />
  <Input name="username" />

  <Label text="Password" />
  <Input name="password" type="password" />

  <Submit value="Login" />
</Form>

image

While this provides a somewhat-customizable default button, the main purpose of this component is to provide a simple interface to use 3D elements to submit your forms. Any children passed in will submit the form on click. For example:

<Form>
  . . .
  <Submit value="submit">
    <MySubmitButton />
  </Submit>
</Form>

image

Clicking on the big red button would submit the <Form>

About

An *accessible* webGL form component library for use with React Three Fiber

Resources

Stars

Watchers

Forks

Packages

No packages published