Skip to content

Commit

Permalink
feat: Modal & Input updates for marketing site (#478)
Browse files Browse the repository at this point in the history
  • Loading branch information
dogmar authored May 31, 2023
1 parent b4a39c0 commit 6bdbca1
Show file tree
Hide file tree
Showing 13 changed files with 1,397 additions and 386 deletions.
33 changes: 17 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,21 +76,21 @@
"use-immer": "0.8.1"
},
"devDependencies": {
"@babel/core": "7.21.8",
"@babel/preset-env": "7.21.5",
"@babel/preset-react": "7.18.6",
"@babel/core": "7.22.1",
"@babel/preset-env": "7.22.4",
"@babel/preset-react": "7.22.3",
"@emotion/react": "11.11.0",
"@emotion/styled": "11.11.0",
"@pluralsh/eslint-config-typescript": "2.5.41",
"@storybook/addon-actions": "7.0.12",
"@storybook/addon-docs": "7.0.12",
"@storybook/addon-essentials": "7.0.12",
"@storybook/addon-interactions": "7.0.12",
"@storybook/addon-links": "7.0.12",
"@storybook/builder-vite": "7.0.12",
"@storybook/node-logger": "7.0.12",
"@storybook/react": "7.0.12",
"@storybook/react-vite": "7.0.12",
"@storybook/addon-actions": "7.0.18",
"@storybook/addon-docs": "7.0.18",
"@storybook/addon-essentials": "7.0.18",
"@storybook/addon-interactions": "7.0.18",
"@storybook/addon-links": "7.0.18",
"@storybook/builder-vite": "7.0.18",
"@storybook/node-logger": "7.0.18",
"@storybook/react": "7.0.18",
"@storybook/react-vite": "7.0.18",
"@storybook/testing-library": "0.1.0",
"@types/react-dom": "18.2.4",
"@types/react-transition-group": "4.4.6",
Expand All @@ -113,17 +113,18 @@
"http-server": "14.1.1",
"husky": "8.0.3",
"jest-mock": "29.4.3",
"lint-staged": "13.2.0",
"lint-staged": "13.2.2",
"npm-run-all": "4.1.5",
"prettier": "2.8.7",
"prettier": "2.8.8",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-transition-group": "4.4.5",
"rimraf": "3.0.2",
"storybook": "7.0.12",
"styled-components": "5.3.10",
"styled-components": "5.3.11",
"typescript": "4.9.5",
"vite": "4.3.8"
"usehooks-ts": "2.9.1",
"vite": "4.3.9"
},
"peerDependencies": {
"@emotion/react": ">=11.10.6",
Expand Down
79 changes: 69 additions & 10 deletions src/components/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import { ExtendTheme, Input as HonorableInput, mergeTheme } from 'honorable'
import type { InputProps as HonorableInputProps } from 'honorable'
import { type ReactNode, forwardRef } from 'react'
import { type ReactNode, forwardRef, useRef } from 'react'
import styled from 'styled-components'
import { mergeRefs } from 'react-merge-refs'

import { simulateInputChange } from '../utils/simulateInputChange'

import { useFillLevel } from './contexts/FillLevelContext'
import { TitleContent } from './Select'
import Tooltip from './Tooltip'
import IconFrame from './IconFrame'
import CloseIcon from './icons/CloseIcon'

export type InputProps = HonorableInputProps & {
suffix?: ReactNode
prefix?: ReactNode
titleContent?: ReactNode
showClearButton?: boolean
}

const PrefixSuffix = styled.div(({ theme }) => ({
Expand All @@ -31,15 +38,51 @@ const startEndStyles = {
paddingLeft: 0,
}

const ClearButton = styled(({ className, ...props }) => (
<div className={className}>
<Tooltip
placement="top"
label="Clear"
>
<IconFrame
clickable
icon={<CloseIcon />}
size="small"
{...props}
/>
</Tooltip>
</div>
))(({ theme }) => ({
display: 'flex',
alignItems: 'center',
alignSelf: 'stretch',
paddingRight: theme.spacing.xsmall,
}))

const InputTitleContent = styled(TitleContent)((_) => ({
alignSelf: 'stretch',
}))

const Input = forwardRef(
(
{ startIcon, endIcon, suffix, prefix, titleContent, ...props }: InputProps,
{
startIcon,
endIcon,
suffix,
prefix,
showClearButton,
titleContent,
inputProps,
...props
}: InputProps,
ref
) => {
const inputRef = useRef<HTMLInputElement>(null)

inputProps = {
...(inputProps ?? {}),
ref: mergeRefs([inputRef, ...(inputProps?.ref ? [inputProps.ref] : [])]),
}
let themeExtension: any = {}
const parentFillLevel = useFillLevel()
const size = (props as any).large
Expand All @@ -48,11 +91,13 @@ const Input = forwardRef(
? 'small'
: 'medium'

if (suffix) {
if (suffix || showClearButton) {
themeExtension = mergeTheme(themeExtension, {
Input: {
Root: [{ paddingRight: 0 }],
EndIcon: [{ ...startEndStyles, ...{ paddingLeft: 'xsmall' } }],
EndIcon: [
{ ...startEndStyles, ...{ paddingLeft: 'xsmall', gap: 0 } },
],
},
})
}
Expand All @@ -77,12 +122,25 @@ const Input = forwardRef(
<HonorableInput
ref={ref}
endIcon={
endIcon || suffix ? (
<>
{endIcon}
{suffix && <PrefixSuffix>{suffix}</PrefixSuffix>}
</>
) : undefined
<>
{showClearButton && props.value && (
<ClearButton
onClick={() => {
const input = inputRef?.current

if (input) {
simulateInputChange(input, '')
}
}}
/>
)}
{endIcon || suffix ? (
<>
{endIcon}
{suffix && <PrefixSuffix>{suffix}</PrefixSuffix>}
</>
) : undefined}
</>
}
startIcon={
startIcon || prefix || titleContent ? (
Expand All @@ -100,6 +158,7 @@ const Input = forwardRef(
</>
) : undefined
}
inputProps={inputProps}
{...props}
/>
</ExtendTheme>
Expand Down
5 changes: 3 additions & 2 deletions src/components/LoadingSpinner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ import {
forwardRef,
useCallback,
useEffect,
useLayoutEffect,
useMemo,
useRef,
useState,
} from 'react'
import { CSSTransition } from 'react-transition-group'

import { useIsomorphicLayoutEffect } from 'react-spring'

import useResizeObserver from '../hooks/useResizeObserver'

export type LoadingSpinnerProps = DivProps & {
Expand Down Expand Up @@ -171,7 +172,7 @@ function CenteringWrapper({ children, ...props }: FlexProps) {
}, [top, windowHeight])

useResizeObserver(ref, onSizeChange)
useLayoutEffect(() => {
useIsomorphicLayoutEffect(() => {
window.addEventListener('resize', onSizeChange)
window.addEventListener('scroll', onSizeChange)

Expand Down
12 changes: 11 additions & 1 deletion src/components/Modal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type ReactNode, type Ref, forwardRef } from 'react'
import { type ReactNode, type Ref, forwardRef, useEffect } from 'react'
import {
Div,
Flex,
Expand All @@ -10,6 +10,8 @@ import PropTypes from 'prop-types'

import { type ColorKey, type Severity } from '../types'

import useLockedBody from '../hooks/useLockedBody'

import CheckRoundedIcon from './icons/CheckRoundedIcon'
import type createIcon from './icons/createIcon'
import ErrorIcon from './icons/ErrorIcon'
Expand All @@ -26,6 +28,7 @@ type ModalPropsType = ModalProps & {
header?: ReactNode
actions?: ReactNode
severity?: ModalSeverity
lockBody?: boolean
}

const severityToIconColorKey: Readonly<
Expand Down Expand Up @@ -71,13 +74,20 @@ function ModalRef(
size = form ? 'large' : 'medium',
onClose,
severity,
lockBody = true,
...props
}: ModalPropsType,
ref: Ref<any>
) {
const HeaderIcon = severityToIcon[severity ?? 'default']
const iconColorKey = severityToIconColorKey[severity ?? 'default']

const [, setBodyLocked] = useLockedBody(open && lockBody)

useEffect(() => {
setBodyLocked(lockBody && open)
}, [lockBody, open, setBodyLocked])

return (
<HonorableModal
open={open}
Expand Down
71 changes: 71 additions & 0 deletions src/hooks/useLockedBody.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Adapted from https://usehooks-ts.com/react-hook/use-locked-body
// Modified to calculate scrollbar width via document.documentElement if no
// rootId is provided, and to be sure to measure scrollbar width before
// overflow is set to hidden (and thus removing the scrollbar).
import { useEffect, useState } from 'react'

import { useIsomorphicLayoutEffect } from 'usehooks-ts'

import canUseDOM from '../utils/canUseDOM'

type UseLockedBodyOutput = [boolean, (locked: boolean) => void]

function useLockedBody(
initialLocked = false,
rootId: string = undefined
): UseLockedBodyOutput {
const [locked, setLocked] = useState(initialLocked)

// Do the side effect before render
useIsomorphicLayoutEffect(() => {
if (!locked) {
return
}

// Save initial body style
const originalOverflow = document.body.style.overflow
const originalPaddingRight = document.body.style.paddingRight

// Get the scrollBar width
let scrollBarWidth = 0

if (canUseDOM) {
if (rootId) {
const root = document.getElementById(rootId)

scrollBarWidth = root ? root.offsetWidth - root.scrollWidth : 0
} else {
scrollBarWidth =
window.innerWidth - document.documentElement.clientWidth
}
}

// Lock body scroll
document.body.style.overflow = 'hidden'

// Avoid width reflow
if (scrollBarWidth) {
document.body.style.paddingRight = `${scrollBarWidth}px`
}

return () => {
document.body.style.overflow = originalOverflow

if (scrollBarWidth) {
document.body.style.paddingRight = originalPaddingRight
}
}
}, [locked])

// Update state if initialValue changes
useEffect(() => {
if (locked !== initialLocked) {
setLocked(initialLocked)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [initialLocked])

return [locked, setLocked]
}

export default useLockedBody
5 changes: 3 additions & 2 deletions src/hooks/useResizeObserver.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type RefObject, useCallback, useLayoutEffect } from 'react'
import { type RefObject, useCallback } from 'react'
import { useIsomorphicLayoutEffect } from 'react-spring'
import ResizeObserver from 'resize-observer-polyfill'

const useResizeObserver = (
Expand All @@ -20,7 +21,7 @@ const useResizeObserver = (
[callback]
)

useLayoutEffect(() => {
useIsomorphicLayoutEffect(() => {
if (!ref.current) {
return
}
Expand Down
Loading

0 comments on commit 6bdbca1

Please sign in to comment.