(core) Polish forms

Summary:
  - Updates styling of form submitted page.
  - Tweaks styling of checkboxes, labels, and questions on form page.
  - Adds new form 404 page.
  - Adds checkbox to not show warning again when publishing or un-publishing a form.
  - Excludes formula, hidden, and attachment columns in submitted form data.
  - Adds placeholder text to form configuration inputs.
  - Improves dark mode styling in Form widget.
  - Updates default title and description of new forms.
  - Updates styling of Form widget buttons.
  - Fixes form success text input handling.

Test Plan: Browser tests.

Reviewers: jarek

Reviewed By: jarek

Differential Revision: https://phab.getgrist.com/D4170
This commit is contained in:
George Gevoian
2024-01-24 01:58:19 -08:00
parent b77c762358
commit 6cb8614017
26 changed files with 769 additions and 302 deletions

View File

@@ -37,7 +37,8 @@ export interface ApiErrorDetails {
}
export type ApiErrorCode =
| 'UserNotConfirmed';
| 'UserNotConfirmed'
| 'FormNotFound';
/**
* An error with an http status code.

View File

@@ -1,3 +1,4 @@
import {isHiddenCol} from 'app/common/gristTypes';
import {CellValue, GristType} from 'app/plugin/GristData';
import {MaybePromise} from 'app/plugin/gutil';
import _ from 'lodash';
@@ -70,6 +71,7 @@ export interface FieldModel {
description: string;
colId: string;
type: string;
isFormula: boolean;
options: FieldOptions;
values(): MaybePromise<[number, CellValue][]>;
}
@@ -202,10 +204,19 @@ abstract class BaseQuestion implements Question {
`;
}
public name(field: FieldModel): string {
const excludeFromFormData = (
field.isFormula ||
field.type === 'Attachments' ||
isHiddenCol(field.colId)
);
return `${excludeFromFormData ? '_' : ''}${field.colId}`;
}
public label(field: FieldModel): string {
// This might be HTML.
const label = field.question;
const name = field.colId;
const name = this.name(field);
return `
<label class='grist-label' for='${name}'>${label}</label>
`;
@@ -218,7 +229,7 @@ class Text extends BaseQuestion {
public input(field: FieldModel, context: RenderContext): string {
const required = field.options.formRequired ? 'required' : '';
return `
<input type='text' name='${field.colId}' ${required}/>
<input type='text' name='${this.name(field)}' ${required}/>
`;
}
}
@@ -227,7 +238,7 @@ class Date extends BaseQuestion {
public input(field: FieldModel, context: RenderContext): string {
const required = field.options.formRequired ? 'required' : '';
return `
<input type='date' name='${field.colId}' ${required}/>
<input type='date' name='${this.name(field)}' ${required}/>
`;
}
}
@@ -236,7 +247,7 @@ class DateTime extends BaseQuestion {
public input(field: FieldModel, context: RenderContext): string {
const required = field.options.formRequired ? 'required' : '';
return `
<input type='datetime-local' name='${field.colId}' ${required}/>
<input type='datetime-local' name='${this.name(field)}' ${required}/>
`;
}
}
@@ -248,7 +259,7 @@ class Choice extends BaseQuestion {
// Insert empty option.
choices.unshift('');
return `
<select name='${field.colId}' ${required} >
<select name='${this.name(field)}' ${required} >
${choices.map((choice) => `<option value='${choice}'>${choice}</option>`).join('')}
</select>
`;
@@ -271,7 +282,7 @@ class Bool extends BaseQuestion {
const label = field.question ? field.question : field.colId;
return `
<label class='grist-switch'>
<input type='checkbox' name='${field.colId}' value="1" ${required} />
<input type='checkbox' name='${this.name(field)}' value="1" ${required} />
<div class="grist-widget_switch grist-switch_transition">
<div class="grist-switch_slider"></div>
<div class="grist-switch_circle"></div>
@@ -287,10 +298,10 @@ class ChoiceList extends BaseQuestion {
const required = field.options.formRequired ? 'required' : '';
const choices: string[] = field.options.choices || [];
return `
<div name='${field.colId}' class='grist-choice-list grist-checkbox-list ${required}'>
<div name='${this.name(field)}' class='grist-checkbox-list ${required}'>
${choices.map((choice) => `
<label>
<input type='checkbox' name='${field.colId}[]' value='${choice}' />
<label class='grist-checkbox'>
<input type='checkbox' name='${this.name(field)}[]' value='${choice}' />
<span>
${choice}
</span>
@@ -310,12 +321,12 @@ class RefList extends BaseQuestion {
// Support for 30 choices, TODO: make it dynamic.
choices.splice(30);
return `
<div name='${field.colId}' class='grist-ref-list grist-checkbox-list ${required}'>
<div name='${this.name(field)}' class='grist-checkbox-list ${required}'>
${choices.map((choice) => `
<label class='grist-checkbox'>
<input type='checkbox'
data-grist-type='${field.type}'
name='${field.colId}[]'
name='${this.name(field)}[]'
value='${String(choice[0])}' />
<span>
${String(choice[1] ?? '')}
@@ -339,7 +350,7 @@ class Ref extends BaseQuestion {
// <option type='number' is not standard, we parse it ourselves.
const required = field.options.formRequired ? 'required' : '';
return `
<select name='${field.colId}' class='grist-ref' data-grist-type='${field.type}' ${required}>
<select name='${this.name(field)}' class='grist-ref' data-grist-type='${field.type}' ${required}>
${choices.map((choice) => `<option value='${String(choice[0])}'>${String(choice[1] ?? '')}</option>`).join('')}
</select>
`;

View File

@@ -102,12 +102,14 @@ export interface BehavioralPromptPrefs {
* List of all popups that user can see and dismiss
*/
export const DismissedPopup = StringUnion(
'deleteRecords', // confirmation for deleting records keyboard shortcut,
'deleteFields', // confirmation for deleting columns keyboard shortcut,
'tutorialFirstCard', // first card of the tutorial,
'formulaHelpInfo', // formula help info shown in the popup editor,
'formulaAssistantInfo', // formula assistant info shown in the popup editor,
'supportGrist', // nudge to opt in to telemetry,
'deleteRecords', // confirmation for deleting records keyboard shortcut
'deleteFields', // confirmation for deleting columns keyboard shortcut
'tutorialFirstCard', // first card of the tutorial
'formulaHelpInfo', // formula help info shown in the popup editor
'formulaAssistantInfo', // formula assistant info shown in the popup editor
'supportGrist', // nudge to opt in to telemetry
'publishForm', // confirmation for publishing a form
'unpublishForm', // confirmation for unpublishing a form
);
export type DismissedPopup = typeof DismissedPopup.type;

View File

@@ -27,6 +27,7 @@ export const Theme = t.iface([], {
export const ThemeColors = t.iface([], {
"text": "string",
"text-light": "string",
"text-medium": "string",
"text-dark": "string",
"text-error": "string",
"text-error-hover": "string",

View File

@@ -25,6 +25,7 @@ export interface ThemeColors {
/* Text */
'text': string;
'text-light': string;
'text-medium': string;
'text-dark': string;
'text-error': string;
'text-error-hover': string;

View File

@@ -4,6 +4,7 @@ export const GristDark: ThemeColors = {
/* Text */
'text': '#EFEFEF',
'text-light': '#A4A4B1',
'text-medium': '#D5D5D5',
'text-dark': '#FFFFFF',
'text-error': '#E63946',
'text-error-hover': '#FF5C5C',

View File

@@ -4,6 +4,7 @@ export const GristLight: ThemeColors = {
/* Text */
'text': '#262633',
'text-light': '#929299',
'text-medium': '#494949',
'text-dark': 'black',
'text-error': '#D0021B',
'text-error-hover': '#A10000',