Skip to content

Commit

Permalink
✨ Modal dialog for video transcripts #2236 (#2398)
Browse files Browse the repository at this point in the history
* ✨ Modal transcript - initial version #2236

* ✨ Modal transcript initial version #2236

* Some more

* ✨ Add transcript for iframe #2236

* ♿️ Aria labels #2236
  • Loading branch information
padms authored Aug 16, 2024
1 parent e6b2465 commit d9cec43
Show file tree
Hide file tree
Showing 23 changed files with 448 additions and 62 deletions.
2 changes: 2 additions & 0 deletions sanityv3/schemas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import campaignBanner from './objects/campaignBanner'
import gridTeaser from './objects/grid/cellTypes/gridTeaser'
import threeColumns from './objects/grid/rowTypes/3columns'
import gridColorTheme from './objects/grid/theme'
import transcript from './objects/transcript'

const {
pageNotFound,
Expand Down Expand Up @@ -202,6 +203,7 @@ const RemainingSchemas = [
gridTeaser,
threeColumns,
gridColorTheme,
transcript,
]

// Then we give our schema to the builder and provide the result to Sanity
Expand Down
2 changes: 2 additions & 0 deletions sanityv3/schemas/objects/iframe.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
url,
height,
action,
transcript,
} from './iframe/sharedIframeFields'

