[GH-1057] Highlighting on hover for property values in the card dialog (#1065)
* Highlight property values on hover: - highlight on hover property values that are not readonly - make property name buttons the same width - set `min-width: 150px` for property values - add `readonly` class for LastModifiedAt/LastModifiedBy/CreatedAt * Make `Editable` used in card property values automatically expandable: - input width computation relies on `useLayoutEffect` and `getComputedStyle` - enable automatic expand for editable fields in `PropertyValueElement` - enable automatic expand for editable inside `URLProperty` - fix for tooltip display in `KanbanCard` * Fix issue with ellipsis in Chrome. * Support highlight on hover for `UserProperty` * Updating hover state and UI * Jest snapshot updated. * Fix jest snapshots * Update jest snapshot * Update jest snapshot Co-authored-by: Asaad Mahmood <asaadmahmood@users.noreply.github.com> Co-authored-by: Chen-I Lim <46905241+chenilim@users.noreply.github.com>
This commit is contained in:
parent
cc5c712bd0
commit
50470efe07
20 changed files with 75 additions and 38 deletions
|
@ -3,7 +3,7 @@
|
||||||
exports[`components/propertyValueElement should match snapshot, date, array value 1`] = `
|
exports[`components/propertyValueElement should match snapshot, date, array value 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="DateRange empty"
|
class="DateRange empty octo-propertyvalue"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="Button "
|
class="Button "
|
||||||
|
@ -44,6 +44,7 @@ exports[`components/propertyValueElement should match snapshot, person, array va
|
||||||
class="Editable octo-propertyvalue"
|
class="Editable octo-propertyvalue"
|
||||||
placeholder="Empty"
|
placeholder="Empty"
|
||||||
spellcheck="true"
|
spellcheck="true"
|
||||||
|
style="width: 5px;"
|
||||||
title=""
|
title=""
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
|
@ -98,6 +99,7 @@ exports[`components/propertyValueElement should match snapshot, url, array value
|
||||||
<input
|
<input
|
||||||
class="Editable octo-propertyvalue"
|
class="Editable octo-propertyvalue"
|
||||||
placeholder="Empty"
|
placeholder="Empty"
|
||||||
|
style="width: 5px;"
|
||||||
title="http://localhost"
|
title="http://localhost"
|
||||||
value="http://localhost"
|
value="http://localhost"
|
||||||
/>
|
/>
|
||||||
|
@ -123,6 +125,7 @@ exports[`components/propertyValueElement should match snapshot, url, array value
|
||||||
<input
|
<input
|
||||||
class="Editable octo-propertyvalue"
|
class="Editable octo-propertyvalue"
|
||||||
placeholder="Empty"
|
placeholder="Empty"
|
||||||
|
style="width: 5px;"
|
||||||
title="http://localhost"
|
title="http://localhost"
|
||||||
value="http://localhost"
|
value="http://localhost"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -35,19 +35,33 @@
|
||||||
|
|
||||||
.octo-propertyrow {
|
.octo-propertyrow {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
|
||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
|
||||||
.octo-propertyvalue {
|
.octo-propertyvalue {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
width: 100%;
|
|
||||||
|
&:not(.readonly) {
|
||||||
|
min-width: 180px;
|
||||||
|
transition: background 100ms ease-out 0s;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(var(--center-channel-color-rgb), 0.08);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.MenuWrapper {
|
.MenuWrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Label {
|
||||||
|
margin: 0 4px 0 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.octo-propertyname {
|
.octo-propertyname {
|
||||||
|
@ -56,6 +70,9 @@
|
||||||
color: rgba(var(--center-channel-color-rgb), 0.6);
|
color: rgba(var(--center-channel-color-rgb), 0.6);
|
||||||
|
|
||||||
.Button {
|
.Button {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 8px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
justify-content: unset;
|
justify-content: unset;
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,11 @@
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.octo-tooltip {
|
||||||
|
display: flex;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.octo-propertyvalue {
|
.octo-propertyvalue {
|
||||||
margin: 4px 0 0;
|
margin: 4px 0 0;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|
|
@ -14,7 +14,7 @@ type Props = {
|
||||||
const CreatedAt = (props: Props): JSX.Element => {
|
const CreatedAt = (props: Props): JSX.Element => {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
return (
|
return (
|
||||||
<div className='CreatedAt octo-propertyvalue'>
|
<div className='CreatedAt octo-propertyvalue readonly'>
|
||||||
{Utils.displayDateTime(new Date(props.createAt), intl)}
|
{Utils.displayDateTime(new Date(props.createAt), intl)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
exports[`components/properties/dateRange cancel set via text input 1`] = `
|
exports[`components/properties/dateRange cancel set via text input 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="DateRange "
|
class="DateRange octo-propertyvalue"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="Button "
|
class="Button "
|
||||||
|
@ -20,7 +20,7 @@ exports[`components/properties/dateRange cancel set via text input 1`] = `
|
||||||
exports[`components/properties/dateRange handle clear 1`] = `
|
exports[`components/properties/dateRange handle clear 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="DateRange "
|
class="DateRange octo-propertyvalue"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="Button "
|
class="Button "
|
||||||
|
@ -37,7 +37,7 @@ exports[`components/properties/dateRange handle clear 1`] = `
|
||||||
exports[`components/properties/dateRange returns default correctly 1`] = `
|
exports[`components/properties/dateRange returns default correctly 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="DateRange empty"
|
class="DateRange empty octo-propertyvalue"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="Button "
|
class="Button "
|
||||||
|
@ -52,7 +52,7 @@ exports[`components/properties/dateRange returns default correctly 1`] = `
|
||||||
exports[`components/properties/dateRange returns local correctly - es local 1`] = `
|
exports[`components/properties/dateRange returns local correctly - es local 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="DateRange "
|
class="DateRange octo-propertyvalue"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="Button "
|
class="Button "
|
||||||
|
@ -69,7 +69,7 @@ exports[`components/properties/dateRange returns local correctly - es local 1`]
|
||||||
exports[`components/properties/dateRange set via text input 1`] = `
|
exports[`components/properties/dateRange set via text input 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="DateRange "
|
class="DateRange octo-propertyvalue"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="Button "
|
class="Button "
|
||||||
|
@ -86,7 +86,7 @@ exports[`components/properties/dateRange set via text input 1`] = `
|
||||||
exports[`components/properties/dateRange set via text input, es locale 1`] = `
|
exports[`components/properties/dateRange set via text input, es locale 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="DateRange "
|
class="DateRange octo-propertyvalue"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="Button "
|
class="Button "
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
.DateRange {
|
.DateRange {
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.inputContainer {
|
.inputContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
|
|
@ -158,7 +158,7 @@ function DateRange(props: Props): JSX.Element {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`DateRange ${displayValue ? '' : 'empty'}`}>
|
<div className={`DateRange ${displayValue ? '' : 'empty'} ` + className}>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setShowDialog(true)}
|
onClick={() => setShowDialog(true)}
|
||||||
>
|
>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
exports[`componnets/properties/lastModifiedAt should match snapshot 1`] = `
|
exports[`componnets/properties/lastModifiedAt should match snapshot 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="LastModifiedAt octo-propertyvalue"
|
class="LastModifiedAt octo-propertyvalue readonly"
|
||||||
>
|
>
|
||||||
June 15, 4:22 PM
|
June 15, 4:22 PM
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -29,7 +29,7 @@ const LastModifiedAt = (props: Props): JSX.Element => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='LastModifiedAt octo-propertyvalue'>
|
<div className='LastModifiedAt octo-propertyvalue readonly'>
|
||||||
{Utils.displayDateTime(new Date(latestBlock.updateAt), intl)}
|
{Utils.displayDateTime(new Date(latestBlock.updateAt), intl)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
exports[`components/properties/lastModifiedBy should match snapshot 1`] = `
|
exports[`components/properties/lastModifiedBy should match snapshot 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="LastModifiedBy octo-propertyvalue"
|
class="LastModifiedBy octo-propertyvalue readonly"
|
||||||
>
|
>
|
||||||
username_1
|
username_1
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -31,7 +31,7 @@ const LastModifiedBy = (props: Props): JSX.Element => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='LastModifiedBy octo-propertyvalue'>
|
<div className='LastModifiedBy octo-propertyvalue readonly'>
|
||||||
{(workspaceUsersById && workspaceUsersById[latestBlock.modifiedBy]?.username) || latestBlock.modifiedBy}
|
{(workspaceUsersById && workspaceUsersById[latestBlock.modifiedBy]?.username) || latestBlock.modifiedBy}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,6 +7,7 @@ exports[`components/properties/link returns link properties correctly 1`] = `
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
class="Editable octo-propertyvalue"
|
class="Editable octo-propertyvalue"
|
||||||
|
style="width: 5px;"
|
||||||
title="https://github.com/mattermost/focalboard"
|
title="https://github.com/mattermost/focalboard"
|
||||||
value="https://github.com/mattermost/focalboard"
|
value="https://github.com/mattermost/focalboard"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -4,11 +4,8 @@
|
||||||
|
|
||||||
&.url {
|
&.url {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
a {
|
|
||||||
padding: 0 8px; // increases clickable area for better UX
|
|
||||||
align-self: stretch;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Link__button {
|
.Link__button {
|
||||||
|
@ -34,10 +31,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
|
||||||
background: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
.Link__button {
|
.Link__button {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -41,6 +41,7 @@ const URLProperty = (props: Props): JSX.Element => {
|
||||||
className='octo-propertyvalue'
|
className='octo-propertyvalue'
|
||||||
placeholderText={props.placeholder}
|
placeholderText={props.placeholder}
|
||||||
value={props.value}
|
value={props.value}
|
||||||
|
autoExpand={true}
|
||||||
readonly={props.readonly}
|
readonly={props.readonly}
|
||||||
onChange={props.onChange}
|
onChange={props.onChange}
|
||||||
onSave={props.onSave}
|
onSave={props.onSave}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
exports[`components/properties/user not readonly 1`] = `
|
exports[`components/properties/user not readonly 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="UserProperty css-2b097c-container"
|
class="UserProperty octo-propertyvalue css-2b097c-container"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
aria-atomic="false"
|
aria-atomic="false"
|
||||||
|
@ -96,7 +96,7 @@ exports[`components/properties/user not readonly 1`] = `
|
||||||
exports[`components/properties/user not readonly not existing user 1`] = `
|
exports[`components/properties/user not readonly not existing user 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="UserProperty css-2b097c-container"
|
class="UserProperty octo-propertyvalue css-2b097c-container"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
aria-atomic="false"
|
aria-atomic="false"
|
||||||
|
@ -182,7 +182,7 @@ exports[`components/properties/user readonly view 1`] = `
|
||||||
exports[`components/properties/user user dropdown open 1`] = `
|
exports[`components/properties/user user dropdown open 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="UserProperty css-2b097c-container"
|
class="UserProperty octo-propertyvalue css-2b097c-container"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
aria-atomic="false"
|
aria-atomic="false"
|
||||||
|
|
|
@ -40,7 +40,7 @@ const UserProperty = (props: Props): JSX.Element => {
|
||||||
isSearchable={true}
|
isSearchable={true}
|
||||||
isClearable={true}
|
isClearable={true}
|
||||||
backspaceRemovesValue={true}
|
backspaceRemovesValue={true}
|
||||||
className={'UserProperty'}
|
className={'UserProperty octo-propertyvalue'}
|
||||||
styles={selectStyles}
|
styles={selectStyles}
|
||||||
placeholder={'Empty'}
|
placeholder={'Empty'}
|
||||||
getOptionLabel={(o: IUser) => o.username}
|
getOptionLabel={(o: IUser) => o.username}
|
||||||
|
|
|
@ -233,6 +233,7 @@ const PropertyValueElement = (props:Props): JSX.Element => {
|
||||||
className='octo-propertyvalue'
|
className='octo-propertyvalue'
|
||||||
placeholderText={emptyDisplayValue}
|
placeholderText={emptyDisplayValue}
|
||||||
value={value.toString()}
|
value={value.toString()}
|
||||||
|
autoExpand={true}
|
||||||
onChange={setValue}
|
onChange={setValue}
|
||||||
onSave={saveTextProperty}
|
onSave={saveTextProperty}
|
||||||
onCancel={() => setValue(propertyValue)}
|
onCancel={() => setValue(propertyValue)}
|
||||||
|
|
|
@ -217,7 +217,7 @@ exports[`components/table/Table extended should match snapshot with CreatedBy 1`
|
||||||
style="width: 100px;"
|
style="width: 100px;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="CreatedAt octo-propertyvalue"
|
class="CreatedAt octo-propertyvalue readonly"
|
||||||
>
|
>
|
||||||
June 15, 4:22 PM
|
June 15, 4:22 PM
|
||||||
</div>
|
</div>
|
||||||
|
@ -305,7 +305,7 @@ exports[`components/table/Table extended should match snapshot with CreatedBy 1`
|
||||||
style="width: 100px;"
|
style="width: 100px;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="CreatedAt octo-propertyvalue"
|
class="CreatedAt octo-propertyvalue readonly"
|
||||||
>
|
>
|
||||||
June 15, 4:22 PM
|
June 15, 4:22 PM
|
||||||
</div>
|
</div>
|
||||||
|
@ -993,7 +993,7 @@ exports[`components/table/Table extended should match snapshot with UpdatedAt 1`
|
||||||
style="width: 100px;"
|
style="width: 100px;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="LastModifiedAt octo-propertyvalue"
|
class="LastModifiedAt octo-propertyvalue readonly"
|
||||||
>
|
>
|
||||||
June 20, 12:22 PM
|
June 20, 12:22 PM
|
||||||
</div>
|
</div>
|
||||||
|
@ -1081,7 +1081,7 @@ exports[`components/table/Table extended should match snapshot with UpdatedAt 1`
|
||||||
style="width: 100px;"
|
style="width: 100px;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="LastModifiedAt octo-propertyvalue"
|
class="LastModifiedAt octo-propertyvalue readonly"
|
||||||
>
|
>
|
||||||
June 22, 11:23 AM
|
June 22, 11:23 AM
|
||||||
</div>
|
</div>
|
||||||
|
@ -1381,7 +1381,7 @@ exports[`components/table/Table extended should match snapshot with UpdatedBy 1`
|
||||||
style="width: 100px;"
|
style="width: 100px;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="LastModifiedBy octo-propertyvalue"
|
class="LastModifiedBy octo-propertyvalue readonly"
|
||||||
>
|
>
|
||||||
username_4
|
username_4
|
||||||
</div>
|
</div>
|
||||||
|
@ -1469,7 +1469,7 @@ exports[`components/table/Table extended should match snapshot with UpdatedBy 1`
|
||||||
style="width: 100px;"
|
style="width: 100px;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="LastModifiedBy octo-propertyvalue"
|
class="LastModifiedBy octo-propertyvalue readonly"
|
||||||
>
|
>
|
||||||
username_3
|
username_3
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba(var(--center-channel-color-rgb), 0.1);
|
background-color: rgba(var(--center-channel-color-rgb), 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.filled {
|
&.filled {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import React, {forwardRef, useImperativeHandle, useRef} from 'react'
|
import React, {forwardRef, useImperativeHandle, useLayoutEffect, useRef} from 'react'
|
||||||
|
|
||||||
import './editable.scss'
|
import './editable.scss'
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ export type EditableProps = {
|
||||||
saveOnEsc?: boolean
|
saveOnEsc?: boolean
|
||||||
readonly?: boolean
|
readonly?: boolean
|
||||||
spellCheck?: boolean
|
spellCheck?: boolean
|
||||||
|
autoExpand?: boolean
|
||||||
|
|
||||||
validator?: (value: string) => boolean
|
validator?: (value: string) => boolean
|
||||||
onCancel?: () => void
|
onCancel?: () => void
|
||||||
|
@ -115,9 +116,26 @@ export function useEditable(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function borderWidth(style: CSSStyleDeclaration): number {
|
||||||
|
return (
|
||||||
|
parseInt(style.borderLeftWidth || '0', 10) +
|
||||||
|
parseInt(style.borderRightWidth || '0', 10)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const Editable = (props: EditableProps, ref: React.Ref<Focusable>): JSX.Element => {
|
const Editable = (props: EditableProps, ref: React.Ref<Focusable>): JSX.Element => {
|
||||||
const elementRef = useRef<HTMLInputElement>(null)
|
const elementRef = useRef<HTMLInputElement>(null)
|
||||||
const elementProps = useEditable(props, ref, elementRef)
|
const elementProps = useEditable(props, ref, elementRef)
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (props.autoExpand && elementRef.current) {
|
||||||
|
const input = elementRef.current
|
||||||
|
const computed = getComputedStyle(input)
|
||||||
|
input.style.width = 'auto'
|
||||||
|
input.style.width = `${input.scrollWidth + borderWidth(computed) + 1}px`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
{...elementProps}
|
{...elementProps}
|
||||||
|
|
Loading…
Reference in a new issue