From 815f441b7a76071cf16461baae7cd1cdac96dae8 Mon Sep 17 00:00:00 2001 From: wuxiaojun514 Date: Fri, 29 Nov 2024 15:20:50 +0800 Subject: [PATCH] fixed #1788 and add styles/className property to support styling customization, should somehow fix #1794 --- .../docs/controls/DynamicForm.md | 66 ++++++ .../dynamicForm/DynamicForm.module.scss | 205 ------------------ .../dynamicForm/DynamicForm.sytles.ts | 152 +++++++++++++ src/controls/dynamicForm/DynamicForm.tsx | 41 +++- src/controls/dynamicForm/IDynamicFormProps.ts | 40 +++- .../dynamicField/DynamicField.styles.ts | 151 +++++++++++++ .../dynamicForm/dynamicField/DynamicField.tsx | 31 ++- .../dynamicField/IDynamicFieldProps.ts | 27 +++ 8 files changed, 493 insertions(+), 220 deletions(-) delete mode 100644 src/controls/dynamicForm/DynamicForm.module.scss create mode 100644 src/controls/dynamicForm/DynamicForm.sytles.ts create mode 100644 src/controls/dynamicForm/dynamicField/DynamicField.styles.ts diff --git a/docs/documentation/docs/controls/DynamicForm.md b/docs/documentation/docs/controls/DynamicForm.md index 1521fa379..a01d7a297 100644 --- a/docs/documentation/docs/controls/DynamicForm.md +++ b/docs/documentation/docs/controls/DynamicForm.md @@ -67,6 +67,8 @@ The `DynamicForm` can be configured with the following properties: | customIcons | { [ columnInternalName: string ]: string } | no | Specifies custom icons for the form. The key of this dictionary is the column internal name, the value is the Fluent UI icon name. | | storeLastActiveTab | boolean | no | When uploading files: Specifies if last active tab will be stored after the Upload panel has been closed. Note: the value of selected tab is stored in the queryString hash. Default - `true` | | folderPath | string | no | Server relative or library relative folder to create the item in. This option is only available for document libraries and works only when the contentTypeId is specified and has a base type of type Document or Folder. Defaults to the root folder of the library. | +| className | string | no | Set CSS Class. | +| styles | IStyleFunctionOrObject<IDynamicFormStyleProps, [IDynamicFormStyles](#IDynamicFormStyles)> | no | Styles to apply on control. See the example [here](#how-to-use-styles-property) | ## Validation Error Dialog Properties `IValidationErrorDialogProps` | Property | Type | Required | Description | @@ -74,3 +76,67 @@ The `DynamicForm` can be configured with the following properties: | showDialogOnValidationError | boolean | no | Specifies if the dialog should be shown on validation error. Default - `false` | | customTitle | string | no | Specifies a custom title to be shown in the validation dialog. Default - empty | | customMessage | string | no | Specifies a custom message to be shown in the validation dialog. Default - empty | + +## IDynamicFormStyles interface +| Property | Type | Description | +| ---- | ---- | ---- | +| root | IStyle | styles for the root element | +| sectionTitle | IStyle | styles for the **section title** when your list has enabled [list formatting on form layout](https://learn.microsoft.com/en-us/sharepoint/dev/declarative-customization/list-form-configuration#configure-custom-body-with-one-or-more-sections) | +| sectionFormFields | IStyle | styles for the **section container** when your list has enabled list formatting on form layout | +| sectionFormField | IStyle | styles for the **section field** when your list has enabled list formatting on form layout | +| sectionLine | IStyle | styles for the **section line break** when your list has enabled list formatting on form layout | +| header | IStyle | styles for the **header** when your list has enabled list formatting on [custom header](https://learn.microsoft.com/en-us/sharepoint/dev/declarative-customization/list-form-configuration#configure-custom-header) | +| footer | IStyle | styles for the **footer** when your list has enabled list formatting on [custom footer](https://learn.microsoft.com/en-us/sharepoint/dev/declarative-customization/list-form-configuration#configure-custom-footer) | +| validationErrorDialog | IStyle | styles for the **content** element in Validation Error Dialog | +| actions | IStyle | styles for the **actions** element in Validation Error Dialog | +| actionsRight | IStyle | styles for the **button container** of Validation Error Dialog | +| action | IStyle | styles for the **close button** in Validation Error Dialog | +| buttons | IStyle | styles for the buttons (save button/cancel button) | +| subComponentStyles | {fieldStyles: [IDynamicFieldStyles](#IDynamicFieldStyles)} | styles of dynamic field control| + + +## IDynamicFieldStyles interface +| Property | Type | Description | +| ---- | ---- | ---- | +| FieldEditor | IStyle | styles for root element | +| fieldContainer | IStyle | styles for container element under root | +| titleContainer | IStyle | styles for the title container element of the field | +| fieldIcon | IStyle | styles for the icon element of the field | +| fieldDisplay | IStyle | styles for sub field control.e.g. TextField,ListItemPicker | +| fieldDisplayNoPadding | IStyle | styles for "Url" field control | +| fieldDescription | IStyle | styles for field description element | +| fieldRequired | IStyle | styles for required element | +| fieldLabel | IStyle | styles for field label element , it will append fieldRequired style if the field is required | +| labelContainer | IStyle | styles for field label container element | +| pickersContainer | IStyle | styles for those picker sub control,e.g. DatePicker,TaxonomyPicker | +| errormessage | IStyle | styles for errormessage element | +| richText | IStyle | styles for richText sub control | +| thumbnailFieldButtons | IStyle | styles for button when field type is 'Thumbnail' | +| selectedFileContainer | IStyle | styles for File Selection Control | + + +## How to use styles property +Property styles of Dynamic Form gives you a set of properties which you can use to modify styles. +In this example it shows 4 columns (by default it shows 3 columns per row) in one row if screen size is bigger than 1280px and make the error message font size a bit large. +```TypeScript + styles={{ + sectionFormField: { + selectors: { + ':has(div)': { + [`@media (min-width: 1280px)`]: { + "min-width": '21%', + "max-width": '21%' //force show 4 columns per row in big screen size + } + }, + }, + }, + subComponentStyles:{ + fieldStyles:{ + errormessage:{ + "font-size":"18px" //overwrite the error message font size in Dynamic Field + } + } + } + }} +``` + diff --git a/src/controls/dynamicForm/DynamicForm.module.scss b/src/controls/dynamicForm/DynamicForm.module.scss deleted file mode 100644 index f5f9a8020..000000000 --- a/src/controls/dynamicForm/DynamicForm.module.scss +++ /dev/null @@ -1,205 +0,0 @@ -@import '~@fluentui/react/dist/sass/References.scss'; - -.FieldEditor { - padding: 4px 3px; -} - -.titleContainer { - display: flex; -} - -.fieldLabel { - font-weight: 600; - font-size: 14px; - padding-top: 5px; - padding-bottom: 5px; - font-family: "Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, - "Helvetica Neue", sans-serif; - -webkit-font-smoothing: antialiased; - font-weight: 600; - color: "[theme:neutralPrimary, default:#323130]"; - box-sizing: border-box; - box-shadow: none; - margin: 0px; - overflow-wrap: break-word; - display: block; -} - -.fieldDescription { - font-weight: 400; - font-size: 12px; - color: #858585; - margin-top: 4px; - font-family: "Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, - "Helvetica Neue", sans-serif; - -webkit-font-smoothing: antialiased; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - overflow-wrap: break-word; - display: block; - user-select: none; -} - -.fieldIcon { - align-self: center; - font-size: 16px; - color: "[theme:neutralSecondary, default: #605e5c]"; - margin-right: 8px; -} - -.fieldDisplayNoPadding { - display: inline-block; - vertical-align: top; - width: 100%; - font-size: 14px; - font-weight: 400; - outline: 0; -} - -.fieldDisplay { - @extend .fieldDisplayNoPadding; - padding: 6px 0 0px 0; - //border: 2px solid; - //border-color: "[theme:white, default:#ffffff]"; - //margin-left: -2px; -} - -.fieldRequired::after { - content: " *"; - color: "[theme:errorText, default:#a4262c]"; - padding-right: 12px; -} - -.buttons { - padding: 15px 4px; -} - -.filePicker { - padding: 7px; - align-items: center; -} - -.richText { - position: relative; -} - -.errormessage { - animation-duration: 0.367s; - animation-timing-function: cubic-bezier(0.1, 0.9, 0.2, 1); - animation-fill-mode: both; - font-family: "Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, - "Helvetica Neue", sans-serif; - -webkit-font-smoothing: antialiased; - font-size: 12px; - font-weight: 400; - color: "[theme:errorText, default:#a4262c]"; - margin: 0px; - padding-top: 5px; - display: flex; - align-items: center; -} - -.fieldContainer { - padding-bottom: 4px; -} - -.labelContainer { - padding-bottom: 7px; -} - -.pickersContainer { - padding: 6px 0 0px; -} - -.thumbnailFieldButtons { - display: flex; -} - -.validationErrorDialog { - :global { - .ms-Dialog-main { - max-width: 540px; - width: 540px; - } - } - - .actions { - position: relative; - width: 100%; - min-height: 24px; - line-height: 24px; - margin-top: 20px; - margin-right: 0px; - margin-bottom: 0px; - margin-left: 0px; - font-size: 0px; - - .actionsRight { - text-align: right; - margin-right: -4px; - font-size: 0px; - - .action { - margin-top: 0px; - margin-right: 4px; - margin-bottom: 0px; - margin-left: 4px; - } - } - } -} - -.selectedFileContainer { - display: flex; - margin: 10px 0px; -} - -h2.sectionTitle { - color: #000000; - font-weight: 600; - font-size: 16px; - margin-top: 6px; - margin-bottom: 12px; - clear: both; -} -.sectionFormFields { - display: flex; - flex-wrap: wrap; -} -.sectionFormField { - @media (max-width: 1920px) { - min-width: 244px; - max-width: 244px; - } - @media (max-width: 1366px) { - min-width: 248px; - max-width: 248px; - margin-right: 44px; - } - @media (max-width: 1024px) { - min-width: 272px; - max-width: 272px; - } - @media (max-width: 640px) { - min-width: 432px; - max-width: 432px; - } - @media (max-width: 480px) { - width: 90%; - } -} -.sectionLine { - width: 100%; - border-top: 1px solid #edebe9; - border-bottom-width: 0; - border-left-width: 0; - border-right-width: 0; - clear: both; -} - -:global { - .sp-field-customFormatter { - min-height: inherit; - display: flex; - align-items: center; - } -} \ No newline at end of file diff --git a/src/controls/dynamicForm/DynamicForm.sytles.ts b/src/controls/dynamicForm/DynamicForm.sytles.ts new file mode 100644 index 000000000..bf60fad61 --- /dev/null +++ b/src/controls/dynamicForm/DynamicForm.sytles.ts @@ -0,0 +1,152 @@ +import { IStyle } from '@fluentui/react'; +import { getFluentUIThemeOrDefault } from '../../common/utilities/ThemeUtility'; +import { getFieldStyles } from './dynamicField/DynamicField.styles'; +import type { + IDynamicFormStyleProps, + IDynamicFormStyles, +} from './IDynamicFormProps'; + +export const getStyles = ( + props: IDynamicFormStyleProps +): IDynamicFormStyles => { + const className = props.className; + const globalClassNames = { + sectionFormField: 'sectionFormField', + sectionFormFields: 'sectionFormFields', + sectionTitle: 'sectionTitle', + sectionLine: 'sectionLine', + header:'header', + footer:'footer', + validationErrorDialog: 'validationErrorDialog', + buttons: 'buttons', + actions: 'actions', + action: 'action', + actionsRight: 'actionsRight', + }; + const theme = getFluentUIThemeOrDefault(); + + const paddingleft_style: IStyle = { + 'padding-left': '20px' + }; + + return { + root: [ + className, + { + selectors: { + '.sp-field-customFormatter': { + 'min-height': 'inherit', + display: 'flex', + 'align-items': 'center', + }, + }, + }, + ], + sectionFormField: [ + globalClassNames.sectionFormField, + { + selectors: { + ':has(div)': { + 'max-width': '50vmin', + 'min-width': '25vmax', + padding: '20px', + [`@media (min-width: 480px)`]: { + width: '90%', + }, + }, + }, + }, + ], + sectionFormFields: [ + globalClassNames.sectionFormFields, + { + display: 'flex', + 'flex-wrap': 'wrap', + }, + ], + + sectionTitle: [ + globalClassNames.sectionTitle, + { + color: '#000000', + 'font-weight': '600', + 'font-size': '16px', + 'margin-top': '6px', + 'margin-bottom': '12px', + clear: 'both', + 'padding-left': '20px' + }, + ], + header: [ + globalClassNames.header, + paddingleft_style + + ], + footer: [ + globalClassNames.footer, + paddingleft_style + ], + sectionLine: [ + globalClassNames.sectionLine, + { + width: '100%', + 'border-top': '1px solid #edebe9', + 'border-bottom-width': '0', + 'border-left-width': '0', + 'border-right-width': '0', + clear: 'both', + }, + ], + validationErrorDialog: [ + globalClassNames.validationErrorDialog, + { + selectors: { + '.ms-Dialog-main': { + 'max-width': '540px', + width: '540px', + }, + }, + }, + ], + buttons: [ + globalClassNames.buttons, + { + padding: '15px 4px', + }, + ], + actions: [ + globalClassNames.actions, + { + position: 'relative', + width: '100%', + 'min-height': '24px', + 'line-height': '24px', + 'margin-top': '20px', + 'margin-right': '0px', + 'margin-bottom': '0px', + 'margin-left': '0px', + 'font-size': '0px', + }, + ], + action: [ + globalClassNames.action, + { + 'margin-top': '0px', + 'margin-right': '4px', + 'margin-bottom': '0px', + 'margin-left': '4px', + }, + ], + actionsRight: [ + globalClassNames.actionsRight, + { + 'text-align': 'right', + 'margin-right': '-4px', + 'font-size': '0px', + }, + ], + subComponentStyles: { + fieldStyles: getFieldStyles({ theme: theme }), + }, + }; +}; diff --git a/src/controls/dynamicForm/DynamicForm.tsx b/src/controls/dynamicForm/DynamicForm.tsx index 06d0c84b2..6ec8dee3c 100644 --- a/src/controls/dynamicForm/DynamicForm.tsx +++ b/src/controls/dynamicForm/DynamicForm.tsx @@ -1,7 +1,6 @@ /* eslint-disable @microsoft/spfx/no-async-await */ import * as React from "react"; import * as strings from "ControlStrings"; -import styles from "./DynamicForm.module.scss"; // Controls import { @@ -22,6 +21,8 @@ import { DateFormat, FieldChangeAdditionalData, IDynamicFieldProps, + IDynamicFieldStyleProps, + IDynamicFieldStyles, } from "./dynamicField/IDynamicFieldProps"; import { FilePicker, IFilePickerResult } from "../filePicker"; @@ -41,18 +42,24 @@ import { ISPField, IUploadImageResult } from "../../common/SPEntities"; import { FormulaEvaluation } from "../../common/utilities/FormulaEvaluation"; import { Context } from "../../common/utilities/FormulaEvaluation.types"; import CustomFormattingHelper from "../../common/utilities/CustomFormatting"; +import { getStyles } from "./DynamicForm.sytles"; +import { getFluentUIThemeOrDefault } from "../../common/utilities/ThemeUtility"; +import { classNamesFunction, IProcessedStyleSet, styled } from "@fluentui/react"; // Dynamic Form Props / State -import { IDynamicFormProps } from "./IDynamicFormProps"; +import { IDynamicFormProps, IDynamicFormStyleProps, IDynamicFormStyles } from "./IDynamicFormProps"; import { IDynamicFormState } from "./IDynamicFormState"; import { Icon } from "@fluentui/react/lib/Icon"; const stackTokens: IStackTokens = { childrenGap: 20 }; +const getstyles = classNamesFunction(); +const getFieldstyles = classNamesFunction(); +const theme = getFluentUIThemeOrDefault(); /** * DynamicForm Class Control */ -export class DynamicForm extends React.Component< +export class DynamicFormBase extends React.Component< IDynamicFormProps, IDynamicFormState > { @@ -63,6 +70,7 @@ export class DynamicForm extends React.Component< private webURL = this.props.webAbsoluteUrl ? this.props.webAbsoluteUrl : this.props.context.pageContext.web.absoluteUrl; + private _classNames: IProcessedStyleSet; constructor(props: IDynamicFormProps) { super(props); @@ -152,10 +160,15 @@ export class DynamicForm extends React.Component< const customFormattingDisabled = this.props.useCustomFormatting === false; + const { className } = this.props; + const styles = (this._classNames = getstyles(this.props.styles, { className: className })); + // Custom Formatting - Header let headerContent: JSX.Element; if (!customFormattingDisabled && customFormatting?.header) { - headerContent = this._customFormatter.renderCustomFormatContent(customFormatting.header, this.getFormValuesForValidation(), true) as JSX.Element; + headerContent =
+ {this._customFormatter.renderCustomFormatContent(customFormatting.header, this.getFormValuesForValidation(), true)} +
} // Custom Formatting - Body @@ -175,7 +188,9 @@ export class DynamicForm extends React.Component< // Custom Formatting - Footer let footerContent: JSX.Element; if (!customFormattingDisabled && customFormatting?.footer) { - footerContent = this._customFormatter.renderCustomFormatContent(customFormatting.footer, this.getFormValuesForValidation(), true) as JSX.Element; + footerContent =
+ {this._customFormatter.renderCustomFormatContent(customFormatting.footer, this.getFormValuesForValidation(), true)} +
} // Content Type @@ -287,8 +302,8 @@ export class DynamicForm extends React.Component< const { fieldOverrides } = this.props; const { hiddenByFormula, isSaving, validationErrors } = this.state; - // If the field is hidden by a formula, don't render it - if (hiddenByFormula.find(h => h === field.columnInternalName)) { + // If the field is hidden by a formula or field doesn't exist (usually occurs in custom formatting section layout when field display name changed), don't render it + if (!field || hiddenByFormula.find(h => h === field.columnInternalName)) { return null; } @@ -977,7 +992,7 @@ export class DynamicForm extends React.Component< let bodySections: ICustomFormattingBodySection[]; if (listInfo.ClientFormCustomFormatter && listInfo.ClientFormCustomFormatter[contentTypeId]) { const customFormatInfo = JSON.parse(listInfo.ClientFormCustomFormatter[contentTypeId]) as ICustomFormatting; - bodySections = customFormatInfo.bodyJSONFormatter.sections; + bodySections = customFormatInfo.bodyJSONFormatter?.sections; headerJSON = customFormatInfo.headerJSONFormatter; footerJSON = customFormatInfo.footerJSONFormatter; } @@ -1413,6 +1428,7 @@ export class DynamicForm extends React.Component< missingSelectedFile } = this.state; + const styles = getFieldstyles(this._classNames.subComponentStyles.fieldStyles(), { theme: theme }); const labelEl = ; return
@@ -1522,3 +1538,12 @@ export class DynamicForm extends React.Component< return folder; }; } + +export const DynamicForm = styled( + DynamicFormBase, + getStyles, + undefined, + { + scope: 'DynamicForm', + }, +); \ No newline at end of file diff --git a/src/controls/dynamicForm/IDynamicFormProps.ts b/src/controls/dynamicForm/IDynamicFormProps.ts index cdf048794..b2fac38c9 100644 --- a/src/controls/dynamicForm/IDynamicFormProps.ts +++ b/src/controls/dynamicForm/IDynamicFormProps.ts @@ -1,7 +1,8 @@ import { BaseComponentContext } from '@microsoft/sp-component-base'; import { IItem } from '@pnp/sp/items'; +import { IStyle, IStyleFunctionOrObject } from '@fluentui/react'; import React from 'react'; -import { IDynamicFieldProps } from './dynamicField'; +import { IDynamicFieldProps,IDynamicFieldStyles } from './dynamicField'; import { IValidationErrorDialogProps } from './IValidationErrorDialogProps'; export interface IDynamicFormProps { @@ -146,4 +147,41 @@ export interface IDynamicFormProps { * Defaults to the root folder. */ folderPath?: string; + /** + * Call to provide customized styling + * Read https://github.com/microsoft/fluentui/blob/master/docs/react-wiki-archive/Component-Styling.md#using-a-styleable-component for more information + */ + styles?: IStyleFunctionOrObject; + + /** + * CSS Class name to add to the root element. + */ + className?: string; } + + + +export type IDynamicFormStyleProps = Pick & { }; + +export interface IDynamicFormSubComponentStyles { + fieldStyles: IDynamicFieldStyles; +} + +export interface IDynamicFormStyles { + root: IStyle; + sectionTitle: IStyle; + sectionFormFields: IStyle; + sectionFormField: IStyle; + sectionLine: IStyle; + header:IStyle; + footer:IStyle; + validationErrorDialog: IStyle; + buttons: IStyle; + actions: IStyle; + actionsRight: IStyle; + action: IStyle; + /** + * sub component styles for dynamic field + */ + subComponentStyles: IDynamicFormSubComponentStyles; +} \ No newline at end of file diff --git a/src/controls/dynamicForm/dynamicField/DynamicField.styles.ts b/src/controls/dynamicForm/dynamicField/DynamicField.styles.ts new file mode 100644 index 000000000..87deb2eac --- /dev/null +++ b/src/controls/dynamicForm/dynamicField/DynamicField.styles.ts @@ -0,0 +1,151 @@ +import { IStyle } from '@fluentui/react'; +import { + IDynamicFieldStyleProps, + IDynamicFieldStyles, +} from './IDynamicFieldProps'; + +export const getFieldStyles = ( + props: IDynamicFieldStyleProps +): IDynamicFieldStyles => { + const { required, theme } = props; + const { palette } = theme; + const globalClassNames = { + titleContianer: 'titleContainer', + fieldEditor: 'fieldEditor', + fieldIcon: 'fieldIcon', + fieldContainer: 'fieldContainer', + fieldDisplay: 'fieldDisplay', + fieldDisplayNoPadding: 'fieldDisplayNoPadding', + fieldDescription: 'fieldDescription', + fieldLabel: 'fieldLabel', + labelContainer: 'labelContainer', + pickersContainer: 'pickersContainer', + errormessage: 'errormessage', + richText: 'richText', + thumbnailFieldButtons: 'thumbnailFieldButtons', + selectedFileContainer: 'selectedFileContainer', + fieldRequired: 'fieldRequired', + }; + + const fieldDisplayNoPadding_style: IStyle = { + display: 'inline-block', + 'vertical-align': 'top', + width: '100%', + 'font-size': '14px', + 'font-weight': '400', + outline: '0', + }; + + const fieldDisplay_style: IStyle = fieldDisplayNoPadding_style && { + padding: '6px 0 0px 0', + }; + + const fieldRequired_style: IStyle = { + selectors: { + '::after': { + content: `' *'`, + color: theme.semanticColors.errorText, + 'padding-right': '12px', + }, + }, + }; + + const fontfamily = + "'Segoe UI', 'Segoe UI Web (West European)', -apple-system, BlinkMacSystemFont, Roboto,'Helvetica Neue', 'sans-serif'"; + + return { + titleContainer: [ + globalClassNames.titleContianer, + { + display: 'flex', + }, + ], + fieldIcon: [ + globalClassNames.fieldIcon, + { + 'align-self': 'center', + 'font-size': '16px', + color: palette.neutralSecondary, + 'margin-right': '8px', + }, + ], + fieldDisplay: [globalClassNames.fieldDisplay, fieldDisplay_style], + fieldContainer: [ + globalClassNames.fieldContainer, + { 'padding-bottom': '4px' }, + ], + fieldDisplayNoPadding: [ + globalClassNames.fieldDisplayNoPadding, + fieldDisplay_style, + ], + FieldEditor: [globalClassNames.fieldEditor, { padding: '4px 3px' }], + fieldDescription: [ + globalClassNames.fieldDescription, + { + 'font-weight': '400', + 'font-size': '12px', + color: '#858585', + 'margin-top': '4px', + 'font-family': fontfamily, + '-webkit-font-smoothing': 'antialiased', + '-webkit-tap-highlight-color': 'rgba(0, 0, 0, 0)', + 'overflow-wrap': 'break-word', + display: 'block', + 'user-select': 'none', + '-webkit-user-select': 'none', + }, + ], + fieldLabel: [ + globalClassNames.fieldLabel, + { + 'font-weight': '600', + 'font-size': '14px', + 'padding-top': '5px', + 'padding-bottom': '5px', + 'font-family': fontfamily, + '-webkit-font-smoothing': 'antialiased', + color: palette.neutralPrimary, + 'box-sizing': 'border-box', + 'box-shadow': 'none', + margin: '0px', + 'overflow-wrap': 'break-word', + display: 'block', + }, + required && [globalClassNames.fieldRequired, fieldRequired_style], + ], + fieldRequired: [globalClassNames.fieldRequired, fieldRequired_style], + labelContainer: [ + globalClassNames.labelContainer, + { 'padding-bottom': '7px' }, + ], + pickersContainer: [ + globalClassNames.pickersContainer, + { padding: '6px 0 0px' }, + ], + selectedFileContainer: [ + globalClassNames.selectedFileContainer, + { display: 'flex', margin: '10px 0px' }, + ], + richText: [globalClassNames.richText, { position: 'relative' }], + thumbnailFieldButtons: [ + globalClassNames.thumbnailFieldButtons, + { display: 'flex' }, + ], + errormessage: [ + globalClassNames.errormessage, + { + 'animation-duration': '0.367s', + 'animation-timing-function': 'cubic-bezier(0.1, 0.9, 0.2, 1)', + 'animation-fill-mode': 'both', + 'font-family': fontfamily, + 'font-size': '12px', + 'font-weight': '400', + color: theme.semanticColors.errorText, + margin: '0px', + 'padding-top': '5px', + display: 'flex', + 'align-items': 'center', + }, + ], + }; +}; diff --git a/src/controls/dynamicForm/dynamicField/DynamicField.tsx b/src/controls/dynamicForm/dynamicField/DynamicField.tsx index b7d859215..317e2db3b 100644 --- a/src/controls/dynamicForm/dynamicField/DynamicField.tsx +++ b/src/controls/dynamicForm/dynamicField/DynamicField.tsx @@ -19,12 +19,16 @@ import { LocationPicker } from '../../locationPicker'; import { IPeoplePickerContext, PeoplePicker, PrincipalType } from '../../peoplepicker'; import { RichText } from '../../richText'; import { IPickerTerms, TaxonomyPicker } from '../../taxonomyPicker'; -import styles from '../DynamicForm.module.scss'; -import { IDynamicFieldProps } from './IDynamicFieldProps'; +import { IDynamicFieldProps, IDynamicFieldStyleProps, IDynamicFieldStyles } from './IDynamicFieldProps'; import { IDynamicFieldState } from './IDynamicFieldState'; import CurrencyMap from "../CurrencyMap"; +import { classNamesFunction, IProcessedStyleSet, styled } from '@fluentui/react'; +import { getFluentUIThemeOrDefault } from '../../../common/utilities/ThemeUtility'; +import { getFieldStyles } from './DynamicField.styles'; -export class DynamicField extends React.Component { +const getClassNames = classNamesFunction(); + +export class DynamicFieldBase extends React.Component { constructor(props: IDynamicFieldProps) { super(props); @@ -36,6 +40,8 @@ export class DynamicField extends React.Component; + public componentDidUpdate(): void { if ((this.props.defaultValue === "" || this.props.defaultValue === null) && this.state.changedValue === null) { this.setState({ changedValue: "" }); @@ -44,6 +50,11 @@ export class DynamicField extends React.Component {this.getFieldComponent()} @@ -70,7 +81,6 @@ export class DynamicField extends React.Component{label}; + const styles=this._classNames; + const labelEl = ; const errorText = this.props.validationErrorMessage || this.getRequiredErrorText(); const errorTextEl = {errorText}; const descriptionEl = {description}; @@ -786,3 +796,12 @@ export class DynamicField extends React.Component( + DynamicFieldBase, + getFieldStyles, + undefined, + { + scope: 'DynamicField', + }, +); diff --git a/src/controls/dynamicForm/dynamicField/IDynamicFieldProps.ts b/src/controls/dynamicForm/dynamicField/IDynamicFieldProps.ts index c73082780..864377185 100644 --- a/src/controls/dynamicForm/dynamicField/IDynamicFieldProps.ts +++ b/src/controls/dynamicForm/dynamicField/IDynamicFieldProps.ts @@ -1,5 +1,6 @@ import { BaseComponentContext } from '@microsoft/sp-component-base'; import { IDropdownOption } from "@fluentui/react/lib/Dropdown"; +import { IStyle, IStyleFunctionOrObject, Theme } from '@fluentui/react'; import { IFilePickerResult } from '../../filePicker'; export type DateFormat = 'DateTime' | 'DateOnly'; @@ -92,4 +93,30 @@ export interface IDynamicFieldProps { itemsQueryCountLimit?: number; customIcon?: string; orderBy?: string; + /** Used for customize component styling */ + styles?:IStyleFunctionOrObject; +} + + +export interface IDynamicFieldStyleProps { + theme: Theme; + required?: boolean; +} + +export interface IDynamicFieldStyles { + titleContainer: IStyle; + fieldIcon:IStyle; + fieldDisplay:IStyle; + fieldDisplayNoPadding:IStyle; + fieldContainer:IStyle; + fieldDescription:IStyle, + fieldLabel:IStyle; + labelContainer:IStyle; + pickersContainer:IStyle; + FieldEditor:IStyle; + errormessage:IStyle; + richText:IStyle; + thumbnailFieldButtons:IStyle; + selectedFileContainer:IStyle; + fieldRequired:IStyle; }