diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/UI/Settings.tsx b/packages/module/patternfly-docs/content/extensions/chatbot/examples/UI/Settings.tsx new file mode 100644 index 00000000..e73538a1 --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/UI/Settings.tsx @@ -0,0 +1,274 @@ +import React from 'react'; + +import Settings from '@patternfly/chatbot/dist/dynamic/Settings'; +import { + Button, + Dropdown, + DropdownItem, + DropdownList, + FormGroup, + MenuToggle, + MenuToggleElement, + Radio, + Switch +} from '@patternfly/react-core'; +import { ChatbotDisplayMode } from '@patternfly/chatbot/dist/dynamic/Chatbot'; + +export const SettingsDemo: React.FunctionComponent = () => { + const [isChecked, setIsChecked] = React.useState(true); + const [isThemeOpen, setIsThemeOpen] = React.useState(false); + const [isLanguageOpen, setIsLanguageOpen] = React.useState(false); + const [isVoiceOpen, setIsVoiceOpen] = React.useState(false); + const [displayMode, setDisplayMode] = React.useState(ChatbotDisplayMode.default); + const [isModalOpen, setIsModalOpen] = React.useState(true); + + const onFocus = (id: string) => { + const element = document.getElementById(id); + (element as HTMLElement).focus(); + }; + + const onEscapePress = (event: KeyboardEvent) => { + console.log(event); + const target = event.target as Element; + if (target?.id === 'voice') { + setIsVoiceOpen(!isVoiceOpen); + onFocus('voice'); + return; + } + if (target?.id === 'language') { + setIsLanguageOpen(!isLanguageOpen); + onFocus('language'); + return; + } + if (target?.id === 'theme') { + setIsThemeOpen(!isThemeOpen); + onFocus('theme'); + return; + } + setIsModalOpen(!isModalOpen); + }; + + const onThemeToggleClick = () => { + setIsThemeOpen(!isThemeOpen); + }; + + const onThemeSelect = ( + _event: React.MouseEvent | undefined, + value: string | number | undefined + ) => { + // eslint-disable-next-line no-console + console.log('selected', value); + onFocus('theme'); + setIsThemeOpen(false); + }; + + const onLanguageToggleClick = () => { + setIsLanguageOpen(!isLanguageOpen); + }; + + const onLanguageSelect = ( + _event: React.MouseEvent | undefined, + value: string | number | undefined + ) => { + // eslint-disable-next-line no-console + console.log('selected', value); + onFocus('language'); + setIsLanguageOpen(false); + }; + + const onVoiceToggleClick = () => { + onFocus('voice'); + setIsVoiceOpen(!isVoiceOpen); + }; + + const onVoiceSelect = ( + _event: React.MouseEvent | undefined, + value: string | number | undefined + ) => { + // eslint-disable-next-line no-console + console.log('selected', value); + setIsVoiceOpen(false); + }; + + const handleChange = (_event: React.FormEvent, checked: boolean) => { + setIsChecked(checked); + }; + + const themeDropdown = ( + setIsThemeOpen(isOpen)} + shouldFocusToggleOnSelect + shouldFocusFirstItemOnOpen + shouldPreventScrollOnItemFocus + toggle={(toggleRef: React.Ref) => ( + // We want to add the id property here as well so the label is coupled + // with the button on screen readers. + + System + + )} + ouiaId="ThemeDropdown" + > + + + System + + + + ); + + const languageDropdown = ( + setIsLanguageOpen(isOpen)} + shouldFocusToggleOnSelect + shouldFocusFirstItemOnOpen + shouldPreventScrollOnItemFocus + toggle={(toggleRef: React.Ref) => ( + // We want to add the id property here as well so the label is coupled + // with the button on screen readers. + + Auto-detect + + )} + ouiaId="LanguageDropdown" + > + + + Auto-detect + + + + ); + const voiceDropdown = ( + setIsVoiceOpen(isOpen)} + shouldFocusToggleOnSelect + shouldFocusFirstItemOnOpen + shouldPreventScrollOnItemFocus + toggle={(toggleRef: React.Ref) => ( + // We want to add the id property here as well so the label is coupled + // with the button on screen readers. + + Bot + + )} + ouiaId="VoiceDropdown" + > + + + Bot + + + + ); + const children = [ + { id: 'theme', label: 'Theme', field: themeDropdown }, + { id: 'language', label: 'Language', field: languageDropdown }, + { id: 'voice', label: 'Voice', field: voiceDropdown }, + { + id: 'analytics', + label: 'Share analytics', + field: ( + + ) + }, + { + id: 'archived-chat', + label: 'Archive chat', + field: ( + // We want to add the id property here as well so the label is coupled + // with the button on screen readers. + + ) + }, + { + id: 'archive-all', + label: 'Archive all chat', + field: ( + // We want to add the id property here as well so the label is coupled + // with the button on screen readers. + + ) + }, + { + id: 'delete-all', + label: 'Delete all chats', + field: ( + // We want to add the id property here as well so the label is coupled + // with the button on screen readers. + + ) + } + ]; + + return ( + <> +
+ + setDisplayMode(ChatbotDisplayMode.default)} + name="basic-inline-radio" + label="Default" + id="default" + /> + setDisplayMode(ChatbotDisplayMode.docked)} + name="basic-inline-radio" + label="Docked" + id="docked" + /> + setDisplayMode(ChatbotDisplayMode.fullscreen)} + name="basic-inline-radio" + label="Fullscreen" + id="fullscreen" + /> + setDisplayMode(ChatbotDisplayMode.embedded)} + name="basic-inline-radio" + label="Embedded" + id="embedded" + /> + + +
+ setIsModalOpen(!isModalOpen)} + fields={children} + > + + ); +}; diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md b/packages/module/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md index ccd1649a..05acf98d 100644 --- a/packages/module/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md @@ -65,6 +65,7 @@ import { ChatbotFooter, ChatbotFootnote } from '@patternfly/chatbot/dist/dynamic import { MessageBar } from '@patternfly/chatbot/dist/dynamic/MessageBar'; import SourceDetailsMenuItem from '@patternfly/chatbot/dist/dynamic/SourceDetailsMenuItem'; import { ChatbotModal } from '@patternfly/chatbot/dist/dynamic/ChatbotModal'; +import Settings from '@patternfly/chatbot/dist/dynamic/Settings'; import { BellIcon, CalendarAltIcon, ClipboardIcon, CodeIcon, UploadIcon } from '@patternfly/react-icons'; import { useDropzone } from 'react-dropzone'; @@ -342,3 +343,9 @@ Based on the [PatternFly modal](/components/modal), this modal adapts to the Cha ```js file="./ChatbotModal.tsx" isFullscreen ``` + +### Settings + +```js file="./Settings.tsx" isFullscreen + +``` diff --git a/packages/module/src/Settings/Settings.scss b/packages/module/src/Settings/Settings.scss new file mode 100644 index 00000000..719307b2 --- /dev/null +++ b/packages/module/src/Settings/Settings.scss @@ -0,0 +1,25 @@ +.pf-chatbot__settings-form { + display: flex; + flex-direction: column; +} + +.pf-chatbot__settings-form-row { + font-size: var(--pf-t--global--font--size--body--lg); + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid var(--pf-t--global--border--color--default); + padding: var(--pf-t--global--spacer--lg); + font-weight: 500; +} + +.pf-chatbot__settings-form-row:last-of-type { + border-bottom: 0px; +} + +.pf-chatbot__settings--title { + font-family: var(--pf-t--global--font--family--heading); + font-size: var(--pf-t--global--font--size--xl); + font-weight: 500; + text-align: center; +} diff --git a/packages/module/src/Settings/Settings.tsx b/packages/module/src/Settings/Settings.tsx new file mode 100644 index 00000000..69da0236 --- /dev/null +++ b/packages/module/src/Settings/Settings.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import ChatbotModal from '../ChatbotModal'; +import { ModalBody, ModalHeader, ModalProps } from '@patternfly/react-core'; +import { ChatbotDisplayMode } from '../Chatbot'; + +export interface SettingsProps extends Omit { + /** Class applied to modal */ + className?: string; + /** Function that handles modal toggle */ + handleModalToggle: (event: React.MouseEvent | MouseEvent | KeyboardEvent) => void; + /** Whether modal is open */ + isModalOpen: boolean; + /** Title of modal */ + title?: string; + /** Display mode for the Chatbot parent; this influences the styles applied */ + displayMode?: ChatbotDisplayMode; + /** Array of fields to display in the settings layout */ + fields?: { id: string; label: string; field: React.ReactElement }[]; +} + +export const Settings: React.FunctionComponent = ({ + handleModalToggle, + isModalOpen, + title = 'Settings', + displayMode = ChatbotDisplayMode.default, + className, + fields = [], + ...props +}) => ( + + +
+

