superglue 0.53.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/generators/superglue/install/install_generator.rb +119 -0
- data/lib/{install/templates/web → generators/superglue/install/templates}/application.json.props +2 -2
- data/lib/generators/superglue/install/templates/js/application.jsx +35 -0
- data/lib/generators/superglue/install/templates/js/application_visit.js +113 -0
- data/lib/generators/superglue/install/templates/js/components.js +2 -0
- data/lib/generators/superglue/install/templates/js/flash.js +44 -0
- data/lib/generators/superglue/install/templates/js/inputs.jsx +302 -0
- data/lib/generators/superglue/install/templates/js/jsconfig.json +9 -0
- data/lib/generators/superglue/install/templates/js/layout.jsx +16 -0
- data/lib/generators/superglue/install/templates/js/page_to_page_mapping.js +35 -0
- data/lib/generators/superglue/install/templates/js/store.js +30 -0
- data/lib/{install/templates/web/application.js → generators/superglue/install/templates/ts/application.tsx} +10 -16
- data/lib/generators/superglue/install/templates/ts/application_visit.ts +122 -0
- data/lib/generators/superglue/install/templates/ts/components.ts +2 -0
- data/lib/generators/superglue/install/templates/ts/flash.ts +46 -0
- data/lib/generators/superglue/install/templates/ts/inputs.tsx +547 -0
- data/lib/generators/superglue/install/templates/ts/layout.tsx +16 -0
- data/lib/generators/superglue/install/templates/ts/page_to_page_mapping.ts +34 -0
- data/lib/generators/superglue/install/templates/ts/store.ts +34 -0
- data/lib/generators/superglue/install/templates/ts/tsconfig.json +27 -0
- data/lib/generators/superglue/scaffold/scaffold_generator.rb +16 -0
- data/lib/generators/superglue/scaffold_controller/scaffold_controller_generator.rb +61 -0
- data/lib/generators/{rails/templates/web → superglue/view_collection/templates/erb}/edit.html.erb +1 -1
- data/lib/generators/{rails/templates/web → superglue/view_collection/templates/erb}/index.html.erb +1 -1
- data/lib/generators/{rails/templates/web → superglue/view_collection/templates/erb}/new.html.erb +1 -1
- data/lib/generators/{rails/templates/web → superglue/view_collection/templates/erb}/show.html.erb +1 -1
- data/lib/generators/superglue/view_collection/templates/js/edit.jsx +40 -0
- data/lib/generators/superglue/view_collection/templates/js/index.jsx +62 -0
- data/lib/generators/superglue/view_collection/templates/js/new.jsx +38 -0
- data/lib/generators/superglue/view_collection/templates/js/show.jsx +26 -0
- data/lib/generators/superglue/view_collection/templates/props/edit.json.props +9 -0
- data/lib/generators/superglue/view_collection/templates/props/index.json.props +14 -0
- data/lib/generators/superglue/view_collection/templates/props/new.json.props +10 -0
- data/lib/generators/superglue/view_collection/templates/props/show.json.props +6 -0
- data/lib/generators/superglue/view_collection/templates/ts/edit.tsx +54 -0
- data/lib/generators/superglue/view_collection/templates/ts/index.tsx +77 -0
- data/lib/generators/superglue/view_collection/templates/ts/new.tsx +50 -0
- data/lib/generators/superglue/view_collection/templates/ts/show.tsx +37 -0
- data/lib/generators/superglue/view_collection/view_collection_generator.rb +180 -0
- data/lib/superglue/helpers.rb +1 -1
- data/lib/superglue.rb +2 -1
- metadata +60 -43
- data/lib/generators/rails/scaffold_controller_generator.rb +0 -12
- data/lib/generators/rails/superglue_generator.rb +0 -98
- data/lib/generators/rails/templates/controller.rb.tt +0 -82
- data/lib/generators/rails/templates/edit.json.props +0 -12
- data/lib/generators/rails/templates/index.json.props +0 -14
- data/lib/generators/rails/templates/new.json.props +0 -13
- data/lib/generators/rails/templates/show.json.props +0 -6
- data/lib/generators/rails/templates/web/edit.js +0 -35
- data/lib/generators/rails/templates/web/index.js +0 -56
- data/lib/generators/rails/templates/web/new.js +0 -33
- data/lib/generators/rails/templates/web/show.js +0 -28
- data/lib/install/templates/web/actions.js +0 -6
- data/lib/install/templates/web/application_visit.js +0 -65
- data/lib/install/templates/web/flash.js +0 -19
- data/lib/install/templates/web/page_to_page_mapping.js +0 -12
- data/lib/install/templates/web/pages.js +0 -15
- data/lib/install/templates/web/store.js +0 -32
- data/lib/install/web.rb +0 -55
- data/lib/tasks/install.rake +0 -9
- /data/lib/{install/templates/web → generators/superglue/install/templates}/initializer.rb +0 -0
- /data/lib/generators/{rails/templates → superglue/view_collection/templates/props}/_form.json.props +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 825363825f0e6cf14431bd47cdc43b6be0dfcfb6723d19004ee99175bd98dda3
|
4
|
+
data.tar.gz: 27a52faf2563be8e0242c2ed68a5e02c531137e03c5e67e5993e06b615139266
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8dc5c1de22f7d525e82b3e00f49cd774a814f689c8f0e5018e5f1192d694ae036af07b14c162b113e91911bfc918019784863adc7cde2bc9b9786784709d357d
|
7
|
+
data.tar.gz: 5a9618185af23be3406af0153d54af83633cdc0a927337c7cd7d33f257150e6def65471b19c186ffe9d385312f979f9624f61b29c3c4ce9266c8a62820ca6b0c
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require "rails/generators/named_base"
|
2
|
+
require "rails/generators/resource_helpers"
|
3
|
+
|
4
|
+
module Superglue
|
5
|
+
module Generators
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
7
|
+
source_root File.expand_path("../templates", __FILE__)
|
8
|
+
|
9
|
+
class_option :typescript,
|
10
|
+
type: :boolean,
|
11
|
+
required: false,
|
12
|
+
default: false,
|
13
|
+
desc: "Use typescript"
|
14
|
+
|
15
|
+
def create_files
|
16
|
+
remove_file "#{app_js_path}/application.js"
|
17
|
+
|
18
|
+
use_typescript = options["typescript"]
|
19
|
+
if use_typescript
|
20
|
+
copy_ts_files
|
21
|
+
else
|
22
|
+
copy_js_files
|
23
|
+
end
|
24
|
+
|
25
|
+
say "Copying Superglue initializer"
|
26
|
+
copy_file "#{__dir__}/templates/initializer.rb", "config/initializers/superglue.rb"
|
27
|
+
|
28
|
+
say "Copying application.json.props"
|
29
|
+
copy_file "#{__dir__}/templates/application.json.props", "app/views/layouts/application.json.props"
|
30
|
+
|
31
|
+
say "Adding required member methods to ApplicationRecord"
|
32
|
+
add_member_methods
|
33
|
+
|
34
|
+
say "Installing Superglue and friends"
|
35
|
+
run "yarn add react react-dom @reduxjs/toolkit react-redux @thoughtbot/superglue"
|
36
|
+
|
37
|
+
if use_typescript
|
38
|
+
run "yarn add -D @types/react-dom @types/react @types/node @thoughtbot/candy_wrapper"
|
39
|
+
end
|
40
|
+
|
41
|
+
say "Superglue is Installed! 🎉", :green
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def copy_ts_files
|
47
|
+
say "Copying application.tsx file to #{app_js_path}"
|
48
|
+
copy_file "#{__dir__}/templates/ts/application.tsx", "#{app_js_path}/application.tsx"
|
49
|
+
|
50
|
+
say "Copying page_to_page_mapping.ts file to #{app_js_path}"
|
51
|
+
copy_file "#{__dir__}/templates/ts/page_to_page_mapping.ts", "#{app_js_path}/page_to_page_mapping.ts"
|
52
|
+
|
53
|
+
say "Copying flash.ts file to #{app_js_path}"
|
54
|
+
copy_file "#{__dir__}/templates/ts/flash.ts", "#{app_js_path}/slices/flash.ts"
|
55
|
+
|
56
|
+
say "Copying store.ts file to #{app_js_path}"
|
57
|
+
copy_file "#{__dir__}/templates/ts/store.ts", "#{app_js_path}/store.ts"
|
58
|
+
|
59
|
+
say "Copying application_visit.ts file to #{app_js_path}"
|
60
|
+
copy_file "#{__dir__}/templates/ts/application_visit.ts", "#{app_js_path}/application_visit.ts"
|
61
|
+
|
62
|
+
say "Copying components to #{app_js_path}"
|
63
|
+
copy_file "#{__dir__}/templates/ts/inputs.tsx", "#{app_js_path}/components/Inputs.tsx"
|
64
|
+
copy_file "#{__dir__}/templates/ts/layout.tsx", "#{app_js_path}/components/Layout.tsx"
|
65
|
+
copy_file "#{__dir__}/templates/ts/components.ts", "#{app_js_path}/components/index.ts"
|
66
|
+
|
67
|
+
say "Copying tsconfig.json file to #{app_js_path}"
|
68
|
+
copy_file "#{__dir__}/templates/ts/tsconfig.json", "tsconfig.json"
|
69
|
+
end
|
70
|
+
|
71
|
+
def copy_js_files
|
72
|
+
say "Copying application.js file to #{app_js_path}"
|
73
|
+
copy_file "#{__dir__}/templates/js/application.jsx", "#{app_js_path}/application.jsx"
|
74
|
+
|
75
|
+
say "Copying page_to_page_mapping.js file to #{app_js_path}"
|
76
|
+
copy_file "#{__dir__}/templates/js/page_to_page_mapping.js", "#{app_js_path}/page_to_page_mapping.js"
|
77
|
+
|
78
|
+
say "Copying flash.js file to #{app_js_path}"
|
79
|
+
copy_file "#{__dir__}/templates/js/flash.js", "#{app_js_path}/slices/flash.js"
|
80
|
+
|
81
|
+
say "Copying store.js file to #{app_js_path}"
|
82
|
+
copy_file "#{__dir__}/templates/js/store.js", "#{app_js_path}/store.js"
|
83
|
+
|
84
|
+
say "Copying application_visit.js file to #{app_js_path}"
|
85
|
+
copy_file "#{__dir__}/templates/js/application_visit.js", "#{app_js_path}/application_visit.js"
|
86
|
+
|
87
|
+
say "Copying components to #{app_js_path}"
|
88
|
+
copy_file "#{__dir__}/templates/js/inputs.jsx", "#{app_js_path}/components/Inputs.jsx"
|
89
|
+
copy_file "#{__dir__}/templates/js/layout.jsx", "#{app_js_path}/components/Layout.jsx"
|
90
|
+
copy_file "#{__dir__}/templates/js/components.js", "#{app_js_path}/components/index.js"
|
91
|
+
|
92
|
+
say "Copying jsconfig.json file to #{app_js_path}"
|
93
|
+
copy_file "#{__dir__}/templates/js/jsconfig.json", "jsconfig.json"
|
94
|
+
end
|
95
|
+
|
96
|
+
def add_member_methods
|
97
|
+
inject_into_file "app/models/application_record.rb", after: "class ApplicationRecord < ActiveRecord::Base\n" do
|
98
|
+
<<-RUBY
|
99
|
+
# This enables digging by index when used with props_template
|
100
|
+
# see https://thoughtbot.github.io/superglue/digging/#index-based-selection
|
101
|
+
def self.member_at(index)
|
102
|
+
offset(index).limit(1).first
|
103
|
+
end
|
104
|
+
|
105
|
+
# This enables digging by attribute when used with props_template
|
106
|
+
# see https://thoughtbot.github.io/superglue/digging/#attribute-based-selection
|
107
|
+
def self.member_by(attr, value)
|
108
|
+
find_by(Hash[attr, value])
|
109
|
+
end
|
110
|
+
RUBY
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def app_js_path
|
115
|
+
"app/javascript/"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import React from "react"
|
2
|
+
import { createRoot } from "react-dom/client"
|
3
|
+
import { Application } from "@thoughtbot/superglue"
|
4
|
+
import { buildVisitAndRemote } from "./application_visit"
|
5
|
+
import { pageIdentifierToPageComponent } from "./page_to_page_mapping"
|
6
|
+
import { store } from "./store"
|
7
|
+
|
8
|
+
if (typeof window !== "undefined") {
|
9
|
+
document.addEventListener("DOMContentLoaded", function() {
|
10
|
+
const appEl = document.getElementById("app")
|
11
|
+
const location = window.location
|
12
|
+
|
13
|
+
if (appEl) {
|
14
|
+
const root = createRoot(appEl)
|
15
|
+
root.render(
|
16
|
+
<Application
|
17
|
+
// The base url prefixed to all calls made by the `visit`
|
18
|
+
// and `remote` thunks.
|
19
|
+
baseUrl={location.origin}
|
20
|
+
// The global var SUPERGLUE_INITIAL_PAGE_STATE is set by your erb
|
21
|
+
// template, e.g., index.html.erb
|
22
|
+
initialPage={window.SUPERGLUE_INITIAL_PAGE_STATE}
|
23
|
+
// The initial path of the page, e.g., /foobar
|
24
|
+
path={location.pathname + location.search + location.hash}
|
25
|
+
// Callback used to setup visit and remote
|
26
|
+
buildVisitAndRemote={buildVisitAndRemote}
|
27
|
+
// Callback used to setup the store
|
28
|
+
store={store}
|
29
|
+
// Mapping between the page identifier to page component
|
30
|
+
mapping={pageIdentifierToPageComponent}
|
31
|
+
/>
|
32
|
+
)
|
33
|
+
}
|
34
|
+
})
|
35
|
+
}
|
@@ -0,0 +1,113 @@
|
|
1
|
+
import { visit, remote } from "@thoughtbot/superglue/action_creators"
|
2
|
+
|
3
|
+
/**
|
4
|
+
* This function returns a wrapped visit and remote that will be used by UJS,
|
5
|
+
* the Navigation component, and passed to your page components through the
|
6
|
+
* NavigationContext.
|
7
|
+
*
|
8
|
+
* You can customize both functions to your liking. For example, for a progress
|
9
|
+
* bar. This file also adds support for data-sg-remote.
|
10
|
+
*/
|
11
|
+
export const buildVisitAndRemote = (ref, store) => {
|
12
|
+
const appRemote = (path, { dataset, ...options }) => {
|
13
|
+
/**
|
14
|
+
* You can make use of `dataset` to add custom UJS options.
|
15
|
+
* If you are implementing a progress bar, you can selectively
|
16
|
+
* hide it for some links. For example:
|
17
|
+
*
|
18
|
+
* ```
|
19
|
+
* <a href="/posts?props_at=data.header" data-sg-remote data-sg-hide-progress>
|
20
|
+
* Click me
|
21
|
+
* </a>
|
22
|
+
* ```
|
23
|
+
*
|
24
|
+
* This would be available as `sgHideProgress` on the dataset
|
25
|
+
*/
|
26
|
+
return store.dispatch(remote(path, options))
|
27
|
+
}
|
28
|
+
|
29
|
+
const appVisit = (path, { dataset, ...options } = {}) => {
|
30
|
+
/**
|
31
|
+
* Do something before we make a request.
|
32
|
+
* e.g, show a [progress bar](https://thoughtbot.github.io/superglue/recipes/progress-bar/).
|
33
|
+
*
|
34
|
+
* Hint: you can access the current pageKey
|
35
|
+
* via `store.getState().superglue.currentPageKey`
|
36
|
+
*/
|
37
|
+
return store
|
38
|
+
.dispatch(visit(path, options))
|
39
|
+
.then(meta => {
|
40
|
+
/**
|
41
|
+
* The assets fingerprints changed, instead of transitioning
|
42
|
+
* just go to the URL directly to retrieve new assets
|
43
|
+
*/
|
44
|
+
if (meta.needsRefresh) {
|
45
|
+
window.location.href = meta.pageKey
|
46
|
+
return meta
|
47
|
+
}
|
48
|
+
|
49
|
+
/**
|
50
|
+
* Your first expanded UJS option, `data-sg-replace`
|
51
|
+
*
|
52
|
+
* This option overrides the `navigationAction` to allow a link click or
|
53
|
+
* a form submission to replace history instead of the usual push.
|
54
|
+
*/
|
55
|
+
const navigatonAction = !!dataset?.sgReplace
|
56
|
+
? "replace"
|
57
|
+
: meta.navigationAction
|
58
|
+
ref.current?.navigateTo(meta.pageKey, {
|
59
|
+
action: navigatonAction
|
60
|
+
})
|
61
|
+
|
62
|
+
/**
|
63
|
+
* Return the meta object, it's used for scroll restoration when
|
64
|
+
* handling the back button. You can skip returning, but Superglue
|
65
|
+
* will warn you about scroll restoration.
|
66
|
+
*/
|
67
|
+
return meta
|
68
|
+
})
|
69
|
+
.finally(() => {
|
70
|
+
/**
|
71
|
+
* Do something after a request.
|
72
|
+
*
|
73
|
+
* This is where you hide a progress bar.
|
74
|
+
*/
|
75
|
+
})
|
76
|
+
.catch(err => {
|
77
|
+
const response = err.response
|
78
|
+
|
79
|
+
if (!response) {
|
80
|
+
/**
|
81
|
+
* This is for errors that are NOT from a HTTP request.
|
82
|
+
*
|
83
|
+
* Tooling like Sentry can capture console errors. If not, feel
|
84
|
+
* free to customize to send the error to your telemetry tool of choice.
|
85
|
+
*/
|
86
|
+
console.error(err)
|
87
|
+
return
|
88
|
+
}
|
89
|
+
|
90
|
+
if (response.ok) {
|
91
|
+
/**
|
92
|
+
* This is for errors that are from a HTTP request.
|
93
|
+
*
|
94
|
+
* If the response is OK, it must be an HTML body, we'll
|
95
|
+
* go to that locaton directly.
|
96
|
+
*/
|
97
|
+
window.location = response.url
|
98
|
+
} else {
|
99
|
+
if (response.status >= 400 && response.status < 500) {
|
100
|
+
window.location.href = "/400.html"
|
101
|
+
return
|
102
|
+
}
|
103
|
+
|
104
|
+
if (response.status >= 500) {
|
105
|
+
window.location.href = "/500.html"
|
106
|
+
return
|
107
|
+
}
|
108
|
+
}
|
109
|
+
})
|
110
|
+
}
|
111
|
+
|
112
|
+
return { visit: appVisit, remote: appRemote }
|
113
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import { createSlice } from "@reduxjs/toolkit"
|
2
|
+
import { saveResponse, beforeVisit } from "@thoughtbot/superglue"
|
3
|
+
|
4
|
+
const initialState = {}
|
5
|
+
|
6
|
+
export const flashSlice = createSlice({
|
7
|
+
name: "flash",
|
8
|
+
initialState: initialState,
|
9
|
+
reducers: {
|
10
|
+
clearFlash(state, { payload }) {
|
11
|
+
const key = payload
|
12
|
+
if (!key) {
|
13
|
+
return {}
|
14
|
+
}
|
15
|
+
|
16
|
+
delete state[key]
|
17
|
+
|
18
|
+
return {
|
19
|
+
...state
|
20
|
+
}
|
21
|
+
},
|
22
|
+
flash(state, { payload }) {
|
23
|
+
return {
|
24
|
+
...state,
|
25
|
+
...payload
|
26
|
+
}
|
27
|
+
}
|
28
|
+
},
|
29
|
+
extraReducers: builder => {
|
30
|
+
builder.addCase(beforeVisit, (_state, _action) => {
|
31
|
+
return {}
|
32
|
+
})
|
33
|
+
builder.addCase(saveResponse, (state, action) => {
|
34
|
+
const { page } = action.payload
|
35
|
+
|
36
|
+
return {
|
37
|
+
...state,
|
38
|
+
...page.slices.flash
|
39
|
+
}
|
40
|
+
})
|
41
|
+
}
|
42
|
+
})
|
43
|
+
|
44
|
+
export const { clearFlash, flash } = flashSlice.actions
|
@@ -0,0 +1,302 @@
|
|
1
|
+
/**
|
2
|
+
* Vanilla is a minimum set of
|
3
|
+
* [candy_wrappers](https://github.com/thoughtbot/candy_wrapper) around react
|
4
|
+
* HTML tags. It works with the output from
|
5
|
+
* [FormProps](https://github.com/thoughtbot/form_props).
|
6
|
+
*
|
7
|
+
* There is no style and structured with bare necessities. You should modify
|
8
|
+
* these components to fit your design needs.
|
9
|
+
*/
|
10
|
+
import React, { useContext, createContext, useMemo } from "react";
|
11
|
+
export const ValidationContext = createContext({});
|
12
|
+
export const useErrorKeyValidation = ({ errorKey, }) => {
|
13
|
+
const errors = useContext(ValidationContext);
|
14
|
+
return useMemo(() => {
|
15
|
+
return errors[errorKey];
|
16
|
+
}, [errors, errorKey]);
|
17
|
+
};
|
18
|
+
/**
|
19
|
+
* Extras renders the hidden inputs generated by form_props.
|
20
|
+
*
|
21
|
+
* Its meant to be used with a form component and renders hidden values for
|
22
|
+
* utf8, crsf_token, _method
|
23
|
+
*/
|
24
|
+
export const Extras = (hiddenInputAttributes) => {
|
25
|
+
const hiddenProps = Object.values(hiddenInputAttributes);
|
26
|
+
const hiddenInputs = hiddenProps.map((props) => (<input {...props} type="hidden" key={props.name}/>));
|
27
|
+
return <>{hiddenInputs}</>;
|
28
|
+
};
|
29
|
+
/**
|
30
|
+
* A basic form component that supports inline errors.
|
31
|
+
*
|
32
|
+
* It's meant to be used with FormProps and mimics the ways that
|
33
|
+
* Rails forms are generated.
|
34
|
+
*/
|
35
|
+
export const Form = ({ extras, validationErrors = {}, children, ...props }) => {
|
36
|
+
return (<form {...props}>
|
37
|
+
<ValidationContext.Provider value={validationErrors}>
|
38
|
+
<Extras {...extras}></Extras>
|
39
|
+
{children}
|
40
|
+
</ValidationContext.Provider>
|
41
|
+
</form>);
|
42
|
+
};
|
43
|
+
/**
|
44
|
+
* An inline error component.
|
45
|
+
*
|
46
|
+
* When a Field has an error, this will show below the label and input.
|
47
|
+
* Please modify this to your liking.
|
48
|
+
*/
|
49
|
+
export const FieldError = ({ errorKey }) => {
|
50
|
+
const errors = useContext(ValidationContext);
|
51
|
+
if (!errorKey || !errors) {
|
52
|
+
return null;
|
53
|
+
}
|
54
|
+
const validationError = errors[errorKey];
|
55
|
+
const hasErrors = errorKey && validationError;
|
56
|
+
if (!hasErrors) {
|
57
|
+
return null;
|
58
|
+
}
|
59
|
+
const errorMessages = Array.isArray(validationError)
|
60
|
+
? validationError
|
61
|
+
: [validationError];
|
62
|
+
return <span>{errorMessages.join(" ")}</span>;
|
63
|
+
};
|
64
|
+
/**
|
65
|
+
* A Field component.
|
66
|
+
*
|
67
|
+
* Combines a label, input and a FieldError. Please modify this to your liking.
|
68
|
+
*/
|
69
|
+
export const FieldBase = ({ label, errorKey, children, ...props }) => {
|
70
|
+
return (<>
|
71
|
+
<label htmlFor={props.id}>{label}</label>
|
72
|
+
{children || <input {...props}/>}
|
73
|
+
<FieldError errorKey={errorKey}/>
|
74
|
+
</>);
|
75
|
+
};
|
76
|
+
export const Checkbox = ({ type: _type, includeHidden, uncheckedValue, errorKey, ...rest }) => {
|
77
|
+
const { name } = rest;
|
78
|
+
return (<FieldBase {...rest} errorKey={errorKey}>
|
79
|
+
{includeHidden && (<input type="hidden" name={name} defaultValue={uncheckedValue} autoComplete="off"/>)}
|
80
|
+
<input type="checkbox" {...rest}></input>
|
81
|
+
</FieldBase>);
|
82
|
+
};
|
83
|
+
/**
|
84
|
+
* A collection checkbox component.
|
85
|
+
*
|
86
|
+
* 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).
|
87
|
+
* Mimics the rails equivalent. Please modify to your liking.
|
88
|
+
*/
|
89
|
+
export const CollectionCheckboxes = ({ includeHidden, collection, label, errorKey, }) => {
|
90
|
+
if (collection.length == 0) {
|
91
|
+
return null;
|
92
|
+
}
|
93
|
+
const checkboxes = collection.map((options) => {
|
94
|
+
return <Checkbox {...options} key={options.id}/>;
|
95
|
+
});
|
96
|
+
const { name } = collection[0];
|
97
|
+
return (<>
|
98
|
+
{includeHidden && (<input type="hidden" name={name} defaultValue={""} autoComplete="off"/>)}
|
99
|
+
<label>{label}</label>
|
100
|
+
{checkboxes}
|
101
|
+
<FieldError errorKey={errorKey}/>
|
102
|
+
</>);
|
103
|
+
};
|
104
|
+
/**
|
105
|
+
* A collection radio button component.
|
106
|
+
*
|
107
|
+
* 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).
|
108
|
+
* Mimics the rails equivalent. Please modify to your liking.
|
109
|
+
*/
|
110
|
+
export const CollectionRadioButtons = ({ includeHidden, collection, label, errorKey, }) => {
|
111
|
+
if (collection.length == 0) {
|
112
|
+
return null;
|
113
|
+
}
|
114
|
+
const radioButtons = collection.map((options) => {
|
115
|
+
return (<div key={options.value}>
|
116
|
+
<input {...options} type="radio"/>
|
117
|
+
<label htmlFor={options.id}>{options.label}</label>
|
118
|
+
</div>);
|
119
|
+
});
|
120
|
+
const { name } = collection[0];
|
121
|
+
return (<>
|
122
|
+
{includeHidden && (<input type="hidden" name={name} defaultValue={""} autoComplete="off"/>)}
|
123
|
+
<label>{label}</label>
|
124
|
+
{radioButtons}
|
125
|
+
<FieldError errorKey={errorKey}/>
|
126
|
+
</>);
|
127
|
+
};
|
128
|
+
/**
|
129
|
+
* A text field component.
|
130
|
+
*
|
131
|
+
* Designed to work with a payload form_props's [text_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#text-helpers).
|
132
|
+
* Mimics the rails equivalent. Please modify to your liking.
|
133
|
+
*/
|
134
|
+
export const TextField = ({ type: _type, ...rest }) => {
|
135
|
+
return <FieldBase {...rest} type="text"/>;
|
136
|
+
};
|
137
|
+
/**
|
138
|
+
* A email field component.
|
139
|
+
*
|
140
|
+
* Designed to work with a payload form_props's [email_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#text-helpers).
|
141
|
+
* Mimics the rails equivalent. Please modify to your liking.
|
142
|
+
*/
|
143
|
+
export const EmailField = ({ type: _type, ...rest }) => {
|
144
|
+
return <FieldBase {...rest} type="email"/>;
|
145
|
+
};
|
146
|
+
/**
|
147
|
+
* A color field component.
|
148
|
+
*
|
149
|
+
* Designed to work with a payload form_props's [color_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#text-helpers).
|
150
|
+
* Mimics the rails equivalent. Please modify to your liking.
|
151
|
+
*/
|
152
|
+
export const ColorField = ({ type: _type, ...rest }) => {
|
153
|
+
return <FieldBase {...rest} type="color"/>;
|
154
|
+
};
|
155
|
+
/**
|
156
|
+
* A date field component.
|
157
|
+
*
|
158
|
+
* Designed to work with a payload form_props's [date_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#date-helpers).
|
159
|
+
* Mimics the rails equivalent. Please modify to your liking.
|
160
|
+
*/
|
161
|
+
export const DateField = ({ type: _type, ...rest }) => {
|
162
|
+
return <FieldBase {...rest} type="date"/>;
|
163
|
+
};
|
164
|
+
/**
|
165
|
+
* A date field component.
|
166
|
+
*
|
167
|
+
* Designed to work with a payload form_props's [date_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#date-helpers).
|
168
|
+
* Mimics the rails equivalent. Please modify to your liking.
|
169
|
+
*/
|
170
|
+
export const DateTimeLocalField = ({ type: _type, ...rest }) => {
|
171
|
+
return <FieldBase {...rest} type="datetime-local"/>;
|
172
|
+
};
|
173
|
+
/**
|
174
|
+
* A search field component.
|
175
|
+
*
|
176
|
+
* Designed to work with a payload form_props's [search_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#text-helpers).
|
177
|
+
* Mimics the rails equivalent. Please modify to your liking.
|
178
|
+
*/
|
179
|
+
export const SearchField = ({ type: _type, ...rest }) => {
|
180
|
+
return <FieldBase {...rest} type="search"/>;
|
181
|
+
};
|
182
|
+
/**
|
183
|
+
* A tel field component.
|
184
|
+
*
|
185
|
+
* Designed to work with a payload form_props's [tel_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#text-helpers).
|
186
|
+
* Mimics the rails equivalent. Please modify to your liking.
|
187
|
+
*/
|
188
|
+
export const TelField = ({ type: _type, ...rest }) => {
|
189
|
+
return <FieldBase {...rest} type="tel"/>;
|
190
|
+
};
|
191
|
+
/**
|
192
|
+
* A url field component.
|
193
|
+
*
|
194
|
+
* Designed to work with a payload form_props's [tel_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#text-helpers).
|
195
|
+
* Mimics the rails equivalent. Please modify to your liking.
|
196
|
+
*/
|
197
|
+
export const UrlField = ({ type: _type, ...rest }) => {
|
198
|
+
return <FieldBase {...rest} type="url"/>;
|
199
|
+
};
|
200
|
+
/**
|
201
|
+
* A month field component.
|
202
|
+
*
|
203
|
+
* Designed to work with a payload form_props's [month_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#date-helpers).
|
204
|
+
* Mimics the rails equivalent. Please modify to your liking.
|
205
|
+
*/
|
206
|
+
export const MonthField = ({ type: _type, ...rest }) => {
|
207
|
+
return <FieldBase {...rest} type="month"/>;
|
208
|
+
};
|
209
|
+
/**
|
210
|
+
* A month field component.
|
211
|
+
*
|
212
|
+
* Designed to work with a payload form_props's [month_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#date-helpers).
|
213
|
+
* Mimics the rails equivalent. Please modify to your liking.
|
214
|
+
*/
|
215
|
+
export const TimeField = ({ type: _type, ...rest }) => {
|
216
|
+
return <FieldBase {...rest} type="time"/>;
|
217
|
+
};
|
218
|
+
/**
|
219
|
+
* A number field component.
|
220
|
+
*
|
221
|
+
* Designed to work with a payload form_props's [month_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#number-helpers).
|
222
|
+
* Mimics the rails equivalent. Please modify to your liking.
|
223
|
+
*/
|
224
|
+
export const NumberField = ({ type: _type, ...rest }) => {
|
225
|
+
return <FieldBase {...rest} type="number"/>;
|
226
|
+
};
|
227
|
+
/**
|
228
|
+
* A range field component.
|
229
|
+
*
|
230
|
+
* Designed to work with a payload form_props's [range_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#number-helpers).
|
231
|
+
* Mimics the rails equivalent. Please modify to your liking.
|
232
|
+
*/
|
233
|
+
export const RangeField = ({ type: _type, ...rest }) => {
|
234
|
+
return <FieldBase {...rest} type="range"/>;
|
235
|
+
};
|
236
|
+
/**
|
237
|
+
* A password field component.
|
238
|
+
*
|
239
|
+
* Designed to work with a payload form_props's [password_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#text-helpers).
|
240
|
+
* Mimics the rails equivalent. Please modify to your liking.
|
241
|
+
*/
|
242
|
+
export const PasswordField = ({ type: _type, ...rest }) => {
|
243
|
+
return <FieldBase {...rest} type="password"/>;
|
244
|
+
};
|
245
|
+
/**
|
246
|
+
* A select component.
|
247
|
+
*
|
248
|
+
* Designed to work with a payload form_props's [select helpers](https://github.com/thoughtbot/form_props?tab=readme-ov-file#select-helpers),
|
249
|
+
* [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).
|
250
|
+
*
|
251
|
+
* Please modify to your liking.
|
252
|
+
*/
|
253
|
+
export const Select = ({ includeHidden, name, id, children, options, multiple, type: _type, ...rest }) => {
|
254
|
+
const addHidden = includeHidden && multiple;
|
255
|
+
const optionElements = options.map((item) => {
|
256
|
+
if ("options" in item) {
|
257
|
+
return (<optgroup label={item.label} key={item.label}>
|
258
|
+
{item.options.map((opt) => (<option key={opt.label} {...opt}/>))}
|
259
|
+
</optgroup>);
|
260
|
+
}
|
261
|
+
else {
|
262
|
+
return <option key={item.label} {...item}/>;
|
263
|
+
}
|
264
|
+
});
|
265
|
+
return (<>
|
266
|
+
{addHidden && (<input type="hidden" name={name} value={""} autoComplete="off"/>)}
|
267
|
+
<select name={name} id={id} multiple={multiple} {...rest}>
|
268
|
+
{children}
|
269
|
+
{optionElements}
|
270
|
+
</select>
|
271
|
+
</>);
|
272
|
+
};
|
273
|
+
/**
|
274
|
+
* A text area component.
|
275
|
+
*
|
276
|
+
* Designed to work with a payload form_props's text_area helper.
|
277
|
+
* Mimics the rails equivalent. Please modify to your liking.
|
278
|
+
*/
|
279
|
+
export const TextArea = ({ type: _type, errorKey, ...rest }) => {
|
280
|
+
const { label } = rest;
|
281
|
+
return (<FieldBase label={label} errorKey={errorKey} id={rest.id}>
|
282
|
+
<textarea {...rest}/>
|
283
|
+
</FieldBase>);
|
284
|
+
};
|
285
|
+
/**
|
286
|
+
* A file field component.
|
287
|
+
*
|
288
|
+
* Designed to work with a payload form_props's [file_field helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#text-helpers).
|
289
|
+
* Mimics the rails equivalent. Please modify to your liking.
|
290
|
+
*/
|
291
|
+
export const FileField = ({ type: _type, ...rest }) => {
|
292
|
+
return <FieldBase {...rest} type="file"/>;
|
293
|
+
};
|
294
|
+
/**
|
295
|
+
* A SubmitButton component.
|
296
|
+
*
|
297
|
+
* Designed to work with a payload form_props's [submit helper](https://github.com/thoughtbot/form_props?tab=readme-ov-file#form-helpers).
|
298
|
+
* Mimics the rails equivalent. Please modify to your liking.
|
299
|
+
*/
|
300
|
+
export const SubmitButton = ({ type: _type, text, ...rest }) => {
|
301
|
+
return <button {...rest} type="submit"> {text} </button>;
|
302
|
+
};
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import { useSelector } from 'react-redux'
|
3
|
+
|
4
|
+
export const Layout = ({children}) => {
|
5
|
+
const flash = useSelector((state) => state.flash)
|
6
|
+
|
7
|
+
return (
|
8
|
+
<div>
|
9
|
+
{flash.success && <p>{flash.success}</p>}
|
10
|
+
{flash.notice && <p>{flash.notice}</p>}
|
11
|
+
{flash.error && <p>{flash.error}</p>}
|
12
|
+
|
13
|
+
{children}
|
14
|
+
</div>
|
15
|
+
)
|
16
|
+
}
|