From 063ceb215f858a8e2a5bde85d8f9ca24240894c6 Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Mon, 21 Sep 2020 15:20:41 +0900
Subject: Separate input component

---
 src/settings/components/ui/Radio.tsx | 44 ++++++++++++++++++++++++++++++++++++
 1 file changed, 44 insertions(+)
 create mode 100644 src/settings/components/ui/Radio.tsx

(limited to 'src/settings/components/ui/Radio.tsx')

diff --git a/src/settings/components/ui/Radio.tsx b/src/settings/components/ui/Radio.tsx
new file mode 100644
index 0000000..20d4ad6
--- /dev/null
+++ b/src/settings/components/ui/Radio.tsx
@@ -0,0 +1,44 @@
+import React from "react";
+import "./Input.scss";
+
+interface Props extends React.AllHTMLAttributes<HTMLElement> {
+  name: string;
+  error?: string;
+  label: string;
+  value: string;
+  onValueChange?: (name: string, value: string) => void;
+  onBlur?: (e: React.FocusEvent<Element>) => void;
+}
+
+class Input extends React.Component<Props> {
+  renderRadio(props: Props) {
+    const inputClassName = props.error ? "input-error" : "";
+    const pp = { ...props };
+    delete pp.onValueChange;
+    return (
+      <div className="settings-ui-input">
+        <label>
+          <input
+            type="radio"
+            className={inputClassName}
+            onChange={this.bindOnChange.bind(this)}
+            {...pp}
+          />
+          {props.label}
+        </label>
+      </div>
+    );
+  }
+
+  render() {
+    return this.renderRadio(this.props);
+  }
+
+  bindOnChange(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
+    if (this.props.onValueChange) {
+      this.props.onValueChange(e.target.name, e.target.value);
+    }
+  }
+}
+
+export default Input;
-- 
cgit v1.2.3


From c28ae6b82860693b8e6b012a939fb8a3dd320e23 Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Mon, 21 Sep 2020 16:08:44 +0900
Subject: Introduce styled-components on settings ui

---
 src/settings/components/site.scss            |  8 +--
 src/settings/components/ui/AddButton.scss    | 13 -----
 src/settings/components/ui/AddButton.tsx     | 31 +++++++-----
 src/settings/components/ui/DeleteButton.scss | 13 -----
 src/settings/components/ui/DeleteButton.tsx  | 30 ++++++-----
 src/settings/components/ui/Input.scss        | 29 -----------
 src/settings/components/ui/Radio.tsx         | 59 +++++++++------------
 src/settings/components/ui/Text.tsx          | 70 ++++++++++++++-----------
 src/settings/components/ui/TextArea.tsx      | 76 ++++++++++++++++------------
 9 files changed, 145 insertions(+), 184 deletions(-)
 delete mode 100644 src/settings/components/ui/AddButton.scss
 delete mode 100644 src/settings/components/ui/DeleteButton.scss
 delete mode 100644 src/settings/components/ui/Input.scss

(limited to 'src/settings/components/ui/Radio.tsx')

