diff --git a/packages/ui-editable/package.json b/packages/ui-editable/package.json
index 2ad8b7132e..985792d6bd 100644
--- a/packages/ui-editable/package.json
+++ b/packages/ui-editable/package.json
@@ -73,18 +73,18 @@
"default": "./es/exports/a.js"
},
"./v11_7": {
- "src": "./src/exports/a.ts",
- "types": "./types/exports/a.d.ts",
- "import": "./es/exports/a.js",
- "require": "./lib/exports/a.js",
- "default": "./es/exports/a.js"
+ "src": "./src/exports/b.ts",
+ "types": "./types/exports/b.d.ts",
+ "import": "./es/exports/b.js",
+ "require": "./lib/exports/b.js",
+ "default": "./es/exports/b.js"
},
"./latest": {
- "src": "./src/exports/a.ts",
- "types": "./types/exports/a.d.ts",
- "import": "./es/exports/a.js",
- "require": "./lib/exports/a.js",
- "default": "./es/exports/a.js"
+ "src": "./src/exports/b.ts",
+ "types": "./types/exports/b.d.ts",
+ "import": "./es/exports/b.js",
+ "require": "./lib/exports/b.js",
+ "default": "./es/exports/b.js"
}
}
}
diff --git a/packages/ui-editable/src/Editable/v1/README.md b/packages/ui-editable/src/Editable/v1/README.md
index e0d39c2e8e..0705bf91c0 100644
--- a/packages/ui-editable/src/Editable/v1/README.md
+++ b/packages/ui-editable/src/Editable/v1/README.md
@@ -56,7 +56,7 @@ const Example = (props) => {
}
const renderViewer = () => {
- return {value}
+ return {value}
}
const handleValueChange = (event) => {
@@ -69,8 +69,10 @@ const Example = (props) => {
const renderEditor = ({ onBlur, editorRef }) => {
return (
- {
+ const [mode, setMode] = useState(props.mode || 'view')
+ const [value, setValue] = useState('This is some text')
+ const [inline, setInline] = useState(true)
+
+ // You must provide this to Editable to be
+ // notified of mode changes
+ const handleChangeMode = (mode) => {
+ setMode(mode)
+ }
+
+ // You attach an event handler to your edit component
+ // to be notified of value changes from user interactions
+ const handleChange = (event) => {
+ setValue(event.target.value)
+ }
+
+ // Renders the view component
+ // Be sure to give it the current value
+ const renderView = () => (
+
+ {value || 'Enter some text'}
+
+ )
+
+ // Renders the edit component.
+ // You have to forward the props on, which
+ // includes an onBlur property to help manage
+ // the mode changes.
+ // Be sure to give it the current value
+ const renderEdit = ({ onBlur, editorRef }) => (
+
+ )
+
+ // Renders the edit button.
+ // Leverage the default implementation provided by InPlaceEdit
+ const renderEditButton = (props) => {
+ props.label = `Edit title "${value}"`
+ return InPlaceEdit.renderDefaultEditButton(props)
+ }
+
+ const onChangeLayout = (event) => {
+ setInline(event.target.checked)
+ }
+
+ return (
+
+
+
+
+
+
+ )
+ }
+
+ render()
+```
+
+A readOnly `InPlaceEdit`
+
+```js
+---
+type: example
+---
+ const Example = (props) => {
+ const [mode, setMode] = useState(props.mode || 'view')
+ const [value, setValue] = useState('This is some text')
+
+ // You must provide this to Editable to be
+ // notified of mode changes
+ const handleChangeMode = (mode) => {
+ setMode(mode)
+ }
+
+ // You attach an event handler to your edit component
+ // to be notified of value changes from user interactions
+ const handleChange = (event) => {
+ setValue(event.target.value)
+ }
+
+ // Renders the view component
+ // Be sure to give it the current value
+ const renderView = () => {value}
+
+ // Renders the edit component.
+ // You have to forward the props on, which
+ // includes an onBlur property to help manage
+ // the mode changes.
+ // Be sure to give it the current value
+ const renderEdit = ({ onBlur, editorRef }) => (
+
+ )
+
+ // Renders the edit button.
+ // Leverage the default implementation provided by InPlaceEdit
+ const renderEditButton = (props) => {
+ props.label = `Edit title "${value}"`
+ return InPlaceEdit.renderDefaultEditButton(props)
+ }
+
+ return (
+
+ )
+ }
+
+ render()
+```
+
+To edit end-justified text, wrap `` in a
+`` component, as follows:
+
+```js
+---
+type: example
+---
+ const Example = (props) => {
+ const [mode, setMode] = useState(props.mode || 'view')
+ const [value, setValue] = useState('This is some text')
+
+ // You must provide this to Editable to be
+ // notified of mode changes
+ const handleChangeMode = (mode) => {
+ setMode(mode)
+ }
+
+ // You attach an event handler to your edit component
+ // to be notified of value changes from user interactions
+ const handleChange = (event) => {
+ setValue(event.target.value)
+ }
+
+ // Renders the view component
+ // Be sure to give it the current value
+ const renderView = () => {value}
+
+ // Renders the edit component.
+ // You have to forward the props on, which
+ // includes an onBlur property to help manage
+ // the mode changes.
+ // Be sure to give it the current value
+ const renderEdit = ({ onBlur, editorRef }) => (
+
+ )
+
+ // Renders the edit button.
+ // Leverage the default implementation provided by InPlaceEdit
+ const renderEditButton = (props) => {
+ props.label = `Edit title "${value}"`
+ return InPlaceEdit.renderDefaultEditButton(props)
+ }
+
+ return (
+
+
+
+ )
+ }
+
+ render()
+```
+
+Same as the first example, but notifies `Editable`'s `onChange`
+when the user has finished editing and the value has changed.
+
+```js
+---
+type: example
+---
+ const Example = (props) => {
+ const [mode, setMode] = useState(props.mode || 'view')
+ const [value, setValue] = useState('Edit me')
+ const [onChangeValue, setOnChangeValue] = useState(undefined)
+
+ // typically provided by the application so it can
+ // be notified of value changes when the user is
+ // finished editing
+ const onChange = (newValue) => {
+ setOnChangeValue(newValue)
+ }
+
+ // You must provide this to Editable to be
+ // notified of mode changes
+ const handleChangeMode = (mode) => {
+ setMode(mode)
+ }
+
+ // You attach an event handler to your edit component
+ // to be notified of value changes from user interactions
+ const handleChange = (event) => {
+ setValue(event.target.value)
+ }
+
+ // Renders the view component
+ // Be sure to give it the current value
+ const renderView = () => {value}
+
+ // Renders the edit component.
+ // You have to forward the props on, which
+ // includes an onBlur property to help manage
+ // the mode changes.
+ // Be sure to give it the current value
+ const renderEdit = ({ onBlur, editorRef }) => (
+
+ )
+
+ // Renders the edit button.
+ // Leverage the default implementation provided by InPlaceEdit
+ const renderEditButton = (props) => {
+ props.label = `Edit title "${value}"`
+ return InPlaceEdit.renderDefaultEditButton(props)
+ }
+
+ return (
+
+
+
+
+ )
+ }
+
+ render()
+```
diff --git a/packages/ui-editable/src/InPlaceEdit/v1/__tests__/InPlaceEdit.test.tsx b/packages/ui-editable/src/InPlaceEdit/v2/__tests__/InPlaceEdit.test.tsx
similarity index 100%
rename from packages/ui-editable/src/InPlaceEdit/v1/__tests__/InPlaceEdit.test.tsx
rename to packages/ui-editable/src/InPlaceEdit/v2/__tests__/InPlaceEdit.test.tsx
diff --git a/packages/ui-editable/src/InPlaceEdit/v2/index.tsx b/packages/ui-editable/src/InPlaceEdit/v2/index.tsx
new file mode 100644
index 0000000000..e90374e7d2
--- /dev/null
+++ b/packages/ui-editable/src/InPlaceEdit/v2/index.tsx
@@ -0,0 +1,205 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 - present Instructure, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { Component } from 'react'
+
+import { Flex } from '@instructure/ui-flex/latest'
+import { IconButton } from '@instructure/ui-buttons/latest'
+import type { IconButtonProps } from '@instructure/ui-buttons/latest'
+import { PencilInstUIIcon } from '@instructure/ui-icons'
+import { logWarn as warn } from '@instructure/console'
+import { createChainedFunction } from '@instructure/ui-utils'
+import { withStyle } from '@instructure/emotion'
+import { View } from '@instructure/ui-view/latest'
+
+import { Editable } from '../../Editable/v1'
+import generateStyle from './styles'
+
+import { allowedProps } from './props'
+import type { InPlaceEditProps } from './props'
+import type { EditableRenderProps } from '../../Editable/v1/props'
+
+/**
+---
+category: components
+---
+**/
+@withStyle(generateStyle)
+class InPlaceEdit extends Component {
+ static readonly componentId = 'InPlaceEdit'
+ static allowedProps = allowedProps
+ static defaultProps = {
+ readOnly: false,
+ showFocusRing: true,
+ inline: true,
+ editButtonPlacement: 'end'
+ }
+
+ ref: Element | null = null
+ _editButtonRef: HTMLButtonElement | null = null
+
+ handleRef = (el: Element | null) => {
+ this.ref = el
+ }
+
+ constructor(props: InPlaceEditProps) {
+ super(props)
+
+ warn(
+ props.readOnly ? props.mode === 'view' : true,
+ '[InPlaceEdit] When readOnly is true, mode is forced to "view"'
+ )
+ }
+
+ componentDidMount() {
+ this.props.makeStyles?.()
+ }
+
+ componentDidUpdate() {
+ this.props.makeStyles?.()
+ }
+
+ handleEditButtonRef = (el: HTMLButtonElement) => {
+ this._editButtonRef = el
+ }
+
+ renderEditor({
+ mode,
+ onBlur,
+ editorRef,
+ readOnly
+ }: ReturnType) {
+ const { showFocusRing, renderEditor } = this.props
+ const isEditMode = !readOnly && mode === 'edit'
+
+ return isEditMode ? (
+
+ {renderEditor({ onBlur, editorRef })}
+
+ ) : null
+ }
+
+ renderViewer({
+ readOnly,
+ mode
+ }: ReturnType) {
+ return readOnly || mode === 'view' ? this.props.renderViewer() : null
+ }
+
+ renderEditButton({
+ buttonRef,
+ ...rest
+ }: ReturnType) {
+ return this.props.renderEditButton({
+ elementRef: createChainedFunction(this.handleEditButtonRef, buttonRef),
+ ...rest
+ })
+ }
+
+ // Render a default edit button, an icon button with the edit icon
+ // the margin makes room for the focus ring
+ static renderDefaultEditButton = ({
+ isVisible,
+ readOnly,
+ label,
+ ...buttonProps
+ }: {
+ isVisible: boolean
+ readOnly?: boolean
+ label: string
+ } & Partial) => {
+ if (readOnly) {
+ return null
+ }
+ return (
+
+ {isVisible ? PencilInstUIIcon : null}
+
+ )
+ }
+
+ renderAll = ({
+ getContainerProps,
+ getViewerProps,
+ getEditorProps,
+ getEditButtonProps
+ }: EditableRenderProps) => {
+ const flexDir =
+ this.props.editButtonPlacement === 'start' ? 'row-reverse' : 'row'
+ const justifyItems = flexDir === 'row-reverse' ? 'end' : 'start'
+ const buttonMargin =
+ this.props.editButtonPlacement === 'start'
+ ? '0 xx-small 0 0'
+ : '0 0 0 xx-small'
+ return (
+
+
+ {this.renderEditor(getEditorProps())}
+ {this.renderViewer(getViewerProps())}
+
+
+ {this.renderEditButton(getEditButtonProps())}
+
+
+ )
+ }
+
+ render() {
+ const { mode, value, onChange, onChangeMode, readOnly } = this.props
+
+ return (
+
+ )
+ }
+}
+
+export default InPlaceEdit
+export { InPlaceEdit }
diff --git a/packages/ui-editable/src/InPlaceEdit/v2/props.ts b/packages/ui-editable/src/InPlaceEdit/v2/props.ts
new file mode 100644
index 0000000000..44c8ad8ee3
--- /dev/null
+++ b/packages/ui-editable/src/InPlaceEdit/v2/props.ts
@@ -0,0 +1,146 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - present Instructure, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import type { WithStyleProps, ComponentStyle } from '@instructure/emotion'
+import type { EditableProps } from '../../Editable/v1/props'
+import React from 'react'
+
+type ExtendedRenderEditButton = {
+ elementRef?: (el: HTMLButtonElement) => void
+ onClick: () => void
+ onFocus: () => void
+ onBlur: () => void
+ isVisible: boolean
+ readOnly?: boolean
+}
+
+type InPlaceEditOwnProps = {
+ /**
+ * Function to render the view mode component.
+ * It is the consumer's responsibility to provide the
+ * current value or children.
+ *
+ * Return value:
+ * - element: the viewer DOM sub-tree.
+ */
+ renderViewer: () => React.ReactNode
+ /**
+ * Function to render the edit mode component
+ * It is the consumer's responsibility to provide the
+ * current value, and to attach the appropriate onChange
+ * event handler needed to capture the updated value. This
+ * new value must then be forwarded to the view mode component.
+ *
+ * Return value:
+ * - element: the editor DOM sub-tree.
+ */
+ renderEditor: (data: {
+ onBlur: () => void
+ editorRef: (el: HTMLElement | null) => void
+ }) => React.ReactNode
+ /**
+ * Function to render the edit button.
+ *
+ * Parameters:
+ * - object: { isVisible, onClick, onFocus, onBlur, buttonRef }
+ *
+ * Return value:
+ *
+ * - element: the edit button DOM sub-tree
+ *
+ * If you choose to use the default edit button, add `label` to the
+ * incoming `props` parameter and call `InPlaceEdit.renderDefaultEditButton(props)`
+ *
+ * If you choose to render a custom button, attach the on* event handlers
+ * and set `buttonRef` as a `ref` type property on the `button` element.
+ *
+ * `isVisible` is a hint as to whether the button is _typically_ shown,
+ * but you're free to ignore it for your use-case.
+ */
+ renderEditButton: (props: ExtendedRenderEditButton) => React.ReactNode | null
+ /**
+ * If `'view'`: the view component is rendered,
+ * if `'edit'`: the edit component is rendered
+ */
+ mode: 'view' | 'edit'
+ /**
+ * Called when the component's mode changes
+ * Parameter:
+ * - newMode: string
+ */
+ onChangeMode: EditableProps['onChangeMode']
+ /**
+ * The current value.
+ * The value is managed by the consuming app, but we need to tell InPlaceEdit
+ * it's changed or it won't re-render
+ */
+ value?: any
+ /**
+ * Called when Editable switches from edit to view mode and the value has changed.
+ * Parameter:
+ * - value: any
+ */
+ onChange?: EditableProps['onChange']
+ /**
+ * The mode is fixed as 'view'
+ */
+ readOnly?: boolean
+ /**
+ * Show a focus outline when the input is focused
+ */
+ showFocusRing?: boolean
+ /**
+ * Put the edit button before or after the view
+ */
+ editButtonPlacement?: 'start' | 'end'
+ /**
+ * Render outermost element inline v. block
+ */
+ inline?: boolean
+}
+
+type PropKeys = keyof InPlaceEditOwnProps
+
+type AllowedPropKeys = Readonly>
+
+type InPlaceEditProps = InPlaceEditOwnProps &
+ WithStyleProps
+
+type InPlaceEditStyle = ComponentStyle<'inPlaceEdit'>
+const allowedProps: AllowedPropKeys = [
+ 'renderViewer',
+ 'renderEditor',
+ 'renderEditButton',
+ 'mode',
+ 'onChangeMode',
+ 'value',
+ 'onChange',
+ 'readOnly',
+ 'showFocusRing',
+ 'editButtonPlacement',
+ 'inline'
+]
+
+export type { InPlaceEditProps, InPlaceEditStyle }
+export { allowedProps }
diff --git a/packages/ui-editable/src/InPlaceEdit/v2/styles.ts b/packages/ui-editable/src/InPlaceEdit/v2/styles.ts
new file mode 100644
index 0000000000..b0c4f3dcd5
--- /dev/null
+++ b/packages/ui-editable/src/InPlaceEdit/v2/styles.ts
@@ -0,0 +1,58 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - present Instructure, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import type { InPlaceEditStyle } from './props'
+
+/**
+ * ---
+ * private: true
+ * ---
+ * Generates the style object from the theme and provided additional information
+ * @param {Object} componentTheme The theme variable object.
+ * @param {Object} props the props of the component, the style is applied to
+ * @param {Object} state the state of the component, the style is applied to
+ * @return {Object} The final style object, which will be used in the component
+ */
+const generateStyle = (): InPlaceEditStyle => {
+ return {
+ inPlaceEdit: {
+ label: 'inPlaceEdit',
+ boxSizing: 'border-box',
+ maxWidth: '100%',
+ position: 'relative',
+ overflow: 'visible',
+ direction: 'inherit',
+ margin: 0,
+ textDecoration: 'none' /* for links styled as buttons */,
+ textAlign: 'inherit',
+ userSelect: 'none',
+ touchAction: 'manipulation',
+ background: 'transparent',
+ border: 'none',
+ outline: 'none'
+ }
+ }
+}
+
+export default generateStyle
diff --git a/packages/ui-editable/src/exports/b.ts b/packages/ui-editable/src/exports/b.ts
new file mode 100644
index 0000000000..e259e17dd7
--- /dev/null
+++ b/packages/ui-editable/src/exports/b.ts
@@ -0,0 +1,29 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 - present Instructure, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+export { Editable } from '../Editable/v1'
+export { InPlaceEdit } from '../InPlaceEdit/v2'
+
+export type { EditableProps } from '../Editable/v1/props'
+export type { InPlaceEditProps } from '../InPlaceEdit/v2/props'
diff --git a/packages/ui-text/src/Text/v2/styles.ts b/packages/ui-text/src/Text/v2/styles.ts
index 28dc430ec4..7df46355e2 100644
--- a/packages/ui-text/src/Text/v2/styles.ts
+++ b/packages/ui-text/src/Text/v2/styles.ts
@@ -206,7 +206,6 @@ const generateStyle = (
borderRadius: 0,
padding: 0,
margin: 0,
- color: 'inherit',
height: 'auto',
width: '100%',
lineHeight: 'inherit',