const ingressContentType = configureBlockContent({
Expand Down Expand Up @@ -74,6 +75,7 @@ export default {
height,
description,
action,
transcript,
{
title: 'Background',
description: 'Pick a colour for the background. Default is white.',
Expand Down
5 changes: 5 additions & 0 deletions sanityv3/schemas/objects/iframe/sharedIframeFields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ export const description = {
of: [descriptionContentType],
}

export const transcript = {
name: 'transcript',
title: 'Enter transcript if this iframe is a youtube video.',
type: 'transcript',
}
export const action = {
name: 'action',
title: 'Link/action',
Expand Down
32 changes: 32 additions & 0 deletions sanityv3/schemas/objects/transcript.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { defineType, defineField } from 'sanity'
import { configureBlockContent } from '../editors/blockContentType'
import CompactBlockEditor from '../components/CompactBlockEditor'

const blockConfig = {
h2: false,
h3: false,
h4: false,
smallText: false,
internalLink: false,
externalLink: false,
attachment: false,
lists: true,
}

const blockContentType = configureBlockContent({ ...blockConfig })

export default {
name: 'transcript',
title: 'Transcript',
type: 'object',
fields: [
{
name: 'text',
type: 'array',
components: {
input: CompactBlockEditor,
},
of: [blockContentType],
},
],
}
5 changes: 5 additions & 0 deletions sanityv3/schemas/objects/videoFile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ export default {
type: 'hlsVideo',
validation: (Rule: Rule) => Rule.required(),
},
{
name: 'transcript',
title: 'Video Transcript',
type: 'transcript',
},
{
name: 'thumbnail',
type: 'imageWithAlt',
Expand Down
7 changes: 7 additions & 0 deletions sanityv3/schemas/objects/videoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ export default {
collapsed: false,
},
},
{
title: 'Show transcript button',
description: 'Shows transcript from the video asset.',
name: 'showTranscript',
type: 'boolean',
},
{
name: 'aspectRatio',
type: 'string',
Expand Down Expand Up @@ -108,6 +114,7 @@ export default {
description: 'Set a fixed height in pixels for the video. Note: this will override the aspect ratio setting.',
validation: (Rule: Rule) => Rule.positive().greaterThan(0).precision(0),
},

{
title: 'Use brand theme for video',
description: 'Make play button bigger and brand red.',
Expand Down
5 changes: 5 additions & 0 deletions sanityv3/schemas/textSnippets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ const snippets: textSnippet = {
defaultValue: 'Loading...',
group: groups.others,
},
read_transcript: {
title: 'Read Transcript',
defaultValue: 'Read transcript',
group: groups.others,
},
menu: {
title: 'Menu',
defaultValue: 'Menu',
Expand Down
1 change: 1 addition & 0 deletions web/components/src/Topbar/Topbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const Topbar = ({ children, ...rest }: HTMLAttributes<HTMLDivElement>) =>
let currentScrollPos = window.pageYOffset
// Fix for iOS to avoid negative scroll positions
if (currentScrollPos < 0) currentScrollPos = 0

setIsVisible(
(prevScrollPos > currentScrollPos && prevScrollPos - currentScrollPos > height) ||
currentScrollPos < prevScrollPos ||
Expand Down
30 changes: 27 additions & 3 deletions web/core/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { twMerge } from 'tailwind-merge'
export const commonButtonStyling = `
w-fit
text-sm
px-5
py-3
px-3
py-2
lg:px-5
lg:py-3
rounded-md
focus:outline-none
focus-visible:envis-outline
Expand All @@ -18,7 +20,7 @@ items-center
gap-3
`

export type Variants = 'contained' | 'outlined' | 'ghost'
export type Variants = 'contained' | 'outlined' | 'ghost' | 'contained-secondary' | 'outlined-secondary'

/** Use for common button styling in Button,IconButton, Link/ButtonLink */
export const getVariant = (variant: Variants): string => {
Expand Down Expand Up @@ -47,6 +49,28 @@ export const getVariant = (variant: Variants): string => {
dark:hover:bg-white-transparent
dark:focus-visible:outline-white-100
`
case 'outlined-secondary':
return `
border
border-north-sea-100
text-black-80
hover:bg-slate-blue-100
hover:text-white-100
focus:outline-none
focus-visible:outline-slate-blue-95
dark:text-white-100
dark:border-white-100
dark:hover:bg-white-transparent
dark:focus-visible:outline-white-100
`
case 'contained-secondary':
return `bg-slate-blue-95
text-white-100
hover:bg-slate-blue-100
hover:text-white-100
focus:outline-none
focus-visible:outline-slate-blue-95
`
case 'contained':
default:
return `bg-norwegian-woods-100
Expand Down
33 changes: 3 additions & 30 deletions web/icons/ArrowRight.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { forwardRef, Ref, SVGProps } from 'react'
import { arrow_forward } from '@equinor/eds-icons'
import { TransformableIcon } from './TransformableIcon'

export type ArrowRightProps = {
/** Size, use if you need large icon resolutions
Expand All @@ -10,35 +11,7 @@ export type ArrowRightProps = {
ref?: Ref<SVGSVGElement>
} & SVGProps<SVGSVGElement>

export const ArrowRight = forwardRef<SVGSVGElement, ArrowRightProps>(function ArrowRight(
{ size = 24, className = '', ...rest },
ref,
) {
let icon = arrow_forward
if (size < 24) {
// fallback to normal icon if small is not made yet
icon = icon.sizes?.small || icon
}
return (
<svg
ref={ref}
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox={`0 0 ${size} ${size}`}
fill="currentColor"
aria-hidden
className={className}
{...rest}
>
{Array.isArray(icon.svgPathData) ? (
icon.svgPathData.map((pathData) => {
return <path key={pathData} fillRule="evenodd" clipRule="evenodd" d={pathData} />
})
) : (
<path fillRule="evenodd" clipRule="evenodd" d={icon.svgPathData} />
)}
</svg>
)
export const ArrowRight = forwardRef<SVGSVGElement, ArrowRightProps>(function ArrowRight({ ...rest }, ref) {
return <TransformableIcon iconData={arrow_forward} {...rest} ref={ref} />
})
export default ArrowRight
50 changes: 50 additions & 0 deletions web/icons/TransformableIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { forwardRef, Ref, SVGProps } from 'react'
import { IconData } from '@equinor/eds-icons'

/**
* Use this to transform an icon for example arrow right can be rotated to
* 45 deg/-90 deg to use it as external link icon or download icon.
* Similarly the + and close
*/
export type TransformableIconProps = {
iconData: IconData
/** Size, use if you need large icon resolutions
* @default 24
*/
size?: 16 | 18 | 24 | 32 | 40 | 48
/** @ignore */
ref?: Ref<SVGSVGElement>
} & SVGProps<SVGSVGElement>

export const TransformableIcon = forwardRef<SVGSVGElement, TransformableIconProps>(function ArrowRight(
{ iconData, size = 24, className = '', ...rest },
ref,
) {
let icon = iconData
if (size < 24) {
// fallback to normal icon if small is not made yet
icon = icon.sizes?.small || icon
}
return (
<svg
ref={ref}
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox={`0 0 ${size} ${size}`}
fill="currentColor"
aria-hidden
className={className}
{...rest}
>
{Array.isArray(icon.svgPathData) ? (
icon.svgPathData.map((pathData) => {
return <path key={pathData} fillRule="evenodd" clipRule="evenodd" d={pathData} />
})
) : (
<path fillRule="evenodd" clipRule="evenodd" d={icon.svgPathData} />
)}
</svg>
)
})
export default TransformableIcon
4 changes: 4 additions & 0 deletions web/lib/queries/common/pageContentFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ _type == "keyNumbers" =>{
...,
${markDefs},
},
"transcript":transcript.text[]{
...,
${markDefs},
},
frameTitle,
"action": action[0]{
${linkSelectorFields},
Expand Down
1 change: 1 addition & 0 deletions web/lib/queries/videoPlayerFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const videoPlayerFields = /* groq */ `
autoPlay,
muted,
},
defined(showTranscript) && showTranscript => { "transcript": videoFile->transcript.text},
"designOptions": {
"aspectRatio": coalesce(aspectRatio, '16:9'),
height,
Expand Down
6 changes: 4 additions & 2 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@sanity/webhook": "^2.0.0",
"@types/easy-soap-request": "^4.1.1",
"@types/uuid": "^8.3.4",
"@types/react-transition-group": "^4.4.10",
"@types/xml2js": "^0.4.11",
"algoliasearch": "^4.16.0",
"date-fns": "^2.29.3",
Expand Down Expand Up @@ -80,7 +81,8 @@
"swr": "^1.3.0",
"tailwind-merge": "^2.2.1",
"uuid": "^9.0.0",
"xml2js": "^0.6.0"
"xml2js": "^0.6.0",
"react-transition-group": "^4.4.5"
},
"devDependencies": {
"@algolia/client-search": "^4.16.0",
Expand Down Expand Up @@ -114,4 +116,4 @@
"vite": "^5.2.10",
"webpack": "^5.88.2"
}
}
}
65 changes: 65 additions & 0 deletions web/pageComponents/shared/TranscriptAndActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { LinkData } from '../../types'
import { getUrlFromAction } from '../../common/helpers'
import { useState } from 'react'
import { PortableTextBlock } from '@portabletext/types'
import { ButtonLink } from '@core/Link'
import { commonButtonStyling, getVariant } from '@core/Button'
import { getLocaleFromName } from '../../lib/localization'
import Modal from '@sections/Modal/Modal'
import RichText from './portableText/RichText'
import { add_circle_filled } from '@equinor/eds-icons'
import { twMerge } from 'tailwind-merge'
import { TransformableIcon } from '../../icons/TransformableIcon'
import { useIntl } from 'react-intl'
import { title } from 'process'

type TranscriptAndActionsProps = {
className?: string
action?: LinkData
transcript?: PortableTextBlock[]
ariaTitle: string
}
const TranscriptAndActions = ({ action, transcript, className, ariaTitle }: TranscriptAndActionsProps) => {
const [isOpen, setIsOpen] = useState(false)
const actionUrl = action ? getUrlFromAction(action) : ''
const intl = useIntl()
const readTranscript = intl.formatMessage({ id: 'read_transcript', defaultMessage: 'Read transcript' })
const handleOpen = () => {
setIsOpen(true)
}
const handleClose = () => {
setIsOpen(false)
}
return (
<div className={twMerge(`grid md:grid-cols-2 md:gap-x-4 md:pt-11 pt-8`, className)}>
{action && action.label && (
<ButtonLink
href={actionUrl || ''}
aria-label={action?.ariaLabel}
variant="outlined"
className={`w-full md:mb-8 mb-4 justify-center ${getVariant('outlined-secondary')}`}
locale={action?.type === 'internalUrl' ? getLocaleFromName(action?.link?.lang) : undefined}
>
{action.label}
</ButtonLink>
)}

{transcript && (
<>
<button
onClick={handleOpen}
aria-label={`${readTranscript} ${ariaTitle}`}
className={`w-full mb-8 ${commonButtonStyling} ${getVariant('contained-secondary')}`}
>
<span className="grow">{readTranscript}</span>
<TransformableIcon className={'scale-90 lg:scale-100'} iconData={add_circle_filled} />
</button>
<Modal isOpen={isOpen} onClose={handleClose} title={ariaTitle}>
<RichText value={transcript} />
</Modal>
</>
)}
</div>
)
}
export default TranscriptAndActions
Loading

0 comments on commit d9cec43

Please sign in to comment.