diff --git a/src/settings/components/site.scss b/src/settings/components/site.scss
index c0c4f9e..e8415e8 100644
--- a/src/settings/components/site.scss
+++ b/src/settings/components/site.scss
@@ -1,12 +1,6 @@
 .vimvixen-settings-form {
   padding: 2px;
-
-  textarea[name=json] {
-    font-family: monospace;
-    width: 100%;
-    min-height: 64ex;
-    resize: vertical;
-  }
+  font-family: system-ui;
 
   fieldset {
     margin: 0;
diff --git a/src/settings/components/ui/AddButton.scss b/src/settings/components/ui/AddButton.scss
deleted file mode 100644
index beb5688..0000000
--- a/src/settings/components/ui/AddButton.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-.ui-add-button {
-  border: none;
-  padding: 4;
-  display: inline;
-  background: none;
-  font-weight: bold;
-  color: green;
-  cursor: pointer;
-
-  &:hover {
-    color: darkgreen;
-  }
-}
diff --git a/src/settings/components/ui/AddButton.tsx b/src/settings/components/ui/AddButton.tsx
index c15a732..8cf4300 100644
--- a/src/settings/components/ui/AddButton.tsx
+++ b/src/settings/components/ui/AddButton.tsx
@@ -1,19 +1,24 @@
-import "./AddButton.scss";
 import React from "react";
+import styled from "styled-components";
 
-type Props = React.AllHTMLAttributes<HTMLInputElement>;
+const Button = styled.input`
+  border: none;
+  padding: 4;
+  display: inline;
+  background: none;
+  font-weight: bold;
+  color: green;
+  cursor: pointer;
 
-class AddButton extends React.Component<Props> {
-  render() {
-    return (
-      <input
-        className="ui-add-button"
-        type="button"
-        value="&#x271a;"
-        {...this.props}
-      />
-    );
+  &:hover {
+    color: darkgreen;
   }
-}
+`;
+
+type Props = React.InputHTMLAttributes<HTMLInputElement>;
+
+const AddButton: React.FC<Props> = (props) => (
+  <Button type="button" value="&#x271a;" {...props} />
+);
 
 export default AddButton;
diff --git a/src/settings/components/ui/DeleteButton.scss b/src/settings/components/ui/DeleteButton.scss
deleted file mode 100644
index 5932a72..0000000
--- a/src/settings/components/ui/DeleteButton.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-
-.ui-delete-button {
-  border: none;
-  padding: 4;
-  display: inline;
-  background: none;
-  color: red;
-  cursor: pointer;
-
-  &:hover {
-    color: darkred;
-  }
-}
diff --git a/src/settings/components/ui/DeleteButton.tsx b/src/settings/components/ui/DeleteButton.tsx
index df8976e..ce0183b 100644
--- a/src/settings/components/ui/DeleteButton.tsx
+++ b/src/settings/components/ui/DeleteButton.tsx
@@ -1,19 +1,23 @@
-import "./DeleteButton.scss";
 import React from "react";
+import styled from "styled-components";
 
-type Props = React.AllHTMLAttributes<HTMLInputElement>;
+const Button = styled.input`
+  border: none;
+  padding: 4;
+  display: inline;
+  background: none;
+  color: red;
+  cursor: pointer;
 
-class DeleteButton extends React.Component<Props> {
-  render() {
-    return (
-      <input
-        className="ui-delete-button"
-        type="button"
-        value="&#x2716;"
-        {...this.props}
-      />
-    );
+  &:hover {
+    color: darkred;
   }
-}
+`;
+
+type Props = React.InputHTMLAttributes<HTMLInputElement>;
+
+const DeleteButton: React.FC<Props> = (props) => (
+  <Button type="button" value="&#x2716;" {...props} />
+);
 
 export default DeleteButton;
diff --git a/src/settings/components/ui/Input.scss b/src/settings/components/ui/Input.scss
deleted file mode 100644
index ad4daf8..0000000
--- a/src/settings/components/ui/Input.scss
+++ /dev/null
@@ -1,29 +0,0 @@
-.settings-ui-input {
-  page-break-inside: avoid;
-
-  * {
-    page-break-inside: avoid;
-  }
-
-  label {
-    font-weight: bold;
-    min-width: 14rem;
-    display: inline-block;
-  }
-
-  input[type='text'] {
-    padding: 4px;
-    width: 8rem;
-  }
-
-  input.input-crror,
-  textarea.input-error {
-    box-shadow: 0 0 2px red;
-  }
-
-  &-error {
-    font-weight: bold;
-    color: red;
-    min-height: 1.5em;
-  }
-}
diff --git a/src/settings/components/ui/Radio.tsx b/src/settings/components/ui/Radio.tsx
index 20d4ad6..c0d4dd9 100644
--- a/src/settings/components/ui/Radio.tsx
+++ b/src/settings/components/ui/Radio.tsx
@@ -1,44 +1,33 @@
 import React from "react";
-import "./Input.scss";
+import styled from "styled-components";
 
-interface Props extends React.AllHTMLAttributes<HTMLElement> {
-  name: string;
-  error?: string;
+const Container = styled.div`
+  font-family: system-ui;
+`;
+
+interface Props extends React.InputHTMLAttributes<HTMLInputElement> {
   label: string;
-  value: string;
   onValueChange?: (name: string, value: string) => void;
-  onBlur?: (e: React.FocusEvent<Element>) => void;
 }
 
-class Input extends React.Component<Props> {
-  renderRadio(props: Props) {
-    const inputClassName = props.error ? "input-error" : "";
-    const pp = { ...props };
-    delete pp.onValueChange;
-    return (
-      <div className="settings-ui-input">
-        <label>
-          <input
-            type="radio"
-            className={inputClassName}
-            onChange={this.bindOnChange.bind(this)}
-            {...pp}
-          />
-          {props.label}
-        </label>
-      </div>
-    );
-  }
+const Radio: React.FC<Props> = (props) => {
+  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+    if (props.onValueChange) {
+      props.onValueChange(e.target.name, e.target.value);
+    }
+  };
 
-  render() {
-    return this.renderRadio(this.props);
-  }
+  const pp = { ...props };
+  delete pp.onValueChange;
 
-  bindOnChange(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
-    if (this.props.onValueChange) {
-      this.props.onValueChange(e.target.name, e.target.value);
-    }
-  }
-}
+  return (
+    <Container>
+      <label htmlFor={props.id}>
+        <input type="radio" onChange={onChange} {...pp} />
+        {props.label}
+      </label>
+    </Container>
+  );
+};
 
-export default Input;
+export default Radio;
diff --git a/src/settings/components/ui/Text.tsx b/src/settings/components/ui/Text.tsx
index 7ecef88..700b08a 100644
--- a/src/settings/components/ui/Text.tsx
+++ b/src/settings/components/ui/Text.tsx
@@ -1,42 +1,54 @@
 import React from "react";
-import "./Input.scss";
+import styled from "styled-components";
 
-interface Props extends React.AllHTMLAttributes<HTMLElement> {
+const Container = styled.div`
+  page-break-inside: avoid;
+`;
+
+const Input = styled.input<{ hasError: boolean }>`
+  padding: 4px;
+  width: 8rem;
+  box-shadow: ${({ hasError }) => (hasError ? "0 0 2px red" : "none")};
+`;
+
+const Label = styled.label`
+  font-weight: bold;
+  min-width: 14rem;
+  display: inline-block;
+`;
+
+interface Props extends React.HTMLAttributes<HTMLElement> {
   name: string;
   error?: string;
   label: string;
   value: string;
   onValueChange?: (name: string, value: string) => void;
-  onBlur?: (e: React.FocusEvent<Element>) => void;
 }
 
-class Input extends React.Component<Props> {
-  renderText(props: Props) {
-    const inputClassName = props.error ? "input-error" : "";
-    const pp = { ...props };
-    delete pp.onValueChange;
-    return (
-      <div className="settings-ui-input">
-        <label htmlFor={props.id}>{props.label}</label>
-        <input
+const Text: React.FC<Props> = (props) => {
+  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+    if (props.onValueChange) {
+      props.onValueChange(e.target.name, e.target.value);
+    }
+  };
+
+  const pp = { ...props };
+  delete pp.onValueChange;
+
+  return (
+    <Container>
+      <Label>
+        {props.label}
+        <br />
+        <Input
           type="text"
-          className={inputClassName}
-          onChange={this.bindOnChange.bind(this)}
+          hasError={props.error !== undefined}
+          onChange={onChange}
           {...pp}
         />
-      </div>
-    );
-  }
-
-  render() {
-    return this.renderText(this.props);
-  }
-
-  bindOnChange(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
-    if (this.props.onValueChange) {
-      this.props.onValueChange(e.target.name, e.target.value);
-    }
-  }
-}
+      </Label>
+    </Container>
+  );
+};
 
-export default Input;
+export default Text;
diff --git a/src/settings/components/ui/TextArea.tsx b/src/settings/components/ui/TextArea.tsx
index f2b832f..58b7bd3 100644
--- a/src/settings/components/ui/TextArea.tsx
+++ b/src/settings/components/ui/TextArea.tsx
@@ -1,42 +1,54 @@
 import React from "react";
-import "./Input.scss";
+import styled from "styled-components";
 
-interface Props extends React.AllHTMLAttributes<HTMLElement> {
-  name: string;
+const Container = styled.div`
+  page-break-inside: avoid;
+`;
+
+const Label = styled.label`
+  font-weight: bold;
+  min-width: 14rem;
+  display: inline-block;
+`;
+
+const ErrorableTextArea = styled.textarea<{ hasError: boolean }>`
+  box-shadow: ${({ hasError }) => (hasError ? "0 0 2px red" : "none")};
+  font-family: monospace;
+  font-family: monospace;
+  width: 100%;
+  min-height: 64ex;
+  resize: vertical;
+`;
+
+const ErrorMessage = styled.p`
+  font-weight: bold;
+  color: red;
+  min-height: 1.5em;
+`;
+
+interface Props extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
   error?: string;
   label: string;
-  value: string;
   onValueChange?: (name: string, value: string) => void;
-  onBlur?: (e: React.FocusEvent<Element>) => void;
 }
 
-class TextArea extends React.Component<Props> {
-  renderTextArea(props: Props) {
-    const inputClassName = props.error ? "input-error" : "";
-    const pp = { ...props };
-    delete pp.onValueChange;
-    return (
-      <div className="settings-ui-input">
-        <label htmlFor={props.id}>{props.label}</label>
-        <textarea
-          className={inputClassName}
-          onChange={this.bindOnChange.bind(this)}
-          {...pp}
-        />
-        <p className="settings-ui-input-error">{this.props.error}</p>
-      </div>
-    );
-  }
-
-  render() {
-    return this.renderTextArea(this.props);
-  }
-
-  bindOnChange(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
-    if (this.props.onValueChange) {
-      this.props.onValueChange(e.target.name, e.target.value);
+const TextArea: React.FC<Props> = (props) => {
+  const onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
+    if (props.onValueChange) {
+      props.onValueChange(e.target.name, e.target.value);
     }
-  }
-}
+  };
+
+  const hasError = typeof props.error !== "undefined" && props.error !== "";
+  const pp = { ...props };
+  delete pp.onValueChange;
+  return (
+    <Container>
+      <Label htmlFor={props.id}>{props.label}</Label>
+      <ErrorableTextArea hasError={hasError} onChange={onChange} {...pp} />
+      {hasError ? <ErrorMessage>{props.error}</ErrorMessage> : null}
+    </Container>
+  );
+};
 
 export default TextArea;
-- 
cgit v1.2.3

