diff --git a/.github/workflows/build_js.yml b/.github/workflows/build_js.yml
new file mode 100644
index 0000000..7f07382
--- /dev/null
+++ b/.github/workflows/build_js.yml
@@ -0,0 +1,25 @@
+name: Test
+on:
+ push:
+ pull_request:
+ workflow_dispatch:
+
+jobs:
+ build:
+ name: Test Wrapper
+ strategy:
+ fail-fast: false
+
+ runs-on: 'ubuntu-latest'
+
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 18
+ - name: Setup project
+ run: npm install
+ - name: Lint
+ run: npm run lint
+ - name: Test
+ run: npm run test
diff --git a/.github/workflows/dynamic-security.yml b/.github/workflows/dynamic-security.yml
new file mode 100644
index 0000000..26a424d
--- /dev/null
+++ b/.github/workflows/dynamic-security.yml
@@ -0,0 +1,19 @@
+name: update-security
+
+on:
+ push:
+ paths:
+ - SECURITY.md
+ branches:
+ - main
+ workflow_dispatch:
+
+jobs:
+ update-security:
+ permissions:
+ contents: write
+ pull-requests: write
+ pages: write
+ uses: thoughtbot/templates/.github/workflows/dynamic-security.yaml@main
+ secrets:
+ token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 3b7f80d..96ab72e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -63,4 +63,6 @@ dist/
package-lock.json
coverage/
-todo.txt
\ No newline at end of file
+todo.txt
+
+temp/
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..26ac540
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,7 @@
+{
+ "semi": false,
+ "singleQuote": true,
+ "printWidth": 80,
+ "useTabs": false,
+ "tabWidth": 2
+}
diff --git a/CODEOWNERS b/CODEOWNERS
new file mode 100644
index 0000000..0917974
--- /dev/null
+++ b/CODEOWNERS
@@ -0,0 +1,15 @@
+# Lines starting with '#' are comments.
+# Each line is a file pattern followed by one or more owners.
+
+# More details are here: https://help.github.com/articles/about-codeowners/
+
+# The '*' pattern is global owners.
+
+# Order is important. The last matching pattern has the most precedence.
+# The folders are ordered as follows:
+
+# In each subsection folders are ordered first by depth, then alphabetically.
+# This should make it easy to add new rules without breaking existing ones.
+
+# Global rule:
+* @jho406
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..848a59a
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,6 @@
+# Code of conduct
+
+By participating in this project, you agree to abide by the
+[thoughtbot code of conduct][1].
+
+[1]: https://thoughtbot.com/open-source-code-of-conduct
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..bcf5227
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Johny Ho
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..cfcfb10
--- /dev/null
+++ b/README.md
@@ -0,0 +1,133 @@
+# candy_wrapper
+
+'candy_wrapper's are lightweight wrapper components around popular UI libraries made to work with [form_props]. Easily
+use the power of Rails forms with any supported React UI library.
+
+## Component status
+
+Each component are meant to be copied from this repo to your own project and customized to your liking. There are no
+CLI tools to help. just copy and paste from github.
+
+| `form_props` helper | Component | Vanilla React | React Aria | NextUI |
+| :---------------------------- | :--------------------- | :----------------- | :------------------- | :------------------- |
+| `f.text_field` | Checkbox | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.collection_check_boxes` | CollectionCheckboxes | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.collection_radio_buttons` | CollectionRadioButtons | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.color_field` | ColorField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.date_field` | DateField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.email_field` | EmailField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| | FieldError | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.month_field` | MonthField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.number_field` | NumberField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.password_field` | PasswordField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.range_field` | RangeField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.search_field` | SearchField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.select` | Select | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.tel_field` | TelField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.text_field` | TextField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.time_field` | TimeField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.url_field` | UrlField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.text_area` | TextArea | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.grouped_collection_select` | Select | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.weekday_select` | Select | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.time_zone_select` | Select | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+
+## Installation
+
+```
+npm install -D candy_wrapper
+```
+
+Go to the wrapper directory in this repo and copy the wrappers for the UI library of your choice into your project.
+
+# Usage
+
+Once you've copied the components to your project. Use [form_props] to build your form:
+
+```ruby
+json.newPostForm do
+ form_props(@post) do |f|
+ f.text_field :title
+ f.submit
+ end
+end
+```
+
+This would create a payload that looks something this:
+
+```js
+{
+ someForm: {
+ props: {
+ id: "create-post",
+ action: "/posts/123",
+ acceptCharset: "UTF-8",
+ method: "post"
+ },
+ extras: {
+ method: {
+ name: "_method",
+ type: "hidden",
+ defaultValue: "patch",
+ autoComplete: "off"
+ },
+ utf8: {
+ name: "utf8",
+ type: "hidden",
+ defaultValue: "\u0026#x2713;",
+ autoComplete: "off"
+ }
+ csrf: {
+ name: "utf8",
+ type: "authenticity_token",
+ defaultValue: "SomeTOken!23$",
+ autoComplete: "off"
+ }
+ },
+ inputs: {
+ title: {name: "post[title]", id: "post_title", type: "text", defaultValue: "hello"},
+ submit: {type: "submit", value: "Update a Post"}
+ }
+ }
+}
+```
+
+Take the payload and pass it to the wrapper:
+
+```js
+import {Form, TextField} from './copied_components'
+
+const {form, extras, inputs} = newPostForm
+
+
+```
+
+## Server errors
+
+Each wrapper comes with inline support for server errors which renders a FieldError
+underneath the input.
+
+```js
+import {Form, TextField} from './copied_components'
+
+const validationErrors = {
+ full_title: "Invalid length"
+}
+
+const {form, extras, inputs} = newPostForm
+
+
+```
+
+## Contributors
+
+Thank you, [contributors]!
+
+[contributors]: https://github.com/thoughtbot/candy_wrapper/graphs/contributors
+[form_props]: https://github.com/thoughtbot/form_props
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..af66180
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,20 @@
+
+# Security Policy
+
+## Supported Versions
+
+Only the the latest version of this project is supported at a given time. If
+you find a security issue with an older version, please try updating to the
+latest version first.
+
+If for some reason you can't update to the latest version, please let us know
+your reasons so that we can have a better understanding of your situation.
+
+## Reporting a Vulnerability
+
+For security inquiries or vulnerability reports, visit
+.
+
+If you have any suggestions to improve this policy, visit .
+
+
diff --git a/package.json b/package.json
index 7b38fcf..74ba72e 100644
--- a/package.json
+++ b/package.json
@@ -1,13 +1,14 @@
{
- "name": "@thoughtbot/candywrappa",
+ "name": "@thoughtbot/candy_wrapper",
"version": "0.0.1",
- "description": "bare forms",
+ "description": "Use rails forms with popular react UI libraries",
"scripts": {
"build": "tsup",
+ "build:wrappers": "tsc --project ./wrappers/tsconfig.json && rm -r wrappers/js/* && mv -v temp/wrappers/ts/* wrappers/js",
"dev": "tsup --watch",
"clean": "rm -rf dist",
"lint": "run-p lint:eslint lint:types lint:prettier",
- "lint:eslint": "eslint --max-warnings=0 --ext js,jsx,ts,tsx ./candywrappa",
+ "lint:eslint": "eslint --max-warnings=0 --ext js,jsx,ts,tsx ./candy_wrapper",
"lint:prettier": "prettier --check '**/*' --ignore-unknown",
"lint:types": "tsc",
"fix:prettier": "prettier --write '**/*' --ignore-unknown",
@@ -20,25 +21,20 @@
},
"repository": {
"type": "git",
- "url": "git+https://github.com/thoughtbot/form_props.git"
+ "url": "git+https://github.com/thoughtbot/candy_wrapper.git"
},
"author": "Johny Ho",
- "main": "dist/cjs/form_props.cjs",
- "module": "dist/form_props.mjs",
- "types": "dist/form_props.d.mts",
+ "main": "dist/cjs/candy_wrapper.cjs",
+ "module": "dist/candy_wrapper.mjs",
+ "types": "dist/candy_wrapper.d.mts",
"exports": {
- "./package.json": "./package.json",
- "./components": {
- "types": "./dist/form_props.d.mts",
- "import": "./dist/form_props.mjs",
- "default": "./dist/cjs/form_props.cjs"
- }
+ "./package.json": "./package.json"
},
"license": "MIT",
"bugs": {
- "url": "https://github.com/thoughtbot/form_props/issues"
+ "url": "https://github.com/thoughtbot/candy_wrapper/issues"
},
- "homepage": "https://github.com/thoughtbot/form_props#readme",
+ "homepage": "https://github.com/thoughtbot/candy_wrapper#readme",
"devDependencies": {
"@nextui-org/react": "^2.4.8",
"@react-aria/test-utils": "^1.0.0-alpha.2",
@@ -63,16 +59,10 @@
"typedoc-plugin-markdown": "^4.2.3",
"typedoc-plugin-missing-exports": "^3.0.0",
"typescript": "^5.5.3",
- "vitest": "^2.0.2"
+ "vitest": "^2.0.2",
+ "@types/react": "^18.3.12"
},
"peerDependencies": {
- "react": "^18.3.1"
- },
- "dependencies": {
- "@types/react": "^18.3.12",
- "@types/react-dom": "^18.3.1",
- "react-aria-components": "^1.4.1",
- "react-stately": "^3.33.0",
- "yarn": "^1.22.22"
+ "react": "^18 || ^19"
}
}
diff --git a/rails/CheckBox.js b/rails/CheckBox.js
deleted file mode 100644
index ab1e641..0000000
--- a/rails/CheckBox.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import * as React from 'react'
-
-export default ({
- includeHidden = true,
- name = null,
- uncheckedValue = null,
- children,
- ...rest
-}) => {
- return (
- <>
- {includeHidden && (
-
- )}
-
- {children}
-
- >
- )
-}
diff --git a/rails/CollectionCheckBoxes.js b/rails/CollectionCheckBoxes.js
deleted file mode 100644
index f3a4aac..0000000
--- a/rails/CollectionCheckBoxes.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import * as React from 'react'
-
-export default ({ includeHidden = true, collection = [], ...rest }) => {
- if (collection.length == 0) {
- return null
- }
-
- const checkboxes = collection.map((options) => {
- const { id } = options
- const { uncheckedValue, label, ...inputOptions } = options
-
- return (
- <>
-
-
- >
- )
- })
-
- const { name } = collection[0]
-
- return (
- <>
- {includeHidden && (
-
- )}
- {checkboxes}
- >
- )
-}
diff --git a/rails/CollectionRadioButtons.js b/rails/CollectionRadioButtons.js
deleted file mode 100644
index dc91de5..0000000
--- a/rails/CollectionRadioButtons.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import * as React from 'react'
-
-export default ({ includeHidden = true, collection = [], ...rest }) => {
- if (collection.length == 0) {
- return null
- }
-
- const checkboxes = collection.map((options) => {
- const { id } = options
- const { label, ...inputOptions } = options
-
- return (
- <>
-
-
- >
- )
- })
-
- const { name } = collection[0]
-
- return (
- <>
- {includeHidden && (
-
- )}
- {checkboxes}
- >
- )
-}
diff --git a/rails/Select.js b/rails/Select.js
deleted file mode 100644
index 27c1a16..0000000
--- a/rails/Select.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import * as React from 'react'
-
-export default ({
- includeHidden = true,
- name = null,
- id = null,
- children,
- options = [],
- multiple = false,
- disabled = false,
- type = null,
- ...rest
-}) => {
- const addHidden = includeHidden && multiple
-
- const optionElements = options.map((option) => {
- if (option.hasOwnProperty('options')) {
- return (
-
- )
- } else {
- return
- }
- })
-
- return (
- <>
- {addHidden && (
-
- )}
-
- >
- )
-}
diff --git a/src/README.md b/src/README.md
new file mode 100644
index 0000000..5fa0c6b
--- /dev/null
+++ b/src/README.md
@@ -0,0 +1,133 @@
+# candy_wrapper
+
+`candy_wrapper`s are lightweight wrapper components around popular UI libraries made to work with [form_props]. Easily
+use the power of Rails forms with any supported React UI library.
+
+## Component status
+
+Each component are meant to be copied from this repo to your own project and customized to your liking. There are no
+CLI tools to help. just copy and paste from github.
+
+| `form_props` helper | Component | Vanilla React | React Aria | NextUI |
+| :---------------------------- | :--------------------- | :----------------- | :------------------- | :------------------- |
+| `f.text_field` | Checkbox | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.collection_check_boxes` | CollectionCheckboxes | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.collection_radio_buttons` | CollectionRadioButtons | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.color_field` | ColorField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.date_field` | DateField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.email_field` | EmailField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| | FieldError | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.month_field` | MonthField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.number_field` | NumberField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.password_field` | PasswordField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.range_field` | RangeField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.search_field` | SearchField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.select` | Select | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.tel_field` | TelField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.text_field` | TextField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.time_field` | TimeField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.url_field` | UrlField | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.text_area` | TextArea | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.grouped_collection_select` | Select | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.weekday_select` | Select | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+| `f.time_zone_select` | Select | :heavy_check_mark: | :white_large_square: | :white_large_square: |
+
+## Installation
+
+```
+npm install -D @thoughtbot/candy_wrapper
+```
+
+Go to the wrapper directory in this repo and copy the wrappers for the UI library of your choice into your project.
+
+## Usage
+
+Once you've copied the components to your project. Use [form_props] to build your form:
+
+```ruby
+json.newPostForm do
+ form_props(@post) do |f|
+ f.text_field :title
+ f.submit
+ end
+end
+```
+
+This would create a payload that looks something this:
+
+```js
+{
+ someForm: {
+ props: {
+ id: "create-post",
+ action: "/posts/123",
+ acceptCharset: "UTF-8",
+ method: "post"
+ },
+ extras: {
+ method: {
+ name: "_method",
+ type: "hidden",
+ defaultValue: "patch",
+ autoComplete: "off"
+ },
+ utf8: {
+ name: "utf8",
+ type: "hidden",
+ defaultValue: "\u0026#x2713;",
+ autoComplete: "off"
+ }
+ csrf: {
+ name: "utf8",
+ type: "authenticity_token",
+ defaultValue: "SomeTOken!23$",
+ autoComplete: "off"
+ }
+ },
+ inputs: {
+ title: {name: "post[title]", id: "post_title", type: "text", defaultValue: "hello"},
+ submit: {type: "submit", value: "Update a Post"}
+ }
+ }
+}
+```
+
+Take the payload and pass it to the wrapper:
+
+```js
+import {Form, TextField} from './copied_components'
+
+const {form, extras, inputs} = newPostForm
+
+
+```
+
+## Server errors
+
+Each wrapper comes with inline support for server errors which renders a FieldError
+underneath the input.
+
+```js
+import {Form, TextField} from './copied_components'
+
+const validationErrors = {
+ full_title: "Invalid length"
+}
+
+const {form, extras, inputs} = newPostForm
+
+
+```
+
+## Contributors
+
+Thank you, [contributors]!
+
+[contributors]: https://github.com/thoughtbot/candy_wrapper/graphs/contributors
+[form_props]: https://github.com/thoughtbot/form_props
diff --git a/src/hooks.ts b/src/hooks.ts
new file mode 100644
index 0000000..2e8153c
--- /dev/null
+++ b/src/hooks.ts
@@ -0,0 +1,17 @@
+import { createContext, useContext, useMemo } from 'react'
+import { ValidationErrors } from './types'
+
+export const ValidationContext = createContext({})
+
+export const useErrorKeyValidation = ({
+ errorKey,
+}: {
+ errorKey: string
+ name: string
+}) => {
+ const errors = useContext(ValidationContext)
+
+ return useMemo(() => {
+ return errors[errorKey]
+ }, [errors, errorKey])
+}
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..3850a8e
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,2 @@
+export * from './types'
+export * from './hooks'
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 0000000..dbe5963
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,165 @@
+export interface BaseInputField {
+ size?: number
+ maxLength?: number
+ value?: string
+ defaultValue?: string
+ name: string
+ id?: string
+}
+
+export interface TextField extends BaseInputField {
+ readonly type: 'text'
+}
+
+export interface EmailField extends BaseInputField {
+ readonly type: 'email'
+}
+
+export interface FileField extends Omit {
+ readonly type: 'file'
+}
+
+export interface DateField extends BaseInputField {
+ readonly type: 'date'
+ min?: string
+ max?: string
+}
+
+export interface DateTimeLocalField extends BaseInputField {
+ readonly type: 'datetime-local'
+ min?: string
+ max?: string
+}
+
+export interface ColorField extends BaseInputField {
+ readonly type: 'color'
+}
+
+export interface HiddenField extends BaseInputField {
+ readonly type: 'hidden'
+}
+
+export interface MonthField extends BaseInputField {
+ readonly type: 'month'
+}
+
+export interface NumberField extends BaseInputField {
+ readonly type: 'number'
+ max?: number
+ min?: number
+}
+
+export interface PasswordField extends BaseInputField {
+ readonly type: 'password'
+}
+
+export interface SearchField extends BaseInputField {
+ readonly type: 'search'
+
+ // this no longer exist in the HTML standard
+ autosave?: string
+ results?: number
+
+ // this no longer exist in the HTML standard
+ onsearch: string
+ incremental?: boolean
+}
+
+export interface TelField extends BaseInputField {
+ readonly type: 'tel'
+}
+
+export interface UrlField extends BaseInputField {
+ readonly type: 'url'
+}
+
+export interface RangeField extends BaseInputField {
+ readonly type: 'range'
+}
+
+export interface TimeField extends BaseInputField {
+ readonly type: 'time'
+}
+
+export interface CheckboxField {
+ readonly type: 'checkbox'
+ includeHidden: boolean
+ name: string
+ id?: string
+
+ // todo: changed to be .to_s in
+ uncheckedValue: string
+
+ value: string
+ checked?: boolean
+ defaultChecked?: boolean
+}
+
+export interface CheckboxFieldWithLabel extends CheckboxField {
+ label: string
+}
+
+export interface CollectionCheckboxesField {
+ collection: CheckboxFieldWithLabel[]
+ includeHidden: boolean
+}
+
+export interface RadioButtonField {
+ readonly type: 'radio'
+ name: string
+ id?: string
+
+ value: string
+ checked?: boolean
+ defaultChecked?: boolean
+}
+
+export interface RadioButtonFieldWithLabel extends CheckboxField {
+ label: string
+}
+
+export interface CollectionRadioButtonsField {
+ collection: RadioButtonFieldWithLabel[]
+ includeHidden: boolean
+}
+
+export interface TextArea {
+ cols?: number
+ rows?: number
+ readonly type: 'textarea'
+ value?: string
+ defaultValue?: string
+ name: string
+ id?: string
+}
+
+export interface SubmitButton {
+ readonly type: 'submit'
+ text: string
+ name: string
+}
+
+export interface SelectOption {
+ value: string
+ label: string
+ disabled?: boolean
+}
+
+export interface SelectOptionGroup {
+ label: string
+ options: SelectOption[]
+}
+
+export interface Select {
+ readonly type: 'select'
+ id?: string
+ name: string
+ defaultValue?: string
+ value?: string
+ multiple?: boolean
+ includeHidden: boolean
+ options: (SelectOption | SelectOptionGroup)[]
+}
+
+export type ValidationError = string | string[]
+export type ValidationErrors = Record
diff --git a/tsconfig.json b/tsconfig.json
index 4011da6..1b1e23c 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -4,12 +4,12 @@
"moduleResolution": "Node",
"strict": true,
"strictPropertyInitialization": false,
- "jsx": "react",
+ "jsx": "preserve",
"allowJs": false,
"target": "ES2021",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
- "noEmit": true
+ "outDir": "./dist",
},
- "include": ["./candywrappa/**/*"]
+ "include": ["./src/**/*"]
}
diff --git a/tsup.config.ts b/tsup.config.ts
index 27be633..20f7ebd 100644
--- a/tsup.config.ts
+++ b/tsup.config.ts
@@ -4,8 +4,7 @@ import { defineConfig } from 'tsup'
export default defineConfig((options) => {
const commonOptions: Partial = {
entry: {
- superglue: 'lib/index.tsx',
- action_creators: 'lib/action_creators/index.ts',
+ candy_wrapper: 'src/index.ts',
},
sourcemap: true,
...options,
diff --git a/types.ts b/types.ts
deleted file mode 100644
index ee16063..0000000
--- a/types.ts
+++ /dev/null
@@ -1,498 +0,0 @@
-import { ReactNode } from 'react'
-
-type JSONObject = {
- [key: string]: JSONValue
-}
-type JSONPrimitive = string | number | boolean | null | undefined
-type JSONMappable = JSONValue[] | JSONObject
-type JSONValue = JSONPrimitive | JSONMappable
-
-export interface InputBase {
- id?: string
- className?: string
- name?: string
- errorKey?: string
- required?: boolean
- // type: string
- // value?: JSONValue
- // defaultValue?: JSONValue
- children: ReactNode
- // [key: string]: JSONValue; for addiotional html elements
-}
-
-export interface TextFieldProps extends InputBase {
- value?: string
- label?: string
- defaultValue?: string
- type: string
- // readonly type: 'text'
-}
-
-export type TextAreaProps = TextFieldProps
-
-export interface EmailFieldProps extends InputBase {
- errorKey?: string
- value?: string
- label?: string
- required?: boolean
- defaultValue?: string
- readonly type: 'email'
-}
-
-export interface TelFieldProps extends InputBase {
- errorKey?: string
- value?: string
- label?: string
- required?: boolean
- defaultValue?: string
- readonly type: 'tel'
-}
-
-export interface ColorFieldProps extends InputBase {
- errorKey?: string
- value?: string
- label?: string
- required?: boolean
- defaultValue?: string
- readonly type: 'color'
-}
-
-export interface FileField extends InputBase {
- readonly type: 'file'
-}
-
-export interface HiddenField extends InputBase {
- value?: string
- name: string
- defaultValue?: string
- readonly type: 'hidden'
- readonly autoComplete: 'off'
-}
-
-export interface NumberFieldProps extends InputBase {
- readonly type: 'number'
- errorKey?: string
- value?: string
- label?: string
- required?: boolean
- defaultValue?: string
- min?: number
- max?: number
- step?: number
-}
-
-export interface RangeFieldProps extends Omit {
- readonly type: 'range'
-}
-
-export interface TimeFieldProps extends InputBase {
- readonly type: 'time'
- errorKey?: string
- value?: string
- label?: string
- required?: boolean
- defaultValue?: string
- min?: string
- max?: string
- step?: number
-}
-
-export interface RangeField extends InputBase {
- readonly type: 'range'
-}
-
-export interface PasswordFieldProps extends InputBase {
- errorKey?: string
- value?: string
- label?: string
- required?: boolean
- defaultValue?: string
- readonly type: 'password'
-}
-
-export interface TelField extends InputBase {
- readonly type: 'tel'
-}
-
-export interface UrlField extends InputBase {
- readonly type: 'tel'
-}
-
-export interface DateFieldProps extends InputBase {
- errorKey?: string
- value?: string
- label?: string
- required?: boolean
- defaultValue?: string
- min?: string
- max?: string
- readonly type: 'date'
-}
-
-export interface DatetimeLocalFieldProps extends Omit {
- readonly type: 'datetime-local'
-}
-
-export interface SearchFieldProps extends InputBase {
- readonly type: 'search'
- errorKey?: string
- value?: string
- label?: string
- required?: boolean
- defaultValue?: string
-}
-
-export interface CheckboxInputProps {
- readonly type: 'checkbox'
- required?: boolean
- name?: string
- checked?: boolean
- defaultChecked?: boolean
- id?: string
- className?: string
- value: string
-}
-
-export interface CheckboxFieldProps extends CheckboxInputProps {
- errorKey?: string
- label?: string
- uncheckedValue: string
- includeHidden: boolean
-}
-
-export interface RadioButtonProps {
- readonly type: 'radio'
- checked?: boolean
- defaultChecked?: boolean
- // uncheckedValue: string
- // includeHidden: boolean
- id?: string
- name?: string
- className?: string
- value: string
- // defaultValue?: JSONValue
-}
-
-export interface RadioButtonWithLabel extends RadioButtonProps {
- label?: string
-}
-
-export interface SelectOption {
- value: string
- label: string
- disabled?: boolean
-}
-
-export interface SelectOptionGroup {
- label: string
- options: SelectOption[]
-}
-
-export interface SelectBase {
- readonly type?: 'select'
- id?: string
- className?: string
- name?: string
- required?: boolean
- defaultValue?: string
- value?: string
- multiple?: boolean
- disabled?: boolean
- includeHidden: boolean
- options: (SelectOption | SelectOptionGroup)[]
-}
-
-export interface SelectProps extends SelectBase {
- label?: string
- errorKey?: string
-}
-
-export interface CollectionSelect extends SelectBase {}
-
-export interface GroupedCollectionSelect extends SelectBase {
- options: []
-}
-
-export interface CollectionCheckboxesInputProps {
- collection: (CheckboxInputProps & { label: string })[]
- includeHidden: boolean
- // add documentation about required not being a thing...
-}
-
-export interface CollectionCheckboxesFieldProps
- extends CollectionCheckboxesInputProps {
- errorKey?: string
- label?: string
- // value?: string | undefined
- id?: string
- className?: string
-}
-
-export interface CollectionRadioButtonsProps {
- collection: RadioButtonWithLabel[]
- name?: string
- includeHidden: boolean
- errorKey?: string
- required?: boolean
- label?: string
-}
-
-// export type AllInputs =
-// | Select
-// | CollectionRadioButtonsProps
-// | CollectionCheckboxesProps
-// | GroupedCollectionSelect
-// | CollectionSelect
-// | CheckboxProps
-// | RadioButtonProps
-// | FileField
-// | TelField
-// | EmailFieldProps
-// | ColorFieldProps
-
-// export interface FormElementProps {
-// id?: string
-// className?: string
-// multipart?: boolean
-// method?: 'dialog' | 'get' | 'post'
-// action?: string
-// acceptCharset?: string
-// encType?:
-// | 'application/x-www-form-urlencoded'
-// | 'multipart/form-data'
-// | 'text/plain'
-// [key: string]: any
-// }
-
-// todo: is it a string of arrays??
-export type FlashErrors = Record
-export type Extras = Record
-
-// export interface FormProps {
-// extras: Record
-// inputs: Type
-// form: FormElementProps
-// flashErrors: FormElementProps
-// children: ReactNode
-// }
-
-export interface SubmitButtonProps {
- type: 'submit'
- text: string
- name: string
-}
-
-// TextInput
-// nils will always become undefined
-// -size - always and a;ways number
-// - type - safe ot be always
-// - value always for , except when the input is a file
-// --- always string, unless its somehow an array. When is it an array??
-// - id is optional... due to https://github.com/rails/rails/blob/c7de35a41bd7255249c9a5750e6a6edf75e61c82/actionview/lib/action_view/helpers/tags/base.rb#L96
-// - name is always there.
-// -
-
-// for max length
-// need to add a story to make this equal with mdn
-// If the value of the type attribute is text, email, search, password, tel, or url, this attribute specifies the maximum number of characters (in Unicode code points) that the user can enter; for other control types, it is ignored.
-
-export interface RailsTextField {
- type: 'text'
- size?: number
- maxLength?: number
- value?: string
- defaultValue?: string
- name: string
- id?: string
- // [key: string]: //; for addiotional html elements
-}
-
-// export interface TextFieldProps2 extends RailsTextField {
-// label: string
-// errorKey: string
-// }
-
-export interface RailsEmailField extends Omit {
- type: 'email'
-}
-
-export interface EmailFieldProps2 extends RailsTextField {
- label: string
- errorKey?: string
-}
-
-export interface RailsFileField extends Omit {
- /// is this right?? omit?
- type: 'file'
-}
-
-export interface RailsDateField extends Omit {
- type: 'date'
- min?: string
- max?: string
-}
-
-export interface DateFieldProps2 extends RailsDateField {
- label: string
- errorKey?: string
-}
-
-export interface RailsDateTimeLocalField extends Omit {
- type: 'datetime-local'
- min?: string
- max?: string
-}
-
-export interface RailsColorField extends Omit {
- type: 'color'
-}
-
-export interface ColorFieldProps2 extends RailsColorField {
- label: string
- errorKey?: string
-}
-
-export interface RailsHiddenField extends Omit {
- type: 'hidden'
-}
-
-export interface RailsMonthField extends Omit {
- type: 'month'
-}
-
-export interface RailsNumberField extends Omit {
- type: 'number'
- max?: number
- min?: number
-}
-
-export interface RailsPasswordField extends Omit {
- type: 'password'
-}
-
-export interface RailsSearchField extends Omit {
- type: 'search'
-
- // this no longer exist in the HTML standard
- autosave?: string
- results?: number
-
- // this no longer exist in the HTML standard
- onsearch: string
- incremental?: boolean
-}
-
-export interface RailsTelField extends Omit {
- type: 'tel'
-}
-
-export interface RailsUrlField extends Omit {
- type: 'url'
-}
-
-export interface RailsRangeField extends Omit {
- type: 'range'
-}
-
-export interface RailsTimeField extends Omit {
- type: 'time'
-}
-
-// value
-export interface RailsCheckboxField {
- type: 'checkbox'
- includeHidden: boolean // already defaulted to be true
- name: string
- id?: string
-
- // changed to be .to_s in rails
- uncheckedValue: string
-
- value: string
- checked?: boolean
- defaultChecked?: boolean
- [key: string]: JSONValue //; for addiotional html elements
-}
-
-export interface CheckboxFieldProps2 extends RailsCheckboxField {
- label: string
-}
-
-export interface RailsCheckboxFieldWithLabel extends RailsCheckboxField {
- label: string
-}
-
-export interface RailsCollectionCheckboxesField {
- collection: RailsCheckboxFieldWithLabel[]
- includeHidden: boolean
- // [key: string]: JSONValue //; for addiotional html elements
-}
-
-export interface RailsRadioButtonField {
- type: 'radio'
- name: string
- id?: string
-
- value: string
- checked?: boolean
- defaultChecked?: boolean
- [key: string]: JSONValue //; for addiotional html elements
-}
-
-export interface RailsRadioButtonFieldWithLabel extends RailsCheckboxField {
- label: string
-}
-
-export interface RailsCollectionRadioButtonsField {
- collection: RailsRadioButtonFieldWithLabel[]
- includeHidden: boolean
- [key: string]: JSONValue //; for addiotional html elements
-}
-
-// export interface CollectionCheckboxesFieldProps2
-// extends RailsCollectionRadioButtonsField {
-// errorKey?: string
-// label: string
-// }
-
-export interface RailsTextArea {
- cols?: number
- rows?: number
- type: 'text'
- value?: string
- defaultValue?: string
- name: string
- id?: string
- [key: string]: JSONValue //; for addiotional html elements
-}
-
-export interface RailsSubmitButton {
- type: 'submit'
- text: string
- name: string
- [key: string]: JSONValue //; for addiotional html elements
-}
-
-export interface RailsSelectOption {
- value: string
- label: string
- disabled?: boolean
-}
-
-export interface RailsSelectOptionGroup {
- label: string
- options: SelectOption[]
-}
-
-export interface RailsSelect {
- readonly type?: 'select'
- id?: string
- name: string
- // required?: boolean
- defaultValue?: string
- value?: string
- multiple?: boolean
- // disabled?: boolean
- includeHidden: boolean
- options: (RailsSelectOption | RailsSelectOptionGroup)[]
-}
diff --git a/vitest.config.ts b/vitest.config.ts
index ad19943..d21c94c 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -1,4 +1,5 @@
import react from '@vitejs/plugin-react'
+import path from 'path'
import { defineConfig } from 'vitest/config'
export default defineConfig({
@@ -8,6 +9,11 @@ export default defineConfig({
clearMocks: true,
restoreMocks: true,
globals: true,
- setupFiles: ['./candywrappa/setup.js'],
+ setupFiles: ['./setup.js'],
+ },
+ resolve: {
+ alias: {
+ '@thoughtbot/candy_wrapper': path.resolve(__dirname, './src/index'),
+ },
},
})
diff --git a/wrappers/js/basic/index.jsx b/wrappers/js/basic/index.jsx
new file mode 100644
index 0000000..3c6fd4f
--- /dev/null
+++ b/wrappers/js/basic/index.jsx
@@ -0,0 +1,266 @@
+/**
+ * Vanilla is a minimum set of components wrapped around regular HTML tags.
+ * It works with the output from [FormProps](https://github.com/thoughtbot/form_props).
+ *
+ * There is no style and structured with bare necessities. You should modify
+ * these components to fit your design needs.
+ */
+import React, { useContext } from 'react';
+import { ValidationContext, } from '@thoughtbot/candy_wrapper';
+/**
+ * Extras renders the hidden inputs generated by form_props.
+ *
+ * Its meant to be used with a form component and renders hidden values for
+ * utf8, crsf_token, _method
+ */
+export const Extras = (hiddenInputAttributes) => {
+ const hiddenProps = Object.values(hiddenInputAttributes);
+ const hiddenInputs = hiddenProps.map((props) => ());
+ return <>{hiddenInputs}>;
+};
+/**
+ * A basic form component that supports inline errors.
+ *
+ * It's meant to be used with FormProps and mimics the ways that
+ * Rails forms are generated.
+ */
+export const Form = ({ extras, validationErrors, children, ...props }) => {
+ return ();
+};
+/**
+ * An inline error component.
+ *
+ * When a Field has an error, this will show below the label and input.
+ * Please modify this to your liking.
+ */
+export const FieldError = ({ errorKey }) => {
+ if (!errorKey) {
+ return null;
+ }
+ const errors = useContext(ValidationContext);
+ const hasErrors = errorKey && errors[errorKey];
+ if (!hasErrors) {
+ return null;
+ }
+ const errorMessages = Array.isArray(errors[errorKey])
+ ? errors[errorKey]
+ : [errors[errorKey]];
+ return {errorMessages.join(' ')};
+};
+/**
+ * A Field component.
+ *
+ * Combines a label, input and a FieldError. Please modify this to your liking.
+ */
+export const FieldBase = ({ label, errorKey, children, ...props }) => {
+ return (<>
+
+ {children || }
+
+ >);
+};
+export const Checkbox = ({ type: _type, includeHidden, uncheckedValue, errorKey, ...rest }) => {
+ const { name } = rest;
+ return (
+ {includeHidden && ()}
+
+ );
+};
+/**
+ * A collection checkbox component.
+ *
+ * Designed to work with a payload form_props's [collection_check_boxes helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#collection-select).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
+export const CollectionCheckboxes = ({ includeHidden, collection, label, errorKey, }) => {
+ if (collection.length == 0) {
+ return null;
+ }
+ const checkboxes = collection.map((options) => {
+ return ;
+ });
+ const { name } = collection[0];
+ return (<>
+ {includeHidden && ()}
+
+ {checkboxes}
+
+ >);
+};
+/**
+ * A collection radio button component.
+ *
+ * Designed to work with a payload form_props's [collection_radio_buttons helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#collection-select).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
+export const CollectionRadioButtons = ({ includeHidden, collection, label, errorKey, }) => {
+ if (collection.length == 0) {
+ return null;
+ }
+ const radioButtons = collection.map((options) => {
+ return (
+
+
+
);
+ });
+ const { name } = collection[0];
+ return (<>
+ {includeHidden && ()}
+
+ {radioButtons}
+
+ >);
+};
+/**
+ * A text field component.
+ *
+ * Designed to work with a payload form_props's [text_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#text-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
+export const TextField = ({ type: _type, ...rest }) => {
+ return ;
+};
+/**
+ * A email field component.
+ *
+ * Designed to work with a payload form_props's [email_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#text-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
+export const EmailField = ({ type: _type, ...rest }) => {
+ return ;
+};
+/**
+ * A color field component.
+ *
+ * Designed to work with a payload form_props's [color_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#text-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
+export const ColorField = ({ type: _type, ...rest }) => {
+ return ;
+};
+/**
+ * A date field component.
+ *
+ * Designed to work with a payload form_props's [date_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#date-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
+export const DateField = ({ type: _type, ...rest }) => {
+ return ;
+};
+/**
+ * A search field component.
+ *
+ * Designed to work with a payload form_props's [search_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#text-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
+export const SearchField = ({ type: _type, ...rest }) => {
+ return ;
+};
+/**
+ * A tel field component.
+ *
+ * Designed to work with a payload form_props's [tel_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#text-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
+export const TelField = ({ type: _type, ...rest }) => {
+ return ;
+};
+/**
+ * A url field component.
+ *
+ * Designed to work with a payload form_props's [tel_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#text-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
+export const UrlField = ({ type: _type, ...rest }) => {
+ return ;
+};
+/**
+ * A month field component.
+ *
+ * Designed to work with a payload form_props's [month_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#date-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
+export const MonthField = ({ type: _type, ...rest }) => {
+ return ;
+};
+/**
+ * A month field component.
+ *
+ * Designed to work with a payload form_props's [month_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#date-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
+export const TimeField = ({ type: _type, ...rest }) => {
+ return ;
+};
+/**
+ * A number field component.
+ *
+ * Designed to work with a payload form_props's [month_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#number-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
+export const NumberField = ({ type: _type, ...rest }) => {
+ return ;
+};
+/**
+ * A range field component.
+ *
+ * Designed to work with a payload form_props's [range_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#number-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
+export const RangeField = ({ type: _type, ...rest }) => {
+ return ;
+};
+/**
+ * A password field component.
+ *
+ * Designed to work with a payload form_props's [password_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#text-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
+export const PasswordField = ({ type: _type, ...rest }) => {
+ return ;
+};
+/**
+ * A select component.
+ *
+ * Designed to work with a payload form_props's [select helpers](https://github.com/thoughtbot/form_props?tab=readme-ov-file#select-helpers),
+ * [collection_select helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#collection-select), and [grouped_collection_select helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#group-collection-select).
+ *
+ * Please modify to your liking.
+ */
+export const Select = ({ includeHidden, name, id, children, options, multiple, type: _type, ...rest }) => {
+ const addHidden = includeHidden && multiple;
+ const optionElements = options.map((item) => {
+ if ('options' in item) {
+ return ();
+ }
+ else {
+ return ;
+ }
+ });
+ return (<>
+ {addHidden && ()}
+
+ >);
+};
+/**
+ * A text area component.
+ *
+ * Designed to work with a payload form_props's text_area helper.
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
+export const TextArea = ({ type: _type, errorKey, ...rest }) => {
+ const { label } = rest;
+ return
+
+ ;
+};
diff --git a/rails/Checkbox.test.jsx b/wrappers/ts/basic/Checkbox.test.jsx
similarity index 95%
rename from rails/Checkbox.test.jsx
rename to wrappers/ts/basic/Checkbox.test.jsx
index d2721ed..1035737 100644
--- a/rails/Checkbox.test.jsx
+++ b/wrappers/ts/basic/Checkbox.test.jsx
@@ -1,6 +1,7 @@
import React from 'react'
import { render } from '@testing-library/react'
-import { Checkbox, ValidationContext } from '.'
+import { Checkbox } from '.'
+import { ValidationContext } from '../../../src'
const buildPayload = () => {
return {
@@ -18,7 +19,7 @@ describe('Checkbox', () => {
const payload = buildPayload()
const { getByLabelText } = render(
-
+
)
const element = getByLabelText('Is admin')
@@ -109,4 +110,4 @@ describe('Checkbox', () => {
const element = getByText('Admin invalid')
expect(element).not.toBe(null)
})
-})
\ No newline at end of file
+})
diff --git a/rails/CollectionCheckboxes.test.jsx b/wrappers/ts/basic/CollectionCheckboxes.test.jsx
similarity index 96%
rename from rails/CollectionCheckboxes.test.jsx
rename to wrappers/ts/basic/CollectionCheckboxes.test.jsx
index 1a02c64..dbf5166 100644
--- a/rails/CollectionCheckboxes.test.jsx
+++ b/wrappers/ts/basic/CollectionCheckboxes.test.jsx
@@ -1,6 +1,7 @@
import React from 'react'
import { render } from '@testing-library/react'
-import { CollectionCheckboxes, ValidationContext } from '.'
+import { CollectionCheckboxes } from '.'
+import { ValidationContext } from '../../../src'
const buildCheckboxPayload = (value, label) => {
return {
@@ -49,7 +50,7 @@ describe('CollectionCheckboxes', () => {
expect(input.value).toEqual('2')
expect(input.type).toEqual('checkbox')
})
-
+
it('renders nothing when the collection is blank', () => {
const payload = buildPayload()
payload.collection = []
@@ -102,4 +103,4 @@ describe('CollectionCheckboxes', () => {
const errorField = getByText('id invalid')
expect(errorField).not.toBeNull()
})
-})
\ No newline at end of file
+})
diff --git a/rails/CollectionRadioButtons.test.jsx b/wrappers/ts/basic/CollectionRadioButtons.test.jsx
similarity index 96%
rename from rails/CollectionRadioButtons.test.jsx
rename to wrappers/ts/basic/CollectionRadioButtons.test.jsx
index 3d1e26a..2047400 100644
--- a/rails/CollectionRadioButtons.test.jsx
+++ b/wrappers/ts/basic/CollectionRadioButtons.test.jsx
@@ -1,8 +1,7 @@
import React from 'react'
import { render } from '@testing-library/react'
-import {
- CollectionRadioButtons, ValidationContext
-} from '.'
+import { CollectionRadioButtons } from '.'
+import { ValidationContext } from '../../../src'
const buildRadioButtonPayload = (value, label, rest) => {
return {
diff --git a/rails/ColorField.test.jsx b/wrappers/ts/basic/ColorField.test.jsx
similarity index 92%
rename from rails/ColorField.test.jsx
rename to wrappers/ts/basic/ColorField.test.jsx
index 7bd2ef8..0635044 100644
--- a/rails/ColorField.test.jsx
+++ b/wrappers/ts/basic/ColorField.test.jsx
@@ -1,6 +1,7 @@
import React from 'react'
import { render } from '@testing-library/react'
-import { ColorField, ValidationContext } from './'
+import { ColorField } from './'
+import { ValidationContext } from '../../../src'
const buildPayload = () => {
return {
diff --git a/rails/DateField.test.jsx b/wrappers/ts/basic/DateField.test.jsx
similarity index 93%
rename from rails/DateField.test.jsx
rename to wrappers/ts/basic/DateField.test.jsx
index 175bbaa..a2b0674 100644
--- a/rails/DateField.test.jsx
+++ b/wrappers/ts/basic/DateField.test.jsx
@@ -1,6 +1,7 @@
import React from 'react'
import { render } from '@testing-library/react'
-import { DateField, ValidationContext } from '.'
+import { DateField } from '.'
+import { ValidationContext } from '../../../src'
const buildPayload = () => {
return {
@@ -22,7 +23,7 @@ describe('DateField', () => {
)
const input = getByLabelText('Birth Date')
-
+
expect(input.value).toEqual('2004-06-15')
expect(input.max).toEqual('2010-08-15')
expect(input.min).toEqual('2000-06-15')
diff --git a/rails/EmailField.test.jsx b/wrappers/ts/basic/EmailField.test.jsx
similarity index 93%
rename from rails/EmailField.test.jsx
rename to wrappers/ts/basic/EmailField.test.jsx
index 05fe45e..c9fe08b 100644
--- a/rails/EmailField.test.jsx
+++ b/wrappers/ts/basic/EmailField.test.jsx
@@ -1,6 +1,7 @@
import React from 'react'
import { render } from '@testing-library/react'
-import { EmailField, ValidationContext } from '.'
+import { EmailField } from '.'
+import { ValidationContext } from '../../../src'
const buildPayload = () => {
return {
diff --git a/rails/FieldError.test.jsx b/wrappers/ts/basic/FieldError.test.jsx
similarity index 75%
rename from rails/FieldError.test.jsx
rename to wrappers/ts/basic/FieldError.test.jsx
index faee269..f3f0460 100644
--- a/rails/FieldError.test.jsx
+++ b/wrappers/ts/basic/FieldError.test.jsx
@@ -1,14 +1,15 @@
import React from 'react'
import { render } from '@testing-library/react'
-import { FieldError, ValidationContext } from './'
+import { FieldError } from './'
+import { ValidationContext } from '../../../src'
describe('FieldError', () => {
it('renders', () => {
- const errors = {name: "Name is invalid"}
+ const errors = { name: 'Name is invalid' }
const { getByText } = render(
-
+
)
@@ -17,24 +18,24 @@ describe('FieldError', () => {
})
it('renders when there are mulitiple errors', () => {
- const errors = {name: ["Name is invalid", "Name is too short"]}
+ const errors = { name: ['Name is invalid', 'Name is too short'] }
const { getByText } = render(
-
+
)
const element = getByText('Name is invalid Name is too short')
expect(element).not.toBe(null)
})
-
+
it('does not render when there are no errors', () => {
const errors = {}
const { queryByText } = render(
-
+
)
@@ -43,11 +44,11 @@ describe('FieldError', () => {
})
it('does not render when the errorKey does not match', () => {
- const errors = {phone: "is absent"}
+ const errors = { phone: 'is absent' }
const { queryByText } = render(
-
+
)
diff --git a/rails/MonthField.test.jsx b/wrappers/ts/basic/MonthField.test.jsx
similarity index 82%
rename from rails/MonthField.test.jsx
rename to wrappers/ts/basic/MonthField.test.jsx
index b10b0c3..c9d880b 100644
--- a/rails/MonthField.test.jsx
+++ b/wrappers/ts/basic/MonthField.test.jsx
@@ -1,16 +1,17 @@
import React from 'react'
import { render } from '@testing-library/react'
-import { MonthField, ValidationContext } from '.'
+import { MonthField } from '.'
+import { ValidationContext } from '../../../src'
const buildPayload = () => {
return {
- type: "month",
- defaultValue: "2004-06",
+ type: 'month',
+ defaultValue: '2004-06',
step: 2,
- max: "2010-12",
- min: "2000-02",
- name: "post[written_on]",
- id: "post_written_on"
+ max: '2010-12',
+ min: '2000-02',
+ name: 'post[written_on]',
+ id: 'post_written_on',
}
}
diff --git a/rails/NumberField.test.jsx b/wrappers/ts/basic/NumberField.test.jsx
similarity index 92%
rename from rails/NumberField.test.jsx
rename to wrappers/ts/basic/NumberField.test.jsx
index 5c28ba5..aa017bb 100644
--- a/rails/NumberField.test.jsx
+++ b/wrappers/ts/basic/NumberField.test.jsx
@@ -1,6 +1,7 @@
import React from 'react'
import { render } from '@testing-library/react'
-import { NumberField, ValidationContext } from '.'
+import { NumberField } from '.'
+import { ValidationContext } from '../../../src'
const buildPayload = () => {
return {
@@ -10,7 +11,7 @@ const buildPayload = () => {
min: 1,
max: 9,
id: 'post_favs',
- step: 2
+ step: 2,
}
}
diff --git a/rails/PasswordField.test.jsx b/wrappers/ts/basic/PasswordField.test.jsx
similarity index 93%
rename from rails/PasswordField.test.jsx
rename to wrappers/ts/basic/PasswordField.test.jsx
index 58222c0..b0cd132 100644
--- a/rails/PasswordField.test.jsx
+++ b/wrappers/ts/basic/PasswordField.test.jsx
@@ -1,6 +1,7 @@
import React from 'react'
import { render } from '@testing-library/react'
-import { PasswordField, ValidationContext } from '.'
+import { PasswordField } from '.'
+import { ValidationContext } from '../../../src'
const buildPayload = () => {
return {
diff --git a/rails/RangeField.test.jsx b/wrappers/ts/basic/RangeField.test.jsx
similarity index 92%
rename from rails/RangeField.test.jsx
rename to wrappers/ts/basic/RangeField.test.jsx
index 71a1ce5..67a7579 100644
--- a/rails/RangeField.test.jsx
+++ b/wrappers/ts/basic/RangeField.test.jsx
@@ -1,6 +1,7 @@
import React from 'react'
import { render } from '@testing-library/react'
-import { RangeField, ValidationContext } from '.'
+import { RangeField } from '.'
+import { ValidationContext } from '../../../src'
const buildPayload = () => {
return {
@@ -10,7 +11,7 @@ const buildPayload = () => {
min: 1,
max: 9,
id: 'post_volume',
- step: 2
+ step: 2,
}
}
diff --git a/rails/SearchField.test.jsx b/wrappers/ts/basic/SearchField.test.jsx
similarity index 93%
rename from rails/SearchField.test.jsx
rename to wrappers/ts/basic/SearchField.test.jsx
index 7063e53..73ca596 100644
--- a/rails/SearchField.test.jsx
+++ b/wrappers/ts/basic/SearchField.test.jsx
@@ -1,6 +1,7 @@
import React from 'react'
import { render } from '@testing-library/react'
-import { SearchField, ValidationContext } from '.'
+import { SearchField } from '.'
+import { ValidationContext } from '../../../src'
const buildPayload = () => {
return {
diff --git a/rails/Select.test.jsx b/wrappers/ts/basic/Select.test.jsx
similarity index 87%
rename from rails/Select.test.jsx
rename to wrappers/ts/basic/Select.test.jsx
index fd0e3bd..b44da64 100644
--- a/rails/Select.test.jsx
+++ b/wrappers/ts/basic/Select.test.jsx
@@ -21,6 +21,7 @@ describe('Select', () => {
it('adds a hidden input on multiple selects if includeHidden is true', () => {
const payload = buildPayload()
payload.multiple = true
+ payload.defaultValue = [payload.defaultValue]
payload.includeHidden = true
const { container } = render()
@@ -58,21 +59,23 @@ describe('Select', () => {
],
}
- const { getAllByRole } = render()
+ const { getAllByRole } = render(
+
+ )
let options = getAllByRole('option')
expect(options[0].value).toEqual('')
expect(options[0].getAttribute('label')).toEqual('Choose a category')
-
+
expect(options[1].value).toEqual('abe')
expect(options[1].getAttribute('label')).toEqual('abe')
-
+
expect(options[2].value).toEqual('')
expect(options[2].getAttribute('label')).toEqual('')
-
+
expect(options[3].value).toEqual('hest')
expect(options[3].getAttribute('label')).toEqual('hest')
})
-
+
it('renders with nested options', async () => {
const payload = {
type: 'select',
@@ -81,26 +84,29 @@ describe('Select', () => {
includeHidden: true,
options: [
{ value: 'abe', label: 'abe' },
- { label: 'sports', options: [
- {value: "soccer", label: "Soccer"},
- {value: "baseball", label: "Baseball"},
- ]},
+ {
+ label: 'sports',
+ options: [
+ { value: 'soccer', label: 'Soccer' },
+ { value: 'baseball', label: 'Baseball' },
+ ],
+ },
{ value: 'hest', label: 'hest' },
],
}
const { getByRole } = render()
let select = getByRole('combobox')
-
- expect(select.id).toEqual("post_category")
- expect(select.name).toEqual("post[category]")
+
+ expect(select.id).toEqual('post_category')
+ expect(select.name).toEqual('post[category]')
const optGroup = within(select).getByRole('group')
expect(optGroup.getAttribute('label')).toEqual('sports')
-
+
const options = within(optGroup).getAllByRole('option')
expect(options[0].value).toEqual('soccer')
expect(options[0].getAttribute('label')).toEqual('Soccer')
-
+
expect(options[1].value).toEqual('baseball')
expect(options[1].getAttribute('label')).toEqual('Baseball')
})
diff --git a/rails/TelField.test.jsx b/wrappers/ts/basic/TelField.test.jsx
similarity index 93%
rename from rails/TelField.test.jsx
rename to wrappers/ts/basic/TelField.test.jsx
index ea1a244..3002dd6 100644
--- a/rails/TelField.test.jsx
+++ b/wrappers/ts/basic/TelField.test.jsx
@@ -1,6 +1,7 @@
import React from 'react'
import { render } from '@testing-library/react'
-import { TelField, ValidationContext } from '.'
+import { TelField } from '.'
+import { ValidationContext } from '../../../src'
const buildPayload = () => {
return {
diff --git a/wrappers/ts/basic/TextArea.test.jsx b/wrappers/ts/basic/TextArea.test.jsx
new file mode 100644
index 0000000..b487b9f
--- /dev/null
+++ b/wrappers/ts/basic/TextArea.test.jsx
@@ -0,0 +1,49 @@
+import React from 'react'
+import { render } from '@testing-library/react'
+import { TextArea } from '.'
+import { ValidationContext } from '../../../src'
+
+const buildPayload = () => {
+ return {
+ type: 'text',
+ name: 'post[category]',
+ id: 'post_category',
+ required: false,
+ defaultValue: 'books',
+ }
+}
+
+describe('TextArea', () => {
+ it('renders', () => {
+ const payload = buildPayload()
+
+ const { getByLabelText } = render(
+
+ )
+
+ const input = getByLabelText('category')
+ expect(input.required).toBeFalsy()
+ expect(input.value).toEqual('books')
+ expect(input.type).toEqual('textarea')
+ })
+
+ it('renders with field errors', async () => {
+ const payload = buildPayload()
+
+ const validationErrors = {
+ category: 'title invalid',
+ }
+
+ const { getByText, getByLabelText } = render(
+
+
+
+ )
+
+ const errorField = getByText('title invalid')
+ expect(errorField).not.toBeNull()
+
+ const input = getByLabelText('category')
+ expect(input.value).toEqual('books')
+ })
+})
diff --git a/rails/TextField.test.jsx b/wrappers/ts/basic/TextField.test.jsx
similarity index 93%
rename from rails/TextField.test.jsx
rename to wrappers/ts/basic/TextField.test.jsx
index 77214c9..ee54bd6 100644
--- a/rails/TextField.test.jsx
+++ b/wrappers/ts/basic/TextField.test.jsx
@@ -1,6 +1,7 @@
import React from 'react'
import { render } from '@testing-library/react'
-import { TextField, ValidationContext } from '.'
+import { TextField } from '.'
+import { ValidationContext } from '../../../src'
const buildPayload = () => {
return {
diff --git a/rails/TimeField.test.jsx b/wrappers/ts/basic/TimeField.test.jsx
similarity index 94%
rename from rails/TimeField.test.jsx
rename to wrappers/ts/basic/TimeField.test.jsx
index 341a075..a1b1c9f 100644
--- a/rails/TimeField.test.jsx
+++ b/wrappers/ts/basic/TimeField.test.jsx
@@ -1,6 +1,7 @@
import React from 'react'
import { render } from '@testing-library/react'
-import { TimeField, ValidationContext } from '.'
+import { TimeField } from '.'
+import { ValidationContext } from '../../../src'
const buildPayload = () => {
return {
diff --git a/rails/UrlField.test.jsx b/wrappers/ts/basic/UrlField.test.jsx
similarity index 92%
rename from rails/UrlField.test.jsx
rename to wrappers/ts/basic/UrlField.test.jsx
index cd36e45..4ae7e8a 100644
--- a/rails/UrlField.test.jsx
+++ b/wrappers/ts/basic/UrlField.test.jsx
@@ -1,6 +1,7 @@
import React from 'react'
import { render } from '@testing-library/react'
-import { UrlField, ValidationContext } from '.'
+import { UrlField } from '.'
+import { ValidationContext } from '../../../src'
const buildPayload = () => {
return {
diff --git a/rails/index.tsx b/wrappers/ts/basic/index.tsx
similarity index 51%
rename from rails/index.tsx
rename to wrappers/ts/basic/index.tsx
index d4421e4..0d718b9 100644
--- a/rails/index.tsx
+++ b/wrappers/ts/basic/index.tsx
@@ -1,42 +1,43 @@
-import React, { ReactNode, createContext, useContext, useMemo } from 'react'
+/**
+ * Vanilla is a minimum set of components wrapped around regular HTML tags.
+ * It works with the output from [FormProps](https://github.com/thoughtbot/form_props).
+ *
+ * There is no style and structured with bare necessities. You should modify
+ * these components to fit your design needs.
+ */
+
+import React, { ReactNode, useContext } from 'react'
import {
- RailsCheckboxField,
- RailsCollectionCheckboxesField,
- RailsHiddenField,
- RailsCollectionRadioButtonsField,
- RailsColorField,
- RailsDateField,
- RailsEmailField,
- RailsMonthField,
- RailsNumberField,
- RailsPasswordField,
- RailsRangeField,
- RailsSearchField,
- RailsSelect,
- RailsTelField,
- RailsTextField,
- RailsTimeField,
- RailsUrlField,
-} from '../types'
-
-export type ValidationError = string | string[]
-export type ValidationErrors = Record
-export const ValidationContext = createContext({})
-
-export const useErrorKeyValidation = ({
- errorKey,
-}: {
- errorKey: string
- name: string
-}) => {
- const serverErrors = useContext(ValidationContext)
-
- return useMemo(() => {
- return serverErrors[errorKey]
- }, [serverErrors, errorKey])
-}
+ ValidationContext,
+ CheckboxField as RailsCheckboxField,
+ CollectionCheckboxesField as RailsCollectionCheckboxesField,
+ HiddenField as RailsHiddenField,
+ CollectionRadioButtonsField as RailsCollectionRadioButtonsField,
+ ColorField as RailsColorField,
+ DateField as RailsDateField,
+ EmailField as RailsEmailField,
+ MonthField as RailsMonthField,
+ NumberField as RailsNumberField,
+ PasswordField as RailsPasswordField,
+ RangeField as RailsRangeField,
+ SearchField as RailsSearchField,
+ Select as RailsSelect,
+ TelField as RailsTelField,
+ TextField as RailsTextField,
+ TimeField as RailsTimeField,
+ UrlField as RailsUrlField,
+ TextArea as RailsTextArea,
+ ValidationErrors,
+} from '@thoughtbot/candy_wrapper'
export type ExtrasProps = Record
+
+/**
+ * Extras renders the hidden inputs generated by form_props.
+ *
+ * Its meant to be used with a form component and renders hidden values for
+ * utf8, crsf_token, _method
+ */
export const Extras = (hiddenInputAttributes: ExtrasProps) => {
const hiddenProps = Object.values(hiddenInputAttributes)
const hiddenInputs = hiddenProps.map((props: RailsHiddenField) => (
@@ -50,6 +51,12 @@ type FormProps = React.FormHTMLAttributes & {
extras: ExtrasProps
validationErrors: ValidationErrors
}
+/**
+ * A basic form component that supports inline errors.
+ *
+ * It's meant to be used with FormProps and mimics the ways that
+ * Rails forms are generated.
+ */
export const Form = ({
extras,
validationErrors,
@@ -66,6 +73,12 @@ export const Form = ({
)
}
+/**
+ * An inline error component.
+ *
+ * When a Field has an error, this will show below the label and input.
+ * Please modify this to your liking.
+ */
export const FieldError = ({ errorKey }: { errorKey: string | undefined }) => {
if (!errorKey) {
return null
@@ -78,11 +91,11 @@ export const FieldError = ({ errorKey }: { errorKey: string | undefined }) => {
return null
}
- const errorMessages = Array.isArray(
- errors[errorKey]
- ) ? errors[errorKey] : [errors[errorKey]]
-
- return {errorMessages.join(" ")}
+ const errorMessages = Array.isArray(errors[errorKey])
+ ? errors[errorKey]
+ : [errors[errorKey]]
+
+ return {errorMessages.join(' ')}
}
export type FieldBaseProps = React.InputHTMLAttributes & {
@@ -91,6 +104,12 @@ export type FieldBaseProps = React.InputHTMLAttributes & {
errorKey?: string
children?: ReactNode
}
+
+/**
+ * A Field component.
+ *
+ * Combines a label, input and a FieldError. Please modify this to your liking.
+ */
export const FieldBase = ({
label,
errorKey,
@@ -111,16 +130,23 @@ type InputProps = {
errorKey?: string
}
+/**
+ * A checkbox component.
+ *
+ * Designed to work with a payload form_props's [checkbox helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#checkbox-helper).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
type CheckboxProps = RailsCheckboxField & InputProps
export const Checkbox = ({
type: _type,
includeHidden,
uncheckedValue,
+ errorKey,
...rest
}: CheckboxProps) => {
const { name } = rest
return (
-
+
{includeHidden && (
{
return (
- <>
+
- >
+
)
})
@@ -202,6 +242,13 @@ export const CollectionRadioButtons = ({
export type TextFieldProps = React.InputHTMLAttributes &
RailsTextField &
InputProps
+
+/**
+ * A text field component.
+ *
+ * Designed to work with a payload form_props's [text_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#text-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
export const TextField = ({ type: _type, ...rest }: TextFieldProps) => {
return
}
@@ -209,6 +256,13 @@ export const TextField = ({ type: _type, ...rest }: TextFieldProps) => {
export type EmailFieldProps = React.InputHTMLAttributes &
RailsEmailField &
InputProps
+
+/**
+ * A email field component.
+ *
+ * Designed to work with a payload form_props's [email_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#text-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
export const EmailField = ({ type: _type, ...rest }: EmailFieldProps) => {
return
}
@@ -216,6 +270,13 @@ export const EmailField = ({ type: _type, ...rest }: EmailFieldProps) => {
export type ColorFieldProps = React.InputHTMLAttributes &
RailsColorField &
InputProps
+
+/**
+ * A color field component.
+ *
+ * Designed to work with a payload form_props's [color_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#text-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
export const ColorField = ({ type: _type, ...rest }: ColorFieldProps) => {
return
}
@@ -223,6 +284,13 @@ export const ColorField = ({ type: _type, ...rest }: ColorFieldProps) => {
export type DateFieldProps = React.InputHTMLAttributes &
RailsDateField &
InputProps
+
+/**
+ * A date field component.
+ *
+ * Designed to work with a payload form_props's [date_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#date-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
export const DateField = ({ type: _type, ...rest }: DateFieldProps) => {
return
}
@@ -230,6 +298,13 @@ export const DateField = ({ type: _type, ...rest }: DateFieldProps) => {
export type SearchFieldProps = React.InputHTMLAttributes &
RailsSearchField &
InputProps
+
+/**
+ * A search field component.
+ *
+ * Designed to work with a payload form_props's [search_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#text-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
export const SearchField = ({ type: _type, ...rest }: SearchFieldProps) => {
return
}
@@ -237,6 +312,13 @@ export const SearchField = ({ type: _type, ...rest }: SearchFieldProps) => {
export type TelFieldProps = React.InputHTMLAttributes &
RailsTelField &
InputProps
+
+/**
+ * A tel field component.
+ *
+ * Designed to work with a payload form_props's [tel_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#text-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
export const TelField = ({ type: _type, ...rest }: TelFieldProps) => {
return
}
@@ -244,6 +326,13 @@ export const TelField = ({ type: _type, ...rest }: TelFieldProps) => {
export type UrlFieldProps = React.InputHTMLAttributes &
RailsUrlField &
InputProps
+
+/**
+ * A url field component.
+ *
+ * Designed to work with a payload form_props's [tel_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#text-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
export const UrlField = ({ type: _type, ...rest }: UrlFieldProps) => {
return
}
@@ -251,6 +340,13 @@ export const UrlField = ({ type: _type, ...rest }: UrlFieldProps) => {
export type MonthFieldProps = React.InputHTMLAttributes &
RailsMonthField &
InputProps
+
+/**
+ * A month field component.
+ *
+ * Designed to work with a payload form_props's [month_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#date-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
export const MonthField = ({ type: _type, ...rest }: MonthFieldProps) => {
return
}
@@ -258,6 +354,13 @@ export const MonthField = ({ type: _type, ...rest }: MonthFieldProps) => {
export type TimeFieldProps = React.InputHTMLAttributes &
RailsTimeField &
InputProps
+
+/**
+ * A month field component.
+ *
+ * Designed to work with a payload form_props's [month_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#date-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
export const TimeField = ({ type: _type, ...rest }: TimeFieldProps) => {
return
}
@@ -265,6 +368,12 @@ export const TimeField = ({ type: _type, ...rest }: TimeFieldProps) => {
export type NumberFieldProps = React.InputHTMLAttributes &
RailsNumberField &
InputProps
+/**
+ * A number field component.
+ *
+ * Designed to work with a payload form_props's [month_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#number-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
export const NumberField = ({ type: _type, ...rest }: NumberFieldProps) => {
return
}
@@ -272,6 +381,12 @@ export const NumberField = ({ type: _type, ...rest }: NumberFieldProps) => {
export type RangeFieldProps = React.InputHTMLAttributes &
RailsRangeField &
InputProps
+/**
+ * A range field component.
+ *
+ * Designed to work with a payload form_props's [range_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#number-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
export const RangeField = ({ type: _type, ...rest }: RangeFieldProps) => {
return
}
@@ -279,6 +394,12 @@ export const RangeField = ({ type: _type, ...rest }: RangeFieldProps) => {
export type PasswordFieldProps = React.InputHTMLAttributes &
RailsPasswordField &
InputProps
+/**
+ * A password field component.
+ *
+ * Designed to work with a payload form_props's [password_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#text-helpers).
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
export const PasswordField = ({ type: _type, ...rest }: PasswordFieldProps) => {
return
}
@@ -288,6 +409,14 @@ export type SelectProps = React.SelectHTMLAttributes &
label?: string
errorKey?: string
}
+/**
+ * A select component.
+ *
+ * Designed to work with a payload form_props's [select helpers](https://github.com/thoughtbot/form_props?tab=readme-ov-file#select-helpers),
+ * [collection_select helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#collection-select), and [grouped_collection_select helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#group-collection-select).
+ *
+ * Please modify to your liking.
+ */
export const Select = ({
includeHidden,
name,
@@ -326,3 +455,21 @@ export const Select = ({
>
)
}
+
+
+export type TextAreaProps = React.InputHTMLAttributes &
+ RailsTextArea &
+ InputProps
+/**
+ * A text area component.
+ *
+ * Designed to work with a payload form_props's text_area helper.
+ * Mimics the rails equivalent. Please modify to your liking.
+ */
+export const TextArea = ({ type: _type, errorKey, ...rest }: TextAreaProps) => {
+ const {label} = rest
+
+ return
+
+
+}
diff --git a/wrappers/tsconfig.json b/wrappers/tsconfig.json
new file mode 100644
index 0000000..c3200ab
--- /dev/null
+++ b/wrappers/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../tsconfig",
+ "compilerOptions": {
+ "removeComments": false,
+ "outDir": "../temp",
+ "paths": {
+ "@thoughtbot/candy_wrapper": ["../src/index"],
+ }
+ },
+ "include": ["./ts/**/*"]
+}