{title}

+
+
+ +
+ {fields.map((field) => ( +
+ + {field.field} +
+ ))} +
+
+
+); + +export default Settings; diff --git a/packages/module/src/Settings/index.ts b/packages/module/src/Settings/index.ts new file mode 100644 index 00000000..1af7202c --- /dev/null +++ b/packages/module/src/Settings/index.ts @@ -0,0 +1,3 @@ +export { default } from './Settings'; + +export * from './Settings'; diff --git a/packages/module/src/index.ts b/packages/module/src/index.ts index 7b599e2c..edb69c1e 100644 --- a/packages/module/src/index.ts +++ b/packages/module/src/index.ts @@ -66,6 +66,9 @@ export * from './PreviewAttachment'; export { default as ResponseActions } from './ResponseActions'; export * from './ResponseActions'; +export { default as Settings } from './Settings'; +export * from './Settings'; + export { default as SourceDetailsMenuItem } from './SourceDetailsMenuItem'; export * from './SourceDetailsMenuItem'; diff --git a/packages/module/src/main.scss b/packages/module/src/main.scss index c11add72..af5b7380 100644 --- a/packages/module/src/main.scss +++ b/packages/module/src/main.scss @@ -22,6 +22,7 @@ @import './MessageBox/MessageBox'; @import './MessageBox/JumpButton'; @import './ResponseActions/ResponseActions'; +@import './Settings/Settings'; @import './SourcesCard/SourcesCard.scss'; @import './SourceDetailsMenuItem/SourceDetailsMenuItem';