From b26b8abb9b35650b050f3856d719c3485fd9c308 Mon Sep 17 00:00:00 2001 From: gingervitis Date: Sun, 31 Jan 2021 21:36:14 -0800 Subject: [PATCH] finalize layout of textfields; add field status component --- .../config/edit-instance-details.tsx | 5 +- .../components/config/edit-server-details.tsx | 5 +- .../components/config/edit-social-links.tsx | 4 +- .../config/form-textfield-with-submit.tsx | 51 ++++-- .../components/config/form-textfield.tsx | 29 ++-- .../components/config/input-status-info.tsx | 22 +++ web/pages/config-public-details.tsx | 5 +- web/styles/config-formfields.scss | 157 +++++++++++++----- web/styles/config-pages.module.scss | 33 ---- web/utils/input-statuses.tsx | 12 +- 10 files changed, 203 insertions(+), 120 deletions(-) create mode 100644 web/pages/components/config/input-status-info.tsx delete mode 100644 web/styles/config-pages.module.scss diff --git a/web/pages/components/config/edit-instance-details.tsx b/web/pages/components/config/edit-instance-details.tsx index bc84616ea..177c6fd4a 100644 --- a/web/pages/components/config/edit-instance-details.tsx +++ b/web/pages/components/config/edit-instance-details.tsx @@ -16,7 +16,6 @@ import { API_YP_SWITCH, } from './constants'; -import configStyles from '../../../styles/config-pages.module.scss'; import { UpdateArgs } from '../../../types/config-section'; export default function EditInstanceDetails() { @@ -57,8 +56,8 @@ export default function EditInstanceDetails() { }; return ( -
-
+
+
-
+
+
+
Social Links

Add all your social media handles and links to your other profiles here.

diff --git a/web/pages/components/config/form-textfield-with-submit.tsx b/web/pages/components/config/form-textfield-with-submit.tsx index f0cfa1890..e24690c31 100644 --- a/web/pages/components/config/form-textfield-with-submit.tsx +++ b/web/pages/components/config/form-textfield-with-submit.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState, useContext } from 'react'; import { Button } from 'antd'; - +import classNames from 'classnames'; import { RESET_TIMEOUT, postConfigUpdateToAPI } from './constants'; import { ServerStatusContext } from '../../../utils/server-status-context'; @@ -13,6 +13,7 @@ import { STATUS_SUCCESS, } from '../../../utils/input-statuses'; import { UpdateArgs } from '../../../types/config-section'; +import InputStatusInfo from './input-status-info'; export const TEXTFIELD_TYPE_TEXT = 'default'; export const TEXTFIELD_TYPE_PASSWORD = 'password'; // Input.Password @@ -43,7 +44,7 @@ export default function TextFieldWithSubmit(props: TextFieldWithSubmitProps) { ...textFieldProps // rest of props } = props; - const { fieldName, required, status, value, onChange, onSubmit } = textFieldProps; + const { fieldName, required, tip, status, value, onChange, onSubmit } = textFieldProps; // Clear out any validation states and messaging const resetStates = () => { @@ -105,23 +106,39 @@ export default function TextFieldWithSubmit(props: TextFieldWithSubmitProps) { } }; + const textfieldContainerClass = classNames({ + 'textfield-with-submit-container': true, + submittable: hasChanged, + }); return ( -
- - - {hasChanged ? ( -
- +
+
+ +
+
+

+

+
{tip}
+ +
+ +
- ) : null} +
); } diff --git a/web/pages/components/config/form-textfield.tsx b/web/pages/components/config/form-textfield.tsx index b2faeb632..fd3940bc3 100644 --- a/web/pages/components/config/form-textfield.tsx +++ b/web/pages/components/config/form-textfield.tsx @@ -1,8 +1,10 @@ import React from 'react'; +import classNames from 'classnames'; import { Input, InputNumber } from 'antd'; import { FieldUpdaterFunc } from '../../../types/config-section'; -import InfoTip from '../info-tip'; +// import InfoTip from '../info-tip'; import { StatusState } from '../../../utils/input-statuses'; +import InputStatusInfo from './input-status-info'; export const TEXTFIELD_TYPE_TEXT = 'default'; export const TEXTFIELD_TYPE_PASSWORD = 'password'; // Input.Password @@ -107,15 +109,20 @@ export default function TextField(props: TextFieldProps) { const fieldId = `field-${fieldName}`; - const { icon: statusIcon, message: statusMessage } = status || {}; + const { type: statusType } = status || {}; + const containerClass = classNames({ + 'textfield-container': true, + [`type-${type}`]: true, + required, + [`status-${statusType}`]: status, + }); return ( -
+
{label ? (
) : null} @@ -135,17 +142,13 @@ export default function TextField(props: TextFieldProps) { disabled={disabled} value={value} /> -
-
- {status ? {statusIcon} : null} - {status ? {statusMessage} : null} -
-

- + +

+ {tip} + {/* */}

-
); } diff --git a/web/pages/components/config/input-status-info.tsx b/web/pages/components/config/input-status-info.tsx new file mode 100644 index 000000000..bd7626f96 --- /dev/null +++ b/web/pages/components/config/input-status-info.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import classNames from 'classnames'; + +import { StatusState } from '../../../utils/input-statuses'; + +interface InputStatusInfoProps { + status: StatusState; +} +export default function InputStatusInfo({ status }: InputStatusInfoProps) { + const { type, icon, message } = status || {}; + const classes = classNames({ + 'status-container': true, + [`status-${type}`]: type, + empty: !message, + }); + return ( +
+ {icon ? {icon} : null} + {message ? {message} : null} +
+ ); +} diff --git a/web/pages/config-public-details.tsx b/web/pages/config-public-details.tsx index fb965301a..b18adefd0 100644 --- a/web/pages/config-public-details.tsx +++ b/web/pages/config-public-details.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { Typography } from 'antd'; import Link from 'next/link'; -import configStyles from '../styles/config-pages.module.scss'; import EditInstanceDetails from './components/config/edit-instance-details'; const { Title } = Typography; @@ -12,8 +11,8 @@ export default function PublicFacingDetails() { <> Edit your public facing instance details -
-
+
+
diff --git a/web/styles/config-formfields.scss b/web/styles/config-formfields.scss index 327bfa407..bb379031c 100644 --- a/web/styles/config-formfields.scss +++ b/web/styles/config-formfields.scss @@ -1,35 +1,75 @@ +// Base styles for form-textfield, form-textfield-with-submit, and helper components. + +.status-container { + &.status-success { + color: var(--ant-success); + } + &.status-error { + color: var(--ant-error); + } + &.status-warning { + color: var(--ant-warning); + } + + &.empty { + display: none; + } + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + font-size: .75rem; + .status-icon { + display: inline-block; + margin-right: .5em; + } +} + +.field-tip { + font-size: .7em; + color: rgba(255,255,255,.7) +} + + .textfield-container { display: flex; flex-direction: row; align-items: flex-start; justify-content: flex-start; width: 100%; - - + max-width: 600px; .label-side { - padding-right: 1em; + padding-right: .75em; text-align: right; - width: 12rem; + width: 12em; margin: .2em 0; } .textfield-label { font-weight: 400; - font-size: .85rem; + font-size: .85em; color: var(--owncast-purple); - } - .required-label { - color: var(--ant-error); - } + &::after { + content: ':'; + } + } + &.required { + .textfield-label { + &::before { + content: '*'; + display: inline-block; + margin-right: .25em; + color: var(--ant-error); + } + } + } .input-side { max-width: 500px; width: 100%; } - - .input-group, - .status-container { + .input-group { display: flex; flex-direction: row; justify-content: flex-start; @@ -37,49 +77,88 @@ } .status-container { - margin: 0 .25em; - min-height: 1.5em; - font-size: .75em; - - .status-icon { + margin: .25em; + width: 100%; + display: block; + &.empty { display: inline-block; - margin-right: .5em; + visibility: visible; } } - .tip { + .field-tip { margin: .5em .5em; - font-size: .75rem; - color: rgba(255,255,255,.75); } @media (max-width: 800px) { - // flex-direction: column; flex-wrap: wrap; .label-side { + width: 100%; text-align: left; } } } -.status-message { - // margin: 1rem 0; - // min-height: 1.4em; - // font-size: .75rem; - &.success { - color: var(--ant-success); - } - &.error { - color: var(--ant-error); - } -} - .textfield-with-submit-container { display: flex; - flex-direction: row; - .update-button-container { - display: inline-block; - margin: .25em; + flex-direction: column; + align-items: flex-start; + margin-bottom: 1em; + + .textfield-component { + width: 100%; + .textfield-container { + .field-tip, + .status-container { + display: none; + } + } } -} \ No newline at end of file + + // for lack of a better name + .lower-container { + display: flex; + flex-direction: row; + align-items: flex-start; + justify-content: flex-start; + + .label-spacer { + width: 12em; + } + .lower-content { + display: flex; + flex-direction: row; + align-items: flex-start; + justify-content: flex-end; + width: 100%; + + .field-tip { + margin-right: 1em; + width: 100%; + } + .status-container { + margin: .5em; + } + } + .update-button-container { + visibility: hidden; + margin: .25em 0; + } + } + + &.submittable { + .lower-container { + .update-button-container { + visibility: visible; + } + } + } + + + @media (max-width: 800px) { + .label-spacer { + display: none; + } + } +} diff --git a/web/styles/config-pages.module.scss b/web/styles/config-pages.module.scss deleted file mode 100644 index 4024444f5..000000000 --- a/web/styles/config-pages.module.scss +++ /dev/null @@ -1,33 +0,0 @@ -.publicDetailsContainer { - display: flex; - flex-direction: row; - align-items: flex-start; - flex-wrap: wrap; - - .textFieldsSection { - margin-right: 2rem; - } -} - - - -.socialLinksEditor { - width: 20rem; - margin: 2em 0; -} - - - -.tag-editor-container, -.config-directory-details-form { - border-radius: 1em; - background-color: rgba(128,99,255,.1); - padding: 1.5em; - margin-bottom: 1em; -} - -////////////////////////////// -// common? -.dataTable { - -} \ No newline at end of file diff --git a/web/utils/input-statuses.tsx b/web/utils/input-statuses.tsx index 6bba60fdd..9dc62f626 100644 --- a/web/utils/input-statuses.tsx +++ b/web/utils/input-statuses.tsx @@ -13,12 +13,7 @@ export const STATUS_PROCESSING = 'proessing'; export const STATUS_SUCCESS = 'success'; export const STATUS_WARNING = 'warning'; -export type InputStatusTypes = - | typeof STATUS_ERROR - | typeof STATUS_INVALID - | typeof STATUS_PROCESSING - | typeof STATUS_SUCCESS - | typeof STATUS_WARNING; +export type InputStatusTypes = 'error' | 'invalid' | 'proessing' | 'success' | 'warning'; export type StatusState = { type: InputStatusTypes; @@ -28,22 +23,27 @@ export type StatusState = { export const INPUT_STATES = { [STATUS_SUCCESS]: { + type: STATUS_SUCCESS, icon: , message: 'Success!', }, [STATUS_ERROR]: { + type: STATUS_ERROR, icon: , message: 'An error occurred.', }, [STATUS_INVALID]: { + type: STATUS_INVALID, icon: , message: 'An error occurred.', }, [STATUS_PROCESSING]: { + type: STATUS_PROCESSING, icon: , message: '', }, [STATUS_WARNING]: { + type: STATUS_WARNING, icon: , message: '', },