Merge pull request #24660 from overleaf/td-bs5-remove-react-bootstrap-0

Remove react-bootstrap 0.33.1

GitOrigin-RevId: c320a6b18c576afdc0fd49559915d3d2f3a7a1ef
This commit is contained in:
Tim Down
2025-04-10 08:04:50 +00:00
committed by Copybot
parent 1741e48d59
commit 28468e134c
136 changed files with 428 additions and 4132 deletions
-128
View File
@@ -3267,27 +3267,6 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/runtime-corejs2": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.16.7.tgz",
"integrity": "sha512-ec0BM0J/9M5Cncha++AlgvvDlk+uM+m6f7K0t74ClcYzsE8LgX4RstRreksMSCI82o3LJS//UswmA0pUWkJpqg==",
"dev": true,
"dependencies": {
"core-js": "^2.6.5",
"regenerator-runtime": "^0.13.4"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/runtime-corejs2/node_modules/core-js": {
"version": "2.6.12",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
"deprecated": "core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.",
"dev": true,
"hasInstallScript": true
},
"node_modules/@babel/runtime-corejs3": {
"version": "7.16.8",
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.16.8.tgz",
@@ -12634,16 +12613,6 @@
"@types/react": "*"
}
},
"node_modules/@types/react-overlays": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@types/react-overlays/-/react-overlays-1.1.3.tgz",
"integrity": "sha512-oOq5NWbyfNz2w2sKvjkHdvGQSMA+VDVfI5UOfGPR0wkik2welad1RDVnVgH15jKf58jrZNBa1Ee4SVBgCGFxCg==",
"dev": true,
"dependencies": {
"@types/react": "*",
"@types/react-transition-group": "*"
}
},
"node_modules/@types/react-redux": {
"version": "7.1.33",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.33.tgz",
@@ -19802,15 +19771,6 @@
"utila": "~0.4"
}
},
"node_modules/dom-helpers": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
"integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
"dev": true,
"dependencies": {
"@babel/runtime": "^7.1.2"
}
},
"node_modules/dom-serializer": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz",
@@ -27577,12 +27537,6 @@
"node": ">=12.0.0"
}
},
"node_modules/keycode": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.1.tgz",
"integrity": "sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==",
"dev": true
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -34620,30 +34574,6 @@
"react": ">=16.4.1"
}
},
"node_modules/react-bootstrap": {
"version": "0.33.1",
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-0.33.1.tgz",
"integrity": "sha512-qWTRravSds87P8WC82tETy2yIso8qDqlIm0czsrduCaYAFtHuyLu0XDbUlfLXeRzqgwm5sRk2wRaTNoiVkk/YQ==",
"dev": true,
"dependencies": {
"@babel/runtime-corejs2": "^7.0.0",
"classnames": "^2.2.5",
"dom-helpers": "^3.2.0",
"invariant": "^2.2.4",
"keycode": "^2.2.0",
"prop-types": "^15.6.1",
"prop-types-extra": "^1.0.1",
"react-overlays": "^0.9.0",
"react-prop-types": "^0.4.0",
"react-transition-group": "^2.0.0",
"uncontrollable": "^7.0.2",
"warning": "^3.0.0"
},
"peerDependencies": {
"react": ">=16.3.0",
"react-dom": ">=16.3.0"
}
},
"node_modules/react-chartjs-2": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.0.1.tgz",
@@ -34849,36 +34779,6 @@
"uc.micro": "^1.0.1"
}
},
"node_modules/react-overlays": {
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-0.9.3.tgz",
"integrity": "sha512-u2T7nOLnK+Hrntho4p0Nxh+BsJl0bl4Xuwj/Y0a56xywLMetgAfyjnDVrudLXsNcKGaspoC+t3C1V80W9QQTdQ==",
"dev": true,
"dependencies": {
"classnames": "^2.2.5",
"dom-helpers": "^3.2.1",
"prop-types": "^15.5.10",
"prop-types-extra": "^1.0.1",
"react-transition-group": "^2.2.1",
"warning": "^3.0.0"
},
"peerDependencies": {
"react": ">=16.3.0",
"react-dom": ">=16.3.0"
}
},
"node_modules/react-prop-types": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/react-prop-types/-/react-prop-types-0.4.0.tgz",
"integrity": "sha1-+ZsL+0AGkpya8gUefBQUpcdbk9A=",
"dev": true,
"dependencies": {
"warning": "^3.0.0"
},
"peerDependencies": {
"react": ">=0.14.0"
}
},
"node_modules/react-proxy": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/react-proxy/-/react-proxy-1.1.8.tgz",
@@ -34989,22 +34889,6 @@
"react-proxy": "^1.1.7"
}
},
"node_modules/react-transition-group": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
"integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
"dev": true,
"dependencies": {
"dom-helpers": "^3.4.0",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2",
"react-lifecycles-compat": "^3.0.4"
},
"peerDependencies": {
"react": ">=15.0.0",
"react-dom": ">=15.0.0"
}
},
"node_modules/reactcss": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
@@ -40502,15 +40386,6 @@
"node": ">=6.0.0"
}
},
"node_modules/warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
"integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
"dev": true,
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/watchpack": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz",
@@ -44482,7 +44357,6 @@
"@types/react-dom": "^17.0.13",
"@types/react-google-recaptcha": "^2.1.5",
"@types/react-linkify": "^1.0.0",
"@types/react-overlays": "^1.1.3",
"@types/recurly__recurly-js": "^4.22.0",
"@types/sinon-chai": "^3.2.8",
"@types/uuid": "^9.0.8",
@@ -44570,7 +44444,6 @@
"prop-types": "^15.7.2",
"qrcode": "^1.4.4",
"react": "^17.0.2",
"react-bootstrap": "^0.33.1",
"react-bootstrap-5": "npm:react-bootstrap@^2.10.5",
"react-chartjs-2": "^5.0.1",
"react-color": "^2.19.3",
@@ -44581,7 +44454,6 @@
"react-google-recaptcha": "^3.1.0",
"react-i18next": "^13.3.1",
"react-linkify": "^1.0.0-alpha",
"react-overlays": "^0.9.3",
"react-refresh": "^0.14.0",
"react-resizable-panels": "^2.1.1",
"resolve-url-loader": "^5.0.0",
+7 -31
View File
@@ -9,14 +9,10 @@ import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
// @ts-ignore
import en from '../../../services/web/locales/en.json'
import { bootstrapVersionArg } from './utils/with-bootstrap-switcher'
function resetMeta(bootstrapVersion?: 3 | 5) {
function resetMeta() {
window.metaAttributesCache = new Map()
window.metaAttributesCache.set('ol-i18n', { currentLangCode: 'en' })
if (bootstrapVersion) {
window.metaAttributesCache.set('ol-bootstrapVersion', bootstrapVersion)
}
window.metaAttributesCache.set('ol-ExposedSettings', {
adminEmail: 'placeholder@example.com',
appName: 'Overleaf',
@@ -126,8 +122,6 @@ const preview: Preview = {
// render stories in iframes, to isolate modals
inlineStories: false,
},
// Default to Bootstrap 5 styles
bootstrap5: true,
},
globalTypes: {
theme: {
@@ -144,17 +138,9 @@ const preview: Preview = {
},
},
loaders: [
async ({ globals }) => {
const { theme } = globals
async () => {
return {
// NOTE: this uses `${theme}style.less` rather than `${theme}.less`
// so that webpack only bundles files ending with "style.less"
bootstrap3Style: await import(
`!!to-string-loader!css-loader!less-loader!../../../services/web/frontend/stylesheets/${theme}style.less`
),
// Themes are applied differently in Bootstrap 5 code
bootstrap5Style: await import(
mainStyle: await import(
// @ts-ignore
`!!to-string-loader!css-loader!resolve-url-loader!sass-loader!../../../services/web/frontend/stylesheets/bootstrap-5/main-style.scss`
),
@@ -163,15 +149,9 @@ const preview: Preview = {
],
decorators: [
(Story, context) => {
const { bootstrap3Style, bootstrap5Style } = context.loaded
const bootstrapVersion = Number(
context.args[bootstrapVersionArg] ||
(context.parameters.bootstrap5 === false ? 3 : 5)
) as 3 | 5
const activeStyle =
bootstrapVersion === 3 ? bootstrap3Style : bootstrap5Style
const { mainStyle } = context.loaded
resetMeta(bootstrapVersion)
resetMeta()
return (
<div
@@ -179,12 +159,8 @@ const preview: Preview = {
context.globals.theme === 'main-light-' ? 'light' : 'default'
}
>
{activeStyle && <style>{activeStyle.default}</style>}
<Story
{...context}
// force re-renders when switching between Bootstrap versions
key={bootstrapVersion}
/>
{mainStyle && <style>{mainStyle.default}</style>}
<Story {...context} />
</div>
)
},
@@ -1,20 +0,0 @@
import { Meta } from '@storybook/react'
export const bootstrapVersionArg = 'bootstrapVersion'
export const bsVersionDecorator: Meta = {
argTypes: {
[bootstrapVersionArg]: {
name: 'Bootstrap Version',
description: 'Bootstrap version for components',
control: { type: 'inline-radio' },
options: ['3', '5'],
table: {
defaultValue: { summary: '5' },
},
},
},
args: {
[bootstrapVersionArg]: '5',
},
}
@@ -10,7 +10,6 @@ export function resetMeta() {
hasLinkedProjectOutputFileFeature: true,
hasLinkUrlFeature: true,
})
window.metaAttributesCache.set('ol-bootstrapVersion', 5)
}
// Populate meta for top-level access in modules on import
@@ -19,20 +19,11 @@ function FileTreeContextMenu() {
toggleButtonRef.current = document.querySelector(
'.entity-menu-toggle'
) as HTMLButtonElement | null
focusContextMenu()
}
}, [contextMenuCoords])
if (!contextMenuCoords || fileTreeReadOnly) return null
// A11y - Move the focus to the context menu when it opens
function focusContextMenu() {
const BS3contextMenu = document.querySelector(
'[aria-labelledby="dropdown-file-tree-context-menu"]'
) as HTMLElement | null
BS3contextMenu?.focus()
}
function close() {
setContextMenuCoords(null)
if (toggleButtonRef.current) {
@@ -79,14 +70,4 @@ function FileTreeContextMenu() {
)
}
// fake component required as Dropdowns require a Toggle, even tho we don't want
// one for the context menu
const FakeDropDownToggle = React.forwardRef<undefined, { bsRole: string }>(
({ bsRole }, ref) => {
return null
}
)
FakeDropDownToggle.displayName = 'FakeDropDownToggle'
export default FileTreeContextMenu
@@ -11,10 +11,7 @@ function ChangeList() {
<div className="history-header history-toggle-switch-container">
<ToggleSwitch labelsOnly={labelsOnly} setLabelsOnly={setLabelsOnly} />
</div>
<div
className="history-version-list-container"
data-history-version-list-container
>
<div className="history-version-list-container">
{labelsOnly ? <LabelsList /> : <AllHistoryList />}
</div>
</aside>
@@ -3,26 +3,20 @@ import {
Dropdown,
DropdownMenu,
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
import BS5DropdownToggleWithTooltip from '@/features/ui/components/bootstrap-5/dropdown-toggle-with-tooltip'
import DropdownToggleWithTooltip from '@/features/ui/components/bootstrap-5/dropdown-toggle-with-tooltip'
type ActionDropdownProps = {
id: string
children: React.ReactNode
parentSelector?: string
isOpened: boolean
iconTag: ReactNode
toolTipDescription: string
setIsOpened: (isOpened: boolean) => void
}
function BS5ActionsDropdown({
id,
children,
isOpened,
iconTag,
setIsOpened,
toolTipDescription,
}: Omit<ActionDropdownProps, 'parentSelector'>) {
function ActionsDropdown(props: ActionDropdownProps) {
const { id, children, isOpened, iconTag, setIsOpened, toolTipDescription } =
props
return (
<Dropdown
align="end"
@@ -30,7 +24,7 @@ function BS5ActionsDropdown({
show={isOpened}
onToggle={open => setIsOpened(open)}
>
<BS5DropdownToggleWithTooltip
<DropdownToggleWithTooltip
id={`history-version-dropdown-${id}`}
className="history-version-dropdown-menu-btn"
aria-label={toolTipDescription}
@@ -39,7 +33,7 @@ function BS5ActionsDropdown({
tooltipProps={{ hidden: isOpened }}
>
{iconTag}
</BS5DropdownToggleWithTooltip>
</DropdownToggleWithTooltip>
<DropdownMenu className="history-version-dropdown-menu">
{children}
</DropdownMenu>
@@ -47,8 +41,4 @@ function BS5ActionsDropdown({
)
}
function ActionsDropdown(props: ActionDropdownProps) {
return <BS5ActionsDropdown {...props} />
}
export default ActionsDropdown
@@ -21,7 +21,6 @@ function CompareVersionDropdown({
id={id}
isOpened={isOpened}
setIsOpened={setIsOpened}
parentSelector="[data-history-version-list-container]"
toolTipDescription={t('compare')}
iconTag={
<MaterialIcon
@@ -25,7 +25,6 @@ function HistoryDropdown({
iconTag={
<MaterialIcon type="more_vert" accessibilityLabel={t('more_actions')} />
}
parentSelector="[data-history-version-list-container]"
>
{children}
</ActionsDropdown>
@@ -1,5 +1,4 @@
import { useTranslation, Trans } from 'react-i18next'
import AccessibleModal from '../../../../../../shared/components/accessible-modal'
import { MergeAndOverride } from '../../../../../../../../types/utils'
import OLButton from '@/features/ui/components/ol/ol-button'
import OLModal, {
@@ -11,7 +10,7 @@ import OLModal, {
import { type UserEmailData } from '../../../../../../../../types/user-email'
type ConfirmationModalProps = MergeAndOverride<
React.ComponentProps<typeof AccessibleModal>,
React.ComponentProps<typeof OLModal>,
{
email: string
isConfirmDisabled: boolean
@@ -4,19 +4,16 @@ import { Institution } from '../../../../../../../types/institution'
type ReconfirmationInfoPromptTextProps = {
primary: boolean
institutionName: Institution['name']
icon?: React.ReactElement // BS3 only
}
function ReconfirmationInfoPromptText({
primary,
institutionName,
icon,
}: ReconfirmationInfoPromptTextProps) {
const { t } = useTranslation()
return (
<>
{icon}
<Trans
i18nKey="are_you_still_at"
values={{
@@ -3,8 +3,6 @@ import { useTranslation } from 'react-i18next'
import { useLocation } from '@/shared/hooks/use-location'
import MaterialIcon from '@/shared/components/material-icon'
import OLTag from '@/features/ui/components/ol/ol-tag'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { bsVersion } from '@/features/utils/bootstrap-5'
import { ManagedGroupSubscription } from '../../../../../../types/subscription/dashboard/subscription'
import { sendMB } from '../../../../infrastructure/event-tracking'
import starIcon from '../../images/star-gradient.svg'
@@ -24,7 +22,7 @@ function AvailableWithGroupProfessionalBadge() {
<OLTag
prepend={<img aria-hidden="true" src={starIcon} alt="" />}
contentProps={{
className: bsVersion({ bs5: 'mw-100' }),
className: 'mw-100',
onClick: handleUpgradeClick,
}}
>
@@ -82,35 +80,17 @@ export function GroupSettingsButtonWithAdBadge({
useGroupSettingsButton(subscription)
return (
<BootstrapVersionSwitcher
bs3={
<div className="row-link text-muted">
<div className="icon">
<MaterialIcon type="settings" />
</div>
<div className="text">
<div className="heading">{heading}</div>
<div className="subtext">{groupSettingRowSubText}</div>
</div>
<span>
<AvailableWithGroupProfessionalBadge />
</span>
<li className="list-group-item row-link">
<div className="row-link-inner">
<MaterialIcon type="settings" className="p-2 p-md-3 text-muted" />
<div className="flex-grow-1 text-truncate text-muted">
<strong>{heading}</strong>
<div className="text-truncate">{groupSettingRowSubText}</div>
</div>
}
bs5={
<li className="list-group-item row-link">
<div className="row-link-inner">
<MaterialIcon type="settings" className="p-2 p-md-3 text-muted" />
<div className="flex-grow-1 text-truncate text-muted">
<strong>{heading}</strong>
<div className="text-truncate">{groupSettingRowSubText}</div>
</div>
<span className="p-2 p-md-3">
<AvailableWithGroupProfessionalBadge />
</span>
</div>
</li>
}
/>
<span className="p-2 p-md-3">
<AvailableWithGroupProfessionalBadge />
</span>
</div>
</li>
)
}
@@ -71,11 +71,6 @@ export default function LeaveGroupModal() {
disabled={inflight}
isLoading={inflight}
loadingLabel={t('processing_uppercase') + '…'}
bs3Props={{
loading: inflight
? t('processing_uppercase') + '…'
: t('leave_now'),
}}
>
{t('leave_now')}
</OLButton>
@@ -7,8 +7,6 @@ import { Trans, useTranslation } from 'react-i18next'
import { useSubscriptionDashboardContext } from '../../context/subscription-dashboard-context'
import { RowLink } from './row-link'
import { ManagedGroupSubscription } from '../../../../../../types/subscription/dashboard/subscription'
import { bsVersion } from '@/features/utils/bootstrap-5'
import classnames from 'classnames'
function ManagedGroupAdministrator({
subscription,
@@ -104,9 +102,7 @@ export default function ManagedGroupSubscriptions() {
{managedGroupSubscriptions.map(subscription => {
return (
<div key={`managed-group-${subscription._id}`}>
<h2 className={classnames('h3', bsVersion({ bs5: 'fw-bold' }))}>
{t('group_management')}
</h2>
<h2 className="h3 fw-bold">{t('group_management')}</h2>
<p>
<ManagedGroupAdministrator subscription={subscription} />
</p>
@@ -51,9 +51,6 @@ function PersonalSubscriptionRecurlySyncEmail() {
disabled={isLoading}
isLoading={isLoading}
loadingLabel={t('updating')}
bs3Props={{
loading: isLoading ? t('updating') + '…' : t('update'),
}}
>
{t('update')}
</OLButton>
@@ -1,5 +1,4 @@
import MaterialIcon from '../../../../shared/components/material-icon'
import { isBootstrap5 } from '@/features/utils/bootstrap-5'
type RowLinkProps = {
href: string
@@ -8,28 +7,7 @@ type RowLinkProps = {
icon: string
}
export function RowLink(props: RowLinkProps) {
return isBootstrap5() ? <BS5RowLink {...props} /> : <BS3RowLink {...props} />
}
function BS3RowLink({ href, heading, subtext, icon }: RowLinkProps) {
return (
<a href={href} className="row-link">
<div className="icon">
<MaterialIcon type={icon} />
</div>
<div className="text">
<div className="heading">{heading}</div>
<div className="subtext">{subtext}</div>
</div>
<div className="icon arrow">
<MaterialIcon type="keyboard_arrow_right" />
</div>
</a>
)
}
function BS5RowLink({ href, heading, subtext, icon }: RowLinkProps) {
export function RowLink({ href, heading, subtext, icon }: RowLinkProps) {
return (
<li className="list-group-item row-link">
<a href={href} className="row-link-inner">
@@ -12,7 +12,6 @@ import { ChangeToGroupModal } from './change-plan/modals/change-to-group-modal'
import { CancelAiAddOnModal } from '@/features/subscription/components/dashboard/states/active/change-plan/modals/cancel-ai-add-on-modal'
import OLButton from '@/features/ui/components/ol/ol-button'
import isInFreeTrial from '../../../../util/is-in-free-trial'
import { bsVersion } from '@/features/utils/bootstrap-5'
import AddOns from '@/features/subscription/components/dashboard/states/active/add-ons'
import {
AI_ADD_ON_CODE,
@@ -20,7 +19,6 @@ import {
isStandaloneAiPlanCode,
} from '@/features/subscription/data/add-on-codes'
import getMeta from '@/utils/meta'
import classnames from 'classnames'
import SubscriptionRemainder from '@/features/subscription/components/dashboard/states/active/subscription-remainder'
import { sendMB } from '../../../../../../infrastructure/event-tracking'
import PauseSubscriptionModal from '@/features/subscription/components/dashboard/pause-modal'
@@ -100,9 +98,7 @@ export function ActiveSubscriptionNew({
/>
)}
</div>
<h2 className={classnames('h3', bsVersion({ bs5: 'fw-bold' }))}>
{t('billing')}
</h2>
<h2 className="h3 fw-bold">{t('billing')}</h2>
<p className="mb-1">
{subscription.plan.annual ? (
<Trans
@@ -169,12 +165,8 @@ export function ActiveSubscriptionNew({
)}
</div>
<hr />
<h2 className={classnames('h3', bsVersion({ bs5: 'fw-bold' }))}>
{t('plan')}
</h2>
<h3 className={classnames('h5 mt-0 mb-1', bsVersion({ bs5: 'fw-bold' }))}>
{planName}
</h3>
<h2 className="h3 fw-bold">{t('plan')}</h2>
<h3 className="h5 mt-0 mb-1 fw-bold">{planName}</h3>
{subscription.pendingPlan &&
subscription.pendingPlan.name !== subscription.plan.name && (
<p className="mb-1">{t('want_change_to_apply_before_plan_end')}</p>
@@ -41,9 +41,6 @@ export default function DowngradePlanButton({
disabled={isButtonDisabled}
isLoading={isLoading}
loadingLabel={t('processing_uppercase') + '…'}
bs3Props={{
loading: isLoading ? t('processing_uppercase') + '…' : buttonText,
}}
>
{buttonText}
</OLButton>
@@ -34,9 +34,6 @@ export default function ExtendTrialButton({
disabled={isButtonDisabled}
isLoading={isLoading}
loadingLabel={t('processing_uppercase') + '…'}
bs3Props={{
loading: isLoading ? t('processing_uppercase') + '…' : buttonText,
}}
>
{buttonText}
</OLButton>
@@ -107,11 +107,6 @@ export function CancelAiAddOnModal() {
isLoading={inflight}
loadingLabel={t('processing_uppercase') + '…'}
onClick={handleConfirmChange}
bs3Props={{
loading: inflight
? t('processing_uppercase') + '…'
: t('cancel_add_on'),
}}
>
{t('cancel_add_on')}
</OLButton>
@@ -22,9 +22,7 @@ import OLFormCheckbox from '@/features/ui/components/ol/ol-form-checkbox'
import { useContactUsModal } from '@/shared/hooks/use-contact-us-modal'
import { UserProvider } from '@/shared/context/user-context'
import OLButton from '@/features/ui/components/ol/ol-button'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import OLNotification from '@/features/ui/components/ol/ol-notification'
import { bsVersion } from '@/features/utils/bootstrap-5'
const educationalPercentDiscount = 40
@@ -79,8 +77,6 @@ function GroupPrice({
})}
</span>
<BootstrapVersionSwitcher bs3={<br />} />
<span className="circle-subtext">
<span aria-hidden>
{t('x_price_per_user', {
@@ -221,10 +217,7 @@ export function ChangeToGroupModal() {
<fieldset className="form-group">
<legend className="legend-as-label">{t('plan')}</legend>
{groupPlans.plans.map(option => (
<div
className={bsVersion({ bs3: 'radio' })}
key={option.code}
>
<div key={option.code}>
<OLFormCheckbox
type="radio"
name="plan-code"
@@ -333,11 +326,6 @@ export function ChangeToGroupModal() {
onClick={upgrade}
isLoading={inflight}
loadingLabel={t('processing_uppercase') + '…'}
bs3Props={{
loading: inflight
? t('processing_uppercase') + '…'
: t('upgrade_now'),
}}
>
{t('upgrade_now')}
</OLButton>
@@ -112,11 +112,6 @@ export function ConfirmChangePlanModal() {
isLoading={inflight}
loadingLabel={t('processing_uppercase') + '…'}
onClick={handleConfirmChange}
bs3Props={{
loading: inflight
? t('processing_uppercase') + '…'
: t('change_plan'),
}}
>
{t('change_plan')}
</OLButton>
@@ -93,11 +93,6 @@ export function KeepCurrentPlanModal() {
isLoading={inflight}
loadingLabel={t('processing_uppercase') + '…'}
onClick={confirmCancelPendingPlanChange}
bs3Props={{
loading: inflight
? t('processing_uppercase') + '…'
: t('revert_pending_plan_change'),
}}
>
{t('revert_pending_plan_change')}
</OLButton>
@@ -1,71 +0,0 @@
import { forwardRef, SyntheticEvent } from 'react'
import classnames from 'classnames'
import RootCloseWrapper from 'react-overlays/lib/RootCloseWrapper'
import { DropdownProps } from 'react-bootstrap'
import { MergeAndOverride } from '../../../../../../types/utils'
type DropdownMenuWithRefProps = MergeAndOverride<
Pick<DropdownProps, 'bsClass' | 'open' | 'pullRight' | 'onClose'>,
{
children: React.ReactNode
bsRole: 'menu'
menuRef: React.MutableRefObject<HTMLElement | undefined>
className?: string
// The props below are passed by react-bootstrap
labelledBy?: string | undefined
rootCloseEvent?: 'click' | 'mousedown' | undefined
onClose?: (e: SyntheticEvent<any>) => void
}
>
const DropdownMenuWithRef = forwardRef<
HTMLUListElement,
DropdownMenuWithRefProps
>(function (props, ref) {
const {
children,
bsRole,
bsClass,
className,
open,
pullRight,
labelledBy,
menuRef,
onClose,
rootCloseEvent,
...rest
} = props
// expose the menu reference to both the `menuRef` and `ref callback` from react-bootstrap
const handleRefs = (node: HTMLUListElement) => {
if (typeof ref === 'function') {
ref(node)
}
menuRef.current = node
}
// Implementation as suggested in
// https://react-bootstrap-v3.netlify.app/components/dropdowns/#btn-dropdowns-props-dropdown
return (
<RootCloseWrapper
disabled={!open}
onRootClose={onClose}
event={rootCloseEvent}
>
<ul
role={bsRole}
className={classnames(className, bsClass, {
'dropdown-menu-right': pullRight,
})}
aria-labelledby={labelledBy}
ref={handleRefs}
{...rest}
>
{children}
</ul>
</RootCloseWrapper>
)
})
DropdownMenuWithRef.displayName = 'DropdownMenuWithRef'
export default DropdownMenuWithRef
@@ -1,53 +0,0 @@
import { forwardRef } from 'react'
import Tooltip from '../../../../shared/components/tooltip'
import classnames from 'classnames'
import { DropdownProps } from 'react-bootstrap'
import { MergeAndOverride } from '../../../../../../types/utils'
type CustomToggleProps = MergeAndOverride<
Pick<DropdownProps, 'bsClass' | 'open'>,
{
children: React.ReactNode
isOpened: boolean
bsRole: 'toggle'
className?: string
tooltipProps: Omit<React.ComponentProps<typeof Tooltip>, 'children'>
}
>
const DropdownToggleWithTooltip = forwardRef<
HTMLButtonElement,
CustomToggleProps
>(function (props, ref) {
const {
tooltipProps,
isOpened,
children,
bsClass,
className,
open,
bsRole: _bsRole,
...rest
} = props
const button = (
<button
type="button"
ref={ref}
className={classnames(bsClass, 'btn', className)}
aria-expanded={open}
aria-haspopup="true"
{...rest}
>
{children}
</button>
)
return (
<>{isOpened ? button : <Tooltip {...tooltipProps}>{button}</Tooltip>}</>
)
})
DropdownToggleWithTooltip.displayName = 'DropdownToggleWithTooltip'
export default DropdownToggleWithTooltip
@@ -1,21 +0,0 @@
import {
FormControl as BS3FormControl,
FormControlProps as BS3FormControlProps,
} from 'react-bootstrap'
type FormControlProps = BS3FormControlProps & {
prepend?: React.ReactNode
append?: React.ReactNode
}
function FormControl({ prepend, append, ...props }: FormControlProps) {
return (
<>
{prepend && <div className="form-control-feedback-left">{prepend}</div>}
<BS3FormControl {...props} />
{append && <div className="form-control-feedback">{append}</div>}
</>
)
}
export default FormControl
@@ -1,14 +0,0 @@
import classNames from 'classnames'
import { FC, HTMLProps } from 'react'
export const ToastContainer: FC<HTMLProps<HTMLDivElement>> = ({
children,
className,
...props
}) => {
return (
<div className={classNames('toast-container', className)} {...props}>
{children}
</div>
)
}
@@ -1,51 +0,0 @@
import classNames from 'classnames'
import { FC, useCallback, useEffect, useRef } from 'react'
type ToastProps = {
onClose?: () => void
onExited?: () => void
autohide?: boolean
delay?: number
show: boolean
className?: string
}
export const Toast: FC<ToastProps> = ({
children,
delay = 5000,
onClose,
onExited,
autohide,
show,
className,
}) => {
const delayRef = useRef(delay)
const onCloseRef = useRef(onClose)
const onExitedRef = useRef(onExited)
const shouldAutoHide = Boolean(autohide && show)
const handleTimeout = useCallback(() => {
if (shouldAutoHide) {
onCloseRef.current?.()
onExitedRef.current?.()
}
}, [shouldAutoHide])
useEffect(() => {
const timeout = window.setTimeout(handleTimeout, delayRef.current)
return () => window.clearTimeout(timeout)
}, [handleTimeout])
if (!show) {
return null
}
return (
<div
className={classNames('toast', show ? 'show' : 'hide', className)}
aria-live="assertive"
role="alert"
>
{children}
</div>
)
}
@@ -1,99 +0,0 @@
import {
Children,
cloneElement,
isValidElement,
useState,
useEffect,
} from 'react'
import {
ToggleButtonGroup as BS3ToggleButtonGroup,
ToggleButtonGroupProps as BS3ToggleButtonGroupProps,
ToggleButtonProps as BS3ToggleButtonProps,
} from 'react-bootstrap'
function ToggleButtonGroup<T extends string | number>({
children,
value,
defaultValue,
onChange,
...props
}: BS3ToggleButtonGroupProps) {
const [selectedValue, setSelectedValue] = useState<T | T[] | null>(
defaultValue || (props.type === 'checkbox' ? [] : null)
)
const isControlled = value !== undefined
useEffect(() => {
if (isControlled) {
if (props.type === 'radio') {
setSelectedValue(value)
} else {
if (Array.isArray(value)) {
setSelectedValue(Array.from(value))
} else {
setSelectedValue([value])
}
}
}
}, [isControlled, value, props.type])
const handleButtonClick = (buttonValue: T) => {
if (props.type === 'radio') {
if (!isControlled) {
setSelectedValue(buttonValue)
}
onChange?.(buttonValue as any)
} else if (props.type === 'checkbox') {
const newValue = Array.isArray(selectedValue)
? selectedValue.includes(buttonValue)
? selectedValue.filter(val => val !== buttonValue) // Deselect
: [...selectedValue, buttonValue] // Select
: [buttonValue] // Initial selection if value is not array yet
if (!isControlled) {
setSelectedValue(newValue)
}
onChange?.(newValue)
}
}
// Clone children and add custom onClick handlers
const modifiedChildren = Children.map(children, child => {
if (isValidElement(child)) {
const childElement = child as React.ReactElement<
BS3ToggleButtonProps & { active?: boolean }
>
const isActive =
props.type === 'radio'
? selectedValue === childElement.props.value
: Array.isArray(selectedValue) &&
selectedValue.includes(childElement.props.value as T)
return cloneElement(childElement, {
onClick: () => {
handleButtonClick(childElement.props.value as T)
},
active: isActive,
})
}
return child
})
return (
<BS3ToggleButtonGroup
{...props}
value={isControlled ? value : undefined}
defaultValue={defaultValue}
// Ignore the broken onChange handler
onChange={() => {}}
>
{modifiedChildren}
</BS3ToggleButtonGroup>
)
}
export default ToggleButtonGroup
@@ -1,15 +0,0 @@
import { isBootstrap5 } from '@/features/utils/bootstrap-5'
type BootstrapVersionSwitcherProps = {
bs3?: React.ReactNode
bs5?: React.ReactNode
}
function BootstrapVersionSwitcher({
bs3,
bs5,
}: BootstrapVersionSwitcherProps): React.ReactElement {
return <>{isBootstrap5() ? bs5 : bs3}</>
}
export default BootstrapVersionSwitcher
@@ -1,12 +1,5 @@
import Icon from '@/shared/components/icon'
import MaterialIcon from '@/shared/components/material-icon'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
export default function OLTagIcon() {
return (
<BootstrapVersionSwitcher
bs3={<Icon type="tag" fw />}
bs5={<MaterialIcon type="sell" />}
/>
)
return <MaterialIcon type="sell" />
}
@@ -1,40 +1,7 @@
import { Label } from 'react-bootstrap'
import Badge from '@/features/ui/components/bootstrap-5/badge'
import BS3Badge from '@/shared/components/badge'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
type OLBadgeProps = React.ComponentProps<typeof Badge> & {
bs3Props?: {
bsStyle?: React.ComponentProps<typeof Label>['bsStyle'] | null
}
}
function OLBadge(props: OLBadgeProps) {
const { bs3Props, ...rest } = props
let bs3BadgeProps: React.ComponentProps<typeof BS3Badge> = {
prepend: rest.prepend,
children: rest.children,
className: rest.className,
bsStyle: rest.bg,
}
if (bs3Props) {
const { bsStyle, ...restBs3Props } = bs3Props
bs3BadgeProps = {
...bs3BadgeProps,
...restBs3Props,
bsStyle: 'bsStyle' in bs3Props ? bsStyle : rest.bg,
}
}
return (
<BootstrapVersionSwitcher
bs3={<BS3Badge {...bs3BadgeProps} />}
bs5={<Badge {...rest} />}
/>
)
function OLBadge(props: React.ComponentProps<typeof Badge>) {
return <Badge {...props} />
}
export default OLBadge
@@ -1,30 +1,7 @@
import { ButtonGroup, ButtonGroupProps } from 'react-bootstrap-5'
import {
ButtonGroup as BS3ButtonGroup,
ButtonGroupProps as BS3ButtonGroupProps,
} from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
type OLButtonGroupProps = ButtonGroupProps & {
bs3Props?: Record<string, unknown>
}
function OLButtonGroup({ bs3Props, as, ...rest }: OLButtonGroupProps) {
const bs3ButtonGroupProps: BS3ButtonGroupProps = {
children: rest.children,
className: rest.className,
vertical: rest.vertical,
...getAriaAndDataProps(rest),
...bs3Props,
}
return (
<BootstrapVersionSwitcher
bs3={<BS3ButtonGroup {...bs3ButtonGroupProps} />}
bs5={<ButtonGroup {...rest} as={as} />}
/>
)
function OLButtonGroup({ as, ...rest }: ButtonGroupProps) {
return <ButtonGroup {...rest} as={as} />
}
export default OLButtonGroup
@@ -1,31 +1,7 @@
import { ButtonToolbar, ButtonToolbarProps } from 'react-bootstrap-5'
import {
ButtonToolbar as BS3ButtonToolbar,
ButtonToolbarProps as BS3ButtonToolbarProps,
} from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
type OLButtonToolbarProps = ButtonToolbarProps & {
bs3Props?: Record<string, unknown>
}
function OLButtonToolbar(props: OLButtonToolbarProps) {
const { bs3Props, ...rest } = props
const bs3ButtonToolbarProps: BS3ButtonToolbarProps = {
children: rest.children,
className: rest.className,
...getAriaAndDataProps(rest),
...bs3Props,
}
return (
<BootstrapVersionSwitcher
bs3={<BS3ButtonToolbar {...bs3ButtonToolbarProps} />}
bs5={<ButtonToolbar {...rest} />}
/>
)
function OLButtonToolbar(props: ButtonToolbarProps) {
return <ButtonToolbar {...props} />
}
export default OLButtonToolbar
@@ -1,132 +1,13 @@
import { forwardRef } from 'react'
import BootstrapVersionSwitcher from '../bootstrap-5/bootstrap-version-switcher'
import { Button as BS3Button } from 'react-bootstrap'
import type { ButtonProps } from '@/features/ui/components/types/button-props'
import type { ButtonProps as BS3ButtonPropsBase } from 'react-bootstrap'
import Button from '../bootstrap-5/button'
import classnames from 'classnames'
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
import { callFnsInSequence } from '@/utils/functions'
import Icon from '@/shared/components/icon'
export type BS3ButtonSize = 'xsmall' | 'sm' | 'medium' | 'lg'
export type OLButtonProps = ButtonProps
export type OLButtonProps = ButtonProps & {
bs3Props?: {
loading?: React.ReactNode
bsSize?: BS3ButtonSize
block?: boolean
className?: string
onMouseOver?: React.MouseEventHandler<HTMLButtonElement>
onMouseOut?: React.MouseEventHandler<HTMLButtonElement>
onFocus?: React.FocusEventHandler<HTMLButtonElement>
onBlur?: React.FocusEventHandler<HTMLButtonElement>
}
}
const OLButton = forwardRef<HTMLButtonElement, OLButtonProps>((props, ref) => {
return <Button {...props} ref={ref} />
})
// Resolve type mismatch of the onClick event handler
export type BS3ButtonProps = Omit<BS3ButtonPropsBase, 'onClick'> & {
onClick?: React.MouseEventHandler<any>
}
export function bs3ButtonProps(props: ButtonProps) {
const bs3ButtonProps: BS3ButtonProps = {
bsStyle: null,
bsSize: props.size,
className: classnames(`btn-${props.variant || 'primary'}`, props.className),
disabled: props.isLoading || props.disabled,
form: props.form,
href: props.href,
id: props.id,
target: props.target,
rel: props.rel,
onClick: props.onClick,
onMouseDown: props.onMouseDown as BS3ButtonProps['onMouseDown'],
type: props.type,
draggable: props.draggable,
download: props.download,
style: props.style,
active: props.active,
}
return bs3ButtonProps
}
function BS3ButtonContent({
children,
leadingIcon,
trailingIcon,
}: {
children: React.ReactNode
leadingIcon: OLButtonProps['leadingIcon']
trailingIcon: OLButtonProps['trailingIcon']
}) {
const leadingIconComponent =
leadingIcon && typeof leadingIcon === 'string' ? (
<Icon type={leadingIcon} />
) : (
leadingIcon
)
const trailingIconComponent =
trailingIcon && typeof trailingIcon === 'string' ? (
<Icon type={trailingIcon} />
) : (
trailingIcon
)
return (
<>
{leadingIconComponent ? <>{leadingIconComponent}&nbsp;</> : null}
{children}
{trailingIconComponent ? <>&nbsp;{trailingIconComponent}</> : null}
</>
)
}
const OLButton = forwardRef<HTMLButtonElement, OLButtonProps>(
({ bs3Props = {}, ...rest }, ref) => {
const { className: _, ...restBs3Props } = bs3Props
// BS3 OverlayTrigger automatically provides 'onMouseOver', 'onMouseOut', 'onFocus', 'onBlur' event handlers
const bs3FinalProps = {
...restBs3Props,
onMouseOver: callFnsInSequence(bs3Props?.onMouseOver, rest.onMouseOver),
onMouseOut: callFnsInSequence(bs3Props?.onMouseOut, rest.onMouseOut),
onFocus: callFnsInSequence(bs3Props?.onFocus, rest.onFocus),
onBlur: callFnsInSequence(bs3Props?.onBlur, rest.onBlur),
}
// Get all `aria-*` and `data-*` attributes
const extraProps = getAriaAndDataProps(rest)
return (
<BootstrapVersionSwitcher
bs3={
<BS3Button
{...bs3ButtonProps({
...rest,
// Override the `className` with bs3 specific className (if provided)
className: bs3Props?.className ?? rest.className,
})}
{...extraProps}
{...bs3FinalProps}
ref={ref as React.LegacyRef<any> | undefined}
>
{bs3Props?.loading || (
<BS3ButtonContent
leadingIcon={rest.leadingIcon}
trailingIcon={rest.trailingIcon}
>
{rest.children}
</BS3ButtonContent>
)}
</BS3Button>
}
bs5={<Button {...rest} ref={ref} />}
/>
)
}
)
OLButton.displayName = 'OLButton'
export default OLButton
@@ -1,18 +1,11 @@
import { Card } from 'react-bootstrap-5'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { FC } from 'react'
import classNames from 'classnames'
const OLCard: FC<{ className?: string }> = ({ children, className }) => {
return (
<BootstrapVersionSwitcher
bs3={<div className={classNames('card', className)}>{children}</div>}
bs5={
<Card className={className}>
<Card.Body>{children}</Card.Body>
</Card>
}
/>
<Card className={className}>
<Card.Body>{children}</Card.Body>
</Card>
)
}
@@ -1,6 +1,4 @@
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { CloseButton, CloseButtonProps } from 'react-bootstrap-5'
import classNames from 'classnames'
import { useTranslation } from 'react-i18next'
import { forwardRef } from 'react'
@@ -8,25 +6,7 @@ const OLCloseButton = forwardRef<HTMLButtonElement, CloseButtonProps>(
(props, ref) => {
const { t } = useTranslation()
const bs3CloseButtonProps: React.ButtonHTMLAttributes<HTMLButtonElement> = {
className: classNames('close', props.className),
onClick: props.onClick,
onMouseOver: props.onMouseOver,
onMouseOut: props.onMouseOut,
'aria-label': t('close'),
}
return (
<BootstrapVersionSwitcher
bs3={
<button {...bs3CloseButtonProps}>
<span aria-hidden="true">&times;</span>
</button>
}
bs5={<CloseButton ref={ref} {...props} />}
/>
)
return <CloseButton ref={ref} aria-label={t('close')} {...props} />
}
)
@@ -1,73 +1,7 @@
import { Col } from 'react-bootstrap-5'
import { Col as BS3Col } from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
type OLColProps = React.ComponentProps<typeof Col> & {
bs3Props?: Record<string, unknown>
}
function OLCol(props: OLColProps) {
const { bs3Props, ...rest } = props
const getBs3Sizes = (obj: typeof rest) => {
const bs5ToBs3SizeMap = {
xs: 'xs',
sm: 'xs',
md: 'sm',
lg: 'md',
xl: 'lg',
xxl: undefined,
} as const
const isBs5ToBs3SizeMapKey = (
key: string
): key is keyof typeof bs5ToBs3SizeMap => {
return key in bs5ToBs3SizeMap
}
const sizes = Object.entries(obj).reduce(
(prev, [key, value]) => {
if (isBs5ToBs3SizeMapKey(key)) {
const bs3Size = bs5ToBs3SizeMap[key]
if (bs3Size) {
if (typeof value === 'object') {
prev[bs3Size] = value.span
prev[`${bs3Size}Offset`] = value.offset
} else {
prev[bs3Size] = value
}
}
}
return prev
},
{} as Record<string, (typeof rest)['xs']>
)
// Add a default sizing for `col-xs-12` if no sizing is available
if (
!Object.keys(sizes).some(key => ['xs', 'sm', 'md', 'lg'].includes(key))
) {
sizes.xs = 12
}
return sizes
}
const bs3ColProps: React.ComponentProps<typeof BS3Col> = {
children: rest.children,
className: rest.className,
...getBs3Sizes(rest),
...bs3Props,
}
return (
<BootstrapVersionSwitcher
bs3={<BS3Col {...bs3ColProps} />}
bs5={<Col {...rest} />}
/>
)
function OLCol(props: React.ComponentProps<typeof Col>) {
return <Col {...props} />
}
export default OLCol
@@ -1,43 +1,13 @@
import { MenuItem, MenuItemProps } from 'react-bootstrap'
import { DropdownItem } from '@/features/ui/components/bootstrap-5/dropdown-menu'
import { DropdownItemProps } from '@/features/ui/components/types/dropdown-menu-props'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import DropdownListItem from '@/features/ui/components/bootstrap-5/dropdown-list-item'
type OLDropdownMenuItemProps = DropdownItemProps & {
bs3Props?: MenuItemProps
}
// This represents a menu item. It wraps the item within an <li> element.
function OLDropdownMenuItem(props: OLDropdownMenuItemProps) {
const { bs3Props, ...rest } = props
const bs3MenuItemProps: MenuItemProps = {
children: rest.leadingIcon ? (
<>
{rest.leadingIcon}
&nbsp;
{rest.children}
</>
) : (
rest.children
),
onClick: rest.onClick,
href: rest.href,
download: rest.download,
eventKey: rest.eventKey,
...bs3Props,
}
function OLDropdownMenuItem(props: DropdownItemProps) {
return (
<BootstrapVersionSwitcher
bs3={<MenuItem {...bs3MenuItemProps} />}
bs5={
<DropdownListItem>
<DropdownItem {...rest} />
</DropdownListItem>
}
/>
<DropdownListItem>
<DropdownItem {...props} />
</DropdownListItem>
)
}
@@ -1,7 +1,4 @@
import { Form, FormCheckProps } from 'react-bootstrap-5'
import { Checkbox as BS3Checkbox } from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
import { MergeAndOverride } from '../../../../../../types/utils'
import FormText from '../bootstrap-5/form/form-text'
@@ -9,128 +6,36 @@ type OLFormCheckboxProps = MergeAndOverride<
FormCheckProps,
{
inputRef?: React.MutableRefObject<HTMLInputElement | null>
bs3Props?: Record<PropertyKey, unknown>
} & (
| { description: string; id: string }
| { description?: undefined; id?: string }
)
>
type RadioButtonProps = {
checked?: boolean
className?: string
description?: string
disabled?: boolean
id: string
name?: string
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
required?: boolean
label: React.ReactElement | string
value: string
}
function OLFormCheckbox(props: OLFormCheckboxProps) {
const { bs3Props, inputRef, ...rest } = props
const { inputRef, ...rest } = props
const bs3FormCheckboxProps: React.ComponentProps<typeof BS3Checkbox> = {
children: rest.label,
checked: rest.checked,
value: rest.value,
id: rest.id,
name: rest.name,
required: rest.required,
readOnly: rest.readOnly,
disabled: rest.disabled,
inline: rest.inline,
title: rest.title,
autoComplete: rest.autoComplete,
defaultChecked: rest.defaultChecked,
className: rest.className,
onChange: rest.onChange as (e: React.ChangeEvent<unknown>) => void,
inputRef: node => {
if (inputRef) {
inputRef.current = node
}
},
...getAriaAndDataProps(rest),
...bs3Props,
}
return (
<BootstrapVersionSwitcher
bs3={
rest.type === 'radio' ? (
<BS3Radio {...(rest as RadioButtonProps)} />
) : (
<BS3Checkbox {...bs3FormCheckboxProps} />
)
}
bs5={
rest.type === 'radio' ? (
<Form.Check
ref={inputRef}
aria-describedby={
rest.description ? `${rest.id}-description` : undefined
}
{...rest}
label={
<>
{rest.label}
{rest.description && (
<FormText
id={`${rest.id}-description`}
className="form-check-label-description"
>
{rest.description}
</FormText>
)}
</>
}
/>
) : (
<Form.Check ref={inputRef} {...rest} />
)
return rest.type === 'radio' ? (
<Form.Check
ref={inputRef}
aria-describedby={rest.description ? `${rest.id}-description` : undefined}
{...rest}
label={
<>
{rest.label}
{rest.description && (
<FormText
id={`${rest.id}-description`}
className="form-check-label-description"
>
{rest.description}
</FormText>
)}
</>
}
/>
)
}
function BS3Radio(props: RadioButtonProps) {
const {
label,
checked,
className,
description,
disabled,
id,
name,
onChange,
required,
value,
} = props
return (
<div className={className}>
<input
checked={checked}
className="me-1"
disabled={disabled}
id={id}
name={name}
onChange={onChange}
type="radio"
required={required}
value={value}
aria-describedby={description ? `${id}-description` : undefined}
/>
<label htmlFor={id} data-disabled={disabled ? 'true' : undefined}>
{label}
</label>{' '}
{description && (
<small id={`${id}-description`} aria-hidden="true">
{description}
</small>
)}
</div>
) : (
<Form.Check ref={inputRef} {...rest} />
)
}
@@ -1,96 +1,27 @@
import { forwardRef, ComponentProps, useCallback } from 'react'
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
import { forwardRef } from 'react'
import FormControl, {
type OLBS5FormControlProps,
} from '@/features/ui/components/bootstrap-5/form/form-control'
import BS3FormControl from '@/features/ui/components/bootstrap-3/form/form-control'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import OLSpinner from '@/features/ui/components/ol/ol-spinner'
import Icon from '@/shared/components/icon'
import type { BsPrefixRefForwardingComponent } from 'react-bootstrap-5/helpers'
type OLFormControlProps = OLBS5FormControlProps & {
bs3Props?: Record<string, unknown>
'data-ol-dirty'?: unknown
'main-field'?: any // For the CM6's benefit in the editor search panel
loading?: boolean
}
type BS3FormControlProps = ComponentProps<typeof BS3FormControl> & {
'main-field'?: any
}
const OLFormControl: BsPrefixRefForwardingComponent<
'input',
OLFormControlProps
> = forwardRef<HTMLInputElement, OLFormControlProps>((props, ref) => {
const { bs3Props, append, ...rest } = props
// Use a callback so that the ref passed to the BS3 FormControl is stable
const bs3InputRef = useCallback(
(inputElement: HTMLInputElement) => {
if (typeof ref === 'function') {
ref(inputElement)
} else if (ref) {
ref.current = inputElement
}
},
[ref]
)
let bs3FormControlProps: BS3FormControlProps = {
inputRef: bs3InputRef,
componentClass: rest.as,
bsSize: rest.size,
id: rest.id,
name: rest.name,
className: rest.className,
style: rest.style,
type: rest.type,
value: rest.value,
defaultValue: rest.defaultValue,
required: rest.required,
disabled: rest.disabled,
placeholder: rest.placeholder,
readOnly: rest.readOnly,
autoComplete: rest.autoComplete,
autoFocus: rest.autoFocus,
minLength: rest.minLength,
maxLength: rest.maxLength,
onChange: rest.onChange as BS3FormControlProps['onChange'],
onKeyDown: rest.onKeyDown as BS3FormControlProps['onKeyDown'],
onFocus: rest.onFocus as BS3FormControlProps['onFocus'],
onBlur: rest.onBlur as BS3FormControlProps['onBlur'],
onInvalid: rest.onInvalid as BS3FormControlProps['onInvalid'],
onPaste: rest.onPaste as BS3FormControlProps['onPaste'],
prepend: rest.prepend,
size: rest.htmlSize,
'main-field': rest['main-field'],
children: rest.children,
...bs3Props,
}
bs3FormControlProps = {
...bs3FormControlProps,
...getAriaAndDataProps(rest),
'data-ol-dirty': rest['data-ol-dirty'],
} as typeof bs3FormControlProps & Record<string, unknown>
const { append, ...rest } = props
return (
<BootstrapVersionSwitcher
bs3={
<BS3FormControl
{...bs3FormControlProps}
append={rest.loading ? <Icon type="spinner" spin /> : append}
/>
}
bs5={
<FormControl
ref={ref}
{...rest}
append={rest.loading ? <OLSpinner size="sm" /> : append}
/>
}
<FormControl
ref={ref}
{...rest}
append={rest.loading ? <OLSpinner size="sm" /> : append}
/>
)
})
@@ -1,38 +1,14 @@
import { Form } from 'react-bootstrap-5'
import {
HelpBlock as BS3HelpBlock,
HelpBlockProps as BS3HelpBlockProps,
} from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { ComponentProps } from 'react'
import classnames from 'classnames'
import FormFeedback from '@/features/ui/components/bootstrap-5/form/form-feedback'
type OLFormFeedbackProps = Pick<
ComponentProps<typeof Form.Control.Feedback>,
'type' | 'className' | 'children'
> & {
bs3Props?: Record<string, unknown>
}
>
function OLFormFeedback(props: OLFormFeedbackProps) {
const { bs3Props, children, ...bs5Props } = props
const bs3HelpBlockProps: BS3HelpBlockProps = {
className: classnames(
bs5Props.className,
bs5Props.type === 'invalid' ? 'invalid-only' : null
),
children,
...bs3Props,
}
return (
<BootstrapVersionSwitcher
bs3={<BS3HelpBlock {...bs3HelpBlockProps} />}
bs5={<FormFeedback {...bs5Props}>{children}</FormFeedback>}
/>
)
return <FormFeedback {...props} />
}
export default OLFormFeedback
@@ -1,42 +1,8 @@
import { FormGroupProps } from 'react-bootstrap-5'
import {
FormGroup as BS3FormGroup,
FormGroupProps as BS3FormGroupProps,
FormControl,
} from 'react-bootstrap'
import FormGroup from '@/features/ui/components/bootstrap-5/form/form-group'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import classNames from 'classnames'
type OLFormGroupProps = FormGroupProps & {
bs3Props?: {
withFeedback?: boolean
hiddenLabel?: boolean
validationState?: BS3FormGroupProps['validationState']
}
}
function OLFormGroup(props: OLFormGroupProps) {
const { bs3Props, className, ...rest } = props
const { withFeedback, hiddenLabel, ...bs3PropsRest } = bs3Props || {}
const bs3FormGroupProps: BS3FormGroupProps = {
controlId: rest.controlId,
className: classNames(className, { 'hidden-label': hiddenLabel }),
...bs3PropsRest,
}
return (
<BootstrapVersionSwitcher
bs3={
<BS3FormGroup {...bs3FormGroupProps}>
{rest.children}
{withFeedback ? <FormControl.Feedback /> : null}
</BS3FormGroup>
}
bs5={<FormGroup className={className} {...rest} />}
/>
)
function OLFormGroup(props: FormGroupProps) {
return <FormGroup {...props} />
}
export default OLFormGroup
@@ -1,28 +1,7 @@
import { Form } from 'react-bootstrap-5'
import { ControlLabel as BS3FormLabel } from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
type OLFormLabelProps = React.ComponentProps<(typeof Form)['Label']> & {
bs3Props?: Record<string, unknown>
}
function OLFormLabel(props: OLFormLabelProps) {
const { bs3Props, ...rest } = props
const bs3FormLabelProps: React.ComponentProps<typeof BS3FormLabel> = {
children: rest.children,
htmlFor: rest.htmlFor,
srOnly: rest.visuallyHidden,
className: rest.className,
...bs3Props,
}
return (
<BootstrapVersionSwitcher
bs3={<BS3FormLabel {...bs3FormLabelProps} />}
bs5={<Form.Label {...rest} />}
/>
)
function OLFormLabel(props: React.ComponentProps<(typeof Form)['Label']>) {
return <Form.Label {...props} />
}
export default OLFormLabel
@@ -1,56 +1,9 @@
import { forwardRef } from 'react'
import { Form, FormSelectProps } from 'react-bootstrap-5'
import {
FormControl as BS3FormControl,
FormControlProps as BS3FormControlProps,
} from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
type OLFormSelectProps = FormSelectProps & {
bs3Props?: Record<string, unknown>
}
const OLFormSelect = forwardRef<HTMLSelectElement, OLFormSelectProps>(
const OLFormSelect = forwardRef<HTMLSelectElement, FormSelectProps>(
(props, ref) => {
const { bs3Props, ...bs5Props } = props
const bs3FormSelectProps: BS3FormControlProps = {
children: bs5Props.children,
bsSize: bs5Props.size,
name: bs5Props.name,
value: bs5Props.value,
defaultValue: bs5Props.defaultValue,
disabled: bs5Props.disabled,
onChange: bs5Props.onChange as BS3FormControlProps['onChange'],
required: bs5Props.required,
placeholder: bs5Props.placeholder,
className: bs5Props.className,
inputRef: (inputElement: HTMLInputElement) => {
if (typeof ref === 'function') {
ref(inputElement as unknown as HTMLSelectElement)
} else if (ref) {
ref.current = inputElement as unknown as HTMLSelectElement
}
},
...bs3Props,
}
// Get all `aria-*` and `data-*` attributes
const extraProps = getAriaAndDataProps(bs5Props)
return (
<BootstrapVersionSwitcher
bs3={
<BS3FormControl
componentClass="select"
{...bs3FormSelectProps}
{...extraProps}
/>
}
bs5={<Form.Select ref={ref} {...bs5Props} />}
/>
)
return <Form.Select ref={ref} {...props} />
}
)
OLFormSelect.displayName = 'OLFormSelect'
@@ -1,53 +1,19 @@
import { FormCheck, FormCheckProps, FormLabel } from 'react-bootstrap-5'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
type OLFormSwitchProps = FormCheckProps & {
inputRef?: React.MutableRefObject<HTMLInputElement | null>
bs3Props?: React.InputHTMLAttributes<HTMLInputElement>
}
function OLFormSwitch(props: OLFormSwitchProps) {
const { bs3Props, inputRef, label, id, ...rest } = props
const bs3FormSwitchProps: React.InputHTMLAttributes<HTMLInputElement> = {
id,
checked: rest.checked,
required: rest.required,
readOnly: rest.readOnly,
disabled: rest.disabled,
autoComplete: rest.autoComplete,
defaultChecked: rest.defaultChecked,
onChange: rest.onChange as (e: React.ChangeEvent<unknown>) => void,
...getAriaAndDataProps(rest),
...bs3Props,
}
const { inputRef, label, id, ...rest } = props
return (
<BootstrapVersionSwitcher
bs3={
<div className="input-switch">
<input
id={id}
type="checkbox"
className="input-switch-hidden-input"
ref={inputRef}
{...bs3FormSwitchProps}
/>
<label htmlFor={id} className="input-switch-btn">
<span className="sr-only">{label}</span>
</label>
</div>
}
bs5={
<>
<FormCheck type="switch" ref={inputRef} id={id} {...rest} />
<FormLabel htmlFor={id} visuallyHidden>
{label}
</FormLabel>
</>
}
/>
<>
<FormCheck type="switch" ref={inputRef} id={id} {...rest} />
<FormLabel htmlFor={id} visuallyHidden>
{label}
</FormLabel>
</>
)
}
@@ -1,36 +1,9 @@
import FormText, {
FormTextProps,
getFormTextClass,
} from '@/features/ui/components/bootstrap-5/form/form-text'
import PolymorphicComponent from '@/shared/components/polymorphic-component'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import classnames from 'classnames'
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
type OLFormTextProps = FormTextProps & {
bs3Props?: Record<string, unknown>
}
function OLFormText({ as = 'div', ...props }: OLFormTextProps) {
const { bs3Props, ...rest } = props
const bs3HelpBlockProps = {
children: rest.children,
className: classnames('small', rest.className, getFormTextClass(rest.type)),
...bs3Props,
} as const satisfies React.ComponentProps<typeof PolymorphicComponent>
// Get all `aria-*` and `data-*` attributes
const extraProps = getAriaAndDataProps(rest)
return (
<BootstrapVersionSwitcher
bs3={
<PolymorphicComponent {...bs3HelpBlockProps} {...extraProps} as={as} />
}
bs5={<FormText {...rest} as={as} />}
/>
)
function OLFormText({ as = 'div', ...rest }: FormTextProps) {
return <FormText {...rest} as={as} />
}
export default OLFormText
@@ -1,45 +1,8 @@
import { Form } from 'react-bootstrap-5'
import { Form as BS3Form, FormProps as BS3FormProps } from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { ComponentProps } from 'react'
import classnames from 'classnames'
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
type OLFormProps = ComponentProps<typeof Form> & {
bs3Props?: ComponentProps<typeof BS3Form>
}
function OLForm(props: OLFormProps) {
const { bs3Props, ...rest } = props
const bs3FormProps: BS3FormProps = {
componentClass: rest.as,
children: rest.children,
id: rest.id,
onSubmit: rest.onSubmit as BS3FormProps['onSubmit'],
onClick: rest.onClick as BS3FormProps['onClick'],
name: rest.name,
noValidate: rest.noValidate,
role: rest.role,
...bs3Props,
}
const bs3ClassName = classnames(
rest.className,
rest.validated ? 'was-validated' : null
)
// Get all `aria-*` and `data-*` attributes
const extraProps = getAriaAndDataProps(rest)
return (
<BootstrapVersionSwitcher
bs3={
<BS3Form className={bs3ClassName} {...bs3FormProps} {...extraProps} />
}
bs5={<Form {...rest} />}
/>
)
function OLForm(props: ComponentProps<typeof Form>) {
return <Form {...props} />
}
export default OLForm
@@ -1,54 +1,13 @@
import { forwardRef } from 'react'
import { bs3ButtonProps, BS3ButtonSize } from './ol-button'
import { Button as BS3Button } from 'react-bootstrap'
import type { IconButtonProps } from '@/features/ui/components/types/icon-button-props'
import BootstrapVersionSwitcher from '../bootstrap-5/bootstrap-version-switcher'
import Icon, { IconProps } from '@/shared/components/icon'
import IconButton from '../bootstrap-5/icon-button'
import { callFnsInSequence } from '@/utils/functions'
export type OLIconButtonProps = IconButtonProps & {
bs3Props?: {
loading?: React.ReactNode
fw?: IconProps['fw']
className?: string
bsSize?: BS3ButtonSize
onMouseOver?: React.MouseEventHandler<HTMLButtonElement>
onMouseOut?: React.MouseEventHandler<HTMLButtonElement>
onFocus?: React.FocusEventHandler<HTMLButtonElement>
onBlur?: React.FocusEventHandler<HTMLButtonElement>
}
}
export type OLIconButtonProps = IconButtonProps
const OLIconButton = forwardRef<HTMLButtonElement, OLIconButtonProps>(
(props, ref) => {
const { bs3Props, ...rest } = props
const { fw, loading, ...bs3Rest } = bs3Props || {}
// BS3 OverlayTrigger automatically provides 'onMouseOver', 'onMouseOut', 'onFocus', 'onBlur' event handlers
const bs3FinalProps = {
'aria-label': rest.accessibilityLabel,
...bs3ButtonProps(rest),
...bs3Rest,
onMouseOver: callFnsInSequence(bs3Props?.onMouseOver, rest.onMouseOver),
onMouseOut: callFnsInSequence(bs3Props?.onMouseOut, rest.onMouseOut),
onFocus: callFnsInSequence(bs3Props?.onFocus, rest.onFocus),
onBlur: callFnsInSequence(bs3Props?.onBlur, rest.onBlur),
}
// BS3 tooltip relies on the 'onMouseOver', 'onMouseOut', 'onFocus', 'onBlur' props
// BS5 tooltip relies on the ref
return (
<BootstrapVersionSwitcher
bs3={
<BS3Button {...bs3FinalProps}>
{loading || <Icon type={rest.icon} fw={fw} />}
</BS3Button>
}
bs5={<IconButton {...rest} ref={ref} />}
/>
)
return <IconButton {...props} ref={ref} />
}
)
OLIconButton.displayName = 'OLIconButton'
@@ -1,40 +1,13 @@
import { ListGroupItem, ListGroupItemProps } from 'react-bootstrap-5'
import {
ListGroupItem as BS3ListGroupItem,
ListGroupItemProps as BS3ListGroupItemProps,
} from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
type OLListGroupItemProps = ListGroupItemProps & {
bs3Props?: BS3ListGroupItemProps
}
function OLListGroupItem(props: OLListGroupItemProps) {
const { bs3Props, ...rest } = props
const bs3ListGroupItemProps: BS3ListGroupItemProps = {
children: rest.children,
active: rest.active,
disabled: rest.disabled,
href: rest.href,
onClick: rest.onClick as BS3ListGroupItemProps['onClick'],
...bs3Props,
}
const extraProps = getAriaAndDataProps(rest)
const as = rest.as ?? 'button'
function OLListGroupItem(props: ListGroupItemProps) {
const as = props.as ?? 'button'
return (
<BootstrapVersionSwitcher
bs3={<BS3ListGroupItem {...bs3ListGroupItemProps} {...extraProps} />}
bs5={
<ListGroupItem
{...rest}
as={as}
type={as === 'button' ? 'button' : undefined}
/>
}
<ListGroupItem
{...props}
as={as}
type={as === 'button' ? 'button' : undefined}
/>
)
}
@@ -1,34 +1,9 @@
import { ListGroup, ListGroupProps } from 'react-bootstrap-5'
import {
ListGroup as BS3ListGroup,
ListGroupProps as BS3ListGroupProps,
} from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
type OLListGroupProps = ListGroupProps & {
bs3Props?: BS3ListGroupProps
}
function OLListGroup(props: ListGroupProps) {
const as = props.as ?? 'div'
function OLListGroup(props: OLListGroupProps) {
const { bs3Props, ...rest } = props
const bs3ListGroupProps: BS3ListGroupProps = {
children: rest.children,
role: rest.role,
componentClass: rest.as,
...bs3Props,
}
const extraProps = getAriaAndDataProps(rest)
const as = rest.as ?? 'div'
return (
<BootstrapVersionSwitcher
bs3={<BS3ListGroup {...bs3ListGroupProps} {...extraProps} />}
bs5={<ListGroup {...rest} as={as} />}
/>
)
return <ListGroup {...props} as={as} />
}
export default OLListGroup
@@ -1,133 +1,37 @@
import {
Modal as BS5Modal,
Modal,
ModalProps,
ModalHeaderProps,
ModalTitleProps,
ModalBody,
ModalFooterProps,
} from 'react-bootstrap-5'
import {
Modal as BS3Modal,
ModalProps as BS3ModalProps,
ModalHeaderProps as BS3ModalHeaderProps,
ModalTitleProps as BS3ModalTitleProps,
ModalBodyProps as BS3ModalBodyProps,
ModalFooterProps as BS3ModalFooterProps,
} from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import AccessibleModal from '@/shared/components/accessible-modal'
import { ModalBodyProps } from 'react-bootstrap-5/ModalBody'
type OLModalProps = ModalProps & {
bs3Props?: Record<string, unknown>
size?: 'sm' | 'lg'
onHide: () => void
}
type OLModalHeaderProps = ModalHeaderProps & {
bs3Props?: Record<string, unknown>
}
type OLModalTitleProps = ModalTitleProps & {
bs3Props?: Record<string, unknown>
}
type OLModalBodyProps = React.ComponentProps<typeof ModalBody> & {
bs3Props?: Record<string, unknown>
}
type OLModalFooterProps = ModalFooterProps & {
bs3Props?: Record<string, unknown>
}
export default function OLModal({ children, ...props }: OLModalProps) {
const { bs3Props, ...bs5Props } = props
return <Modal {...props}>{children}</Modal>
}
const bs3ModalProps: BS3ModalProps = {
bsClass: bs5Props.bsPrefix,
bsSize: bs5Props.size,
show: bs5Props.show,
onHide: bs5Props.onHide,
onExited: bs5Props.onExited,
backdrop: bs5Props.backdrop,
animation: bs5Props.animation,
id: bs5Props.id,
className: bs5Props.className,
backdropClassName: bs5Props.backdropClassName,
...bs3Props,
}
export function OLModalHeader({ children, ...props }: ModalHeaderProps) {
return <Modal.Header {...props}>{children}</Modal.Header>
}
export function OLModalTitle({ children, ...props }: ModalTitleProps) {
return (
<BootstrapVersionSwitcher
bs3={<AccessibleModal {...bs3ModalProps}>{children}</AccessibleModal>}
bs5={<BS5Modal {...bs5Props}>{children}</BS5Modal>}
/>
<Modal.Title as="h2" {...props}>
{children}
</Modal.Title>
)
}
export function OLModalHeader({ children, ...props }: OLModalHeaderProps) {
const { bs3Props, ...bs5Props } = props
const bs3ModalProps: BS3ModalHeaderProps = {
bsClass: bs5Props.bsPrefix,
onHide: bs5Props.onHide,
closeButton: bs5Props.closeButton,
closeLabel: bs5Props.closeLabel,
}
return (
<BootstrapVersionSwitcher
bs3={<BS3Modal.Header {...bs3ModalProps}>{children}</BS3Modal.Header>}
bs5={<BS5Modal.Header {...bs5Props}>{children}</BS5Modal.Header>}
/>
)
export function OLModalBody({ children, ...props }: ModalBodyProps) {
return <Modal.Body {...props}>{children}</Modal.Body>
}
export function OLModalTitle({ children, ...props }: OLModalTitleProps) {
const { bs3Props, ...bs5Props } = props
const bs3ModalProps: BS3ModalTitleProps = {
componentClass: bs5Props.as,
}
return (
<BootstrapVersionSwitcher
bs3={<BS3Modal.Title {...bs3ModalProps}>{children}</BS3Modal.Title>}
bs5={
<BS5Modal.Title as="h2" {...bs5Props}>
{children}
</BS5Modal.Title>
}
/>
)
}
export function OLModalBody({ children, ...props }: OLModalBodyProps) {
const { bs3Props, ...bs5Props } = props
const bs3ModalProps: BS3ModalBodyProps = {
componentClass: bs5Props.as,
className: bs5Props.className,
}
return (
<BootstrapVersionSwitcher
bs3={<BS3Modal.Body {...bs3ModalProps}>{children}</BS3Modal.Body>}
bs5={<BS5Modal.Body {...bs5Props}>{children}</BS5Modal.Body>}
/>
)
}
export function OLModalFooter({ children, ...props }: OLModalFooterProps) {
const { bs3Props, ...bs5Props } = props
const bs3ModalProps: BS3ModalFooterProps = {
componentClass: bs5Props.as,
className: bs5Props.className,
}
return (
<BootstrapVersionSwitcher
bs3={<BS3Modal.Footer {...bs3ModalProps}>{children}</BS3Modal.Footer>}
bs5={<BS5Modal.Footer {...bs5Props}>{children}</BS5Modal.Footer>}
/>
)
export function OLModalFooter({ children, ...props }: ModalFooterProps) {
return <Modal.Footer {...props}>{children}</Modal.Footer>
}
@@ -1,42 +1,10 @@
import Notification from '@/shared/components/notification'
import { Alert, AlertProps } from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import classnames from 'classnames'
type OLNotificationProps = React.ComponentProps<typeof Notification> & {
bs3Props?: {
icon?: React.ReactElement
className?: string
}
}
function OLNotification(props: OLNotificationProps) {
const { bs3Props, ...notificationProps } = props
const alertProps = {
// Map `error` to `danger`
bsStyle:
notificationProps.type === 'error' ? 'danger' : notificationProps.type,
className: classnames(notificationProps.className, bs3Props?.className),
onDismiss: notificationProps.onDismiss,
} as const satisfies AlertProps
function OLNotification(props: React.ComponentProps<typeof Notification>) {
return (
<BootstrapVersionSwitcher
bs3={
<Alert {...alertProps}>
{bs3Props?.icon}
{bs3Props?.icon && ' '}
{notificationProps.content}
{notificationProps.action}
</Alert>
}
bs5={
<div className="notification-list">
<Notification {...notificationProps} />
</div>
}
/>
<div className="notification-list">
<Notification {...props} />
</div>
)
}
@@ -1,61 +1,7 @@
import { Overlay, OverlayProps } from 'react-bootstrap-5'
import {
Overlay as BS3Overlay,
OverlayProps as BS3OverlayProps,
} from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
type OLOverlayProps = OverlayProps & {
bs3Props?: BS3OverlayProps
}
function OLOverlay(props: OLOverlayProps) {
const { bs3Props, ...bs5Props } = props
let bs3OverlayProps: BS3OverlayProps = {
children: bs5Props.children,
target: bs5Props.target as BS3OverlayProps['target'],
container: bs5Props.container,
containerPadding: bs5Props.containerPadding,
show: bs5Props.show,
rootClose: bs5Props.rootClose,
animation: bs5Props.transition,
onHide: bs5Props.onHide as BS3OverlayProps['onHide'],
onEnter: bs5Props.onEnter as BS3OverlayProps['onEnter'],
onEntering: bs5Props.onEntering as BS3OverlayProps['onEntering'],
onEntered: bs5Props.onEntered as BS3OverlayProps['onEntered'],
onExit: bs5Props.onExit as BS3OverlayProps['onExit'],
onExiting: bs5Props.onExiting as BS3OverlayProps['onExiting'],
onExited: bs5Props.onExited as BS3OverlayProps['onExited'],
}
if (bs5Props.placement) {
const bs3PlacementOptions = [
'top',
'right',
'bottom',
'left',
] satisfies Array<
Extract<OverlayProps['placement'], BS3OverlayProps['placement']>
>
for (const placement of bs3PlacementOptions) {
// BS5 has more placement options than BS3, such as "left-start", so these are mapped to "left" etc.
if (bs5Props.placement.startsWith(placement)) {
bs3OverlayProps.placement = placement
break
}
}
}
bs3OverlayProps = { ...bs3OverlayProps, ...bs3Props }
return (
<BootstrapVersionSwitcher
bs3={<BS3Overlay {...bs3OverlayProps} />}
bs5={<Overlay {...bs5Props} />}
/>
)
function OLOverlay(props: OverlayProps) {
return <Overlay {...props} />
}
export default OLOverlay
@@ -1,24 +1,18 @@
import { Card, CardBody } from 'react-bootstrap-5'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { FC } from 'react'
import classNames from 'classnames'
// This wraps the Bootstrap 5 Card component but is restricted to the very
// basic way we're using it, which is as a container for page content. The
// Bootstrap 3 equivalent in our codebase is a div with class "card"
// Bootstrap 3 equivalent previously in our codebase is a div with class "card"
const OLPageContentCard: FC<{ className?: string }> = ({
children,
className,
}) => {
return (
<BootstrapVersionSwitcher
bs3={<div className={classNames('card', className)}>{children}</div>}
bs5={
<Card className={classNames('page-content-card', className)}>
<CardBody>{children}</CardBody>
</Card>
}
/>
<Card className={classNames('page-content-card', className)}>
<CardBody>{children}</CardBody>
</Card>
)
}
@@ -1,83 +1,18 @@
import { forwardRef } from 'react'
import { Popover, PopoverProps } from 'react-bootstrap-5'
import {
Popover as BS3Popover,
PopoverProps as BS3PopoverProps,
} from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
type OLPopoverProps = Omit<PopoverProps, 'title'> & {
title?: React.ReactNode
bs3Props?: BS3PopoverProps
}
const OLPopover = forwardRef<HTMLDivElement, OLPopoverProps>((props, ref) => {
// BS3 passes in some props automatically so the `props`
// type should be adjusted to reflect the actual received object
const propsCombinedWithAutoInjectedBs3Props = props as OLPopoverProps &
Pick<
BS3PopoverProps,
'arrowOffsetLeft' | 'arrowOffsetTop' | 'positionLeft' | 'positionTop'
>
const {
bs3Props,
title,
children,
arrowOffsetLeft,
arrowOffsetTop,
positionLeft,
positionTop,
...bs5Props
} = propsCombinedWithAutoInjectedBs3Props
let bs3PopoverProps: BS3PopoverProps = {
children,
arrowOffsetLeft,
arrowOffsetTop,
positionLeft,
positionTop,
title,
id: bs5Props.id,
className: bs5Props.className,
style: bs5Props.style,
}
if (bs5Props.placement) {
const bs3PlacementOptions = [
'top',
'right',
'bottom',
'left',
] satisfies Array<
Extract<PopoverProps['placement'], BS3PopoverProps['placement']>
>
for (const placement of bs3PlacementOptions) {
if (placement === bs5Props.placement) {
bs3PopoverProps.placement = bs5Props.placement
break
}
}
}
bs3PopoverProps = { ...bs3PopoverProps, ...bs3Props }
const { title, children, ...bs5Props } = props
return (
<BootstrapVersionSwitcher
bs3={
<BS3Popover
{...bs3PopoverProps}
ref={ref as React.LegacyRef<BS3Popover>}
/>
}
bs5={
<Popover {...bs5Props} ref={ref}>
{title && <Popover.Header>{title}</Popover.Header>}
<Popover.Body>{children}</Popover.Body>
</Popover>
}
/>
<Popover {...bs5Props} ref={ref}>
{title && <Popover.Header>{title}</Popover.Header>}
<Popover.Body>{children}</Popover.Body>
</Popover>
)
})
OLPopover.displayName = 'OLPopover'
@@ -1,16 +1,7 @@
import { Row } from 'react-bootstrap-5'
import { Row as BS3Row } from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
type OLRowProps = React.ComponentProps<typeof Row>
function OLRow(props: OLRowProps) {
return (
<BootstrapVersionSwitcher
bs3={<BS3Row className={props.className}>{props.children}</BS3Row>}
bs5={<Row {...props} />}
/>
)
function OLRow(props: React.ComponentProps<typeof Row>) {
return <Row {...props} />
}
export default OLRow
@@ -1,29 +1,14 @@
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import Icon from '@/shared/components/icon'
import { Spinner } from 'react-bootstrap-5'
import classNames from 'classnames'
export type OLSpinnerSize = 'sm' | 'lg'
function OLSpinner({ size = 'sm' }: { size: OLSpinnerSize }) {
return (
<BootstrapVersionSwitcher
bs3={
<Icon
type="refresh"
fw
spin
className={classNames({ 'fa-2x': size === 'lg' })}
/>
}
bs5={
<Spinner
size={size === 'sm' ? 'sm' : undefined}
animation="border"
aria-hidden="true"
role="status"
/>
}
<Spinner
size={size === 'sm' ? 'sm' : undefined}
animation="border"
aria-hidden="true"
role="status"
/>
)
}
@@ -1,32 +1,7 @@
import Table from '@/features/ui/components/bootstrap-5/table'
import { Table as BS3Table } from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
type OLFormProps = React.ComponentProps<typeof Table> & {
bs3Props?: React.ComponentProps<typeof BS3Table>
}
function OLTable(props: OLFormProps) {
const { bs3Props, container, ...rest } = props
const bs3FormProps: React.ComponentProps<typeof BS3Table> = {
bsClass: rest.className,
id: rest.id,
condensed: rest.size === 'sm',
children: rest.children,
responsive:
typeof rest.responsive !== 'string' ? rest.responsive : undefined,
...getAriaAndDataProps(rest),
...bs3Props,
}
return (
<BootstrapVersionSwitcher
bs3={<BS3Table {...bs3FormProps} />}
bs5={<Table container={container} {...rest} />}
/>
)
function OLTable(props: React.ComponentProps<typeof Table>) {
return <Table {...props} />
}
export default OLTable
@@ -1,37 +1,10 @@
import Tag from '@/features/ui/components/bootstrap-5/tag'
import BS3Tag from '@/shared/components/tag'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { forwardRef } from 'react'
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
type OLTagProps = React.ComponentProps<typeof Tag> & {
bs3Props?: React.ComponentProps<typeof BS3Tag>
}
type OLTagProps = React.ComponentProps<typeof Tag>
const OLTag = forwardRef<HTMLElement, OLTagProps>((props: OLTagProps, ref) => {
const { bs3Props, ...rest } = props
const bs3TagProps: React.ComponentProps<typeof BS3Tag> = {
children: rest.children,
prepend: rest.prepend,
closeBtnProps: rest.closeBtnProps,
className: rest.className,
onClick: rest.onClick,
onFocus: rest.onFocus,
onBlur: rest.onBlur,
onMouseOver: rest.onMouseOver,
onMouseOut: rest.onMouseOut,
contentProps: rest.contentProps,
...getAriaAndDataProps(rest),
...bs3Props,
}
return (
<BootstrapVersionSwitcher
bs3={<BS3Tag {...bs3TagProps} />}
bs5={<Tag ref={ref} {...rest} />}
/>
)
return <Tag ref={ref} {...props} />
})
OLTag.displayName = 'OLTag'
@@ -1,8 +1,5 @@
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { CSSProperties, FC } from 'react'
import { ToastContainer as BS5ToastContainer } from 'react-bootstrap-5'
import { ToastContainer as BS3ToastContainer } from '../bootstrap-3/toast-container'
type OLToastContainerProps = {
style?: CSSProperties
@@ -15,17 +12,8 @@ export const OLToastContainer: FC<OLToastContainerProps> = ({
style,
}) => {
return (
<BootstrapVersionSwitcher
bs5={
<BS5ToastContainer className={className} style={style}>
{children}
</BS5ToastContainer>
}
bs3={
<BS3ToastContainer className={className} style={style}>
{children}
</BS3ToastContainer>
}
/>
<BS5ToastContainer className={className} style={style}>
{children}
</BS5ToastContainer>
)
}
@@ -7,8 +7,6 @@ import {
import { useTranslation } from 'react-i18next'
import MaterialIcon from '../../../../shared/components/material-icon'
import { ReactNode, useCallback, useState } from 'react'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { Toast as BS3Toast } from '../bootstrap-3/toast'
export type OLToastProps = {
type: NotificationType
@@ -78,29 +76,14 @@ export const OLToast = ({
</div>
)
return (
<BootstrapVersionSwitcher
bs5={
<BS5Toast
onClose={handleClose}
autohide={autoHide}
onExited={handleOnHidden}
delay={delay}
show={show}
>
{toastElement}
</BS5Toast>
}
bs3={
<BS3Toast
onClose={handleClose}
autohide={autoHide}
delay={delay}
onExited={handleOnHidden}
show={show}
>
{toastElement}
</BS3Toast>
}
/>
<BS5Toast
onClose={handleClose}
autohide={autoHide}
onExited={handleOnHidden}
delay={delay}
show={show}
>
{toastElement}
</BS5Toast>
)
}
@@ -1,42 +1,7 @@
import { ToggleButtonGroup, ToggleButtonGroupProps } from 'react-bootstrap-5'
import BS3ToggleButtonGroup from '@/features/ui/components/bootstrap-3/toggle-button-group'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
type BS3ToggleButtonGroupProps = React.ComponentProps<
typeof BS3ToggleButtonGroup
>
type OLToggleButtonGroupProps<T> = ToggleButtonGroupProps<T> & {
bs3Props?: BS3ToggleButtonGroupProps
}
function OLToggleButtonGroup<T>(props: OLToggleButtonGroupProps<T>) {
const { bs3Props, ...rest } = props
const bs3ToggleButtonGroupProps = {
name: rest.name,
type: rest.type,
value: rest.value,
onChange: rest.onChange,
children: rest.children,
className: rest.className,
defaultValue: rest.defaultValue,
defaultChecked: rest.defaultChecked,
...bs3Props,
} as BS3ToggleButtonGroupProps
// Get all `aria-*` and `data-*` attributes
const extraProps = getAriaAndDataProps(rest)
return (
<BootstrapVersionSwitcher
bs3={
<BS3ToggleButtonGroup {...extraProps} {...bs3ToggleButtonGroupProps} />
}
bs5={<ToggleButtonGroup {...rest} />}
/>
)
function OLToggleButtonGroup<T>(props: ToggleButtonGroupProps<T>) {
return <ToggleButtonGroup {...props} />
}
export default OLToggleButtonGroup
@@ -1,48 +1,7 @@
import { ToggleButton, ToggleButtonProps } from 'react-bootstrap-5'
import {
ToggleButton as BS3ToggleButton,
ToggleButtonProps as BS3ToggleButtonProps,
} from 'react-bootstrap'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
import classnames from 'classnames'
type OLToggleButtonProps = ToggleButtonProps & {
bs3Props?: BS3ToggleButtonProps
}
function OLToggleButton(props: OLToggleButtonProps) {
const { bs3Props, ...rest } = props
const bs3ToggleButtonProps: BS3ToggleButtonProps & { active?: boolean } = {
type: rest.type,
name: rest.name,
active: rest.active,
checked: rest.checked,
disabled: rest.disabled,
onChange: rest.onChange as BS3ToggleButtonProps['onChange'],
onClick: rest.onClick as BS3ToggleButtonProps['onClick'],
value: rest.value as BS3ToggleButtonProps['value'],
children: rest.children,
className: classnames(`btn-${props.variant || 'primary'}`, rest.className),
...bs3Props,
}
// Get all `aria-*` and `data-*` attributes
const extraProps = getAriaAndDataProps(rest)
return (
<BootstrapVersionSwitcher
bs3={
<BS3ToggleButton
{...extraProps}
{...bs3ToggleButtonProps}
bsStyle={null}
/>
}
bs5={<ToggleButton {...rest} />}
/>
)
function OLToggleButton(props: ToggleButtonProps) {
return <ToggleButton {...props} />
}
export default OLToggleButton
@@ -1,48 +1,7 @@
import Tooltip from '@/features/ui/components/bootstrap-5/tooltip'
import BS3Tooltip from '@/shared/components/tooltip'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
type OLTooltipProps = React.ComponentProps<typeof Tooltip> & {
bs3Props?: Record<string, unknown>
}
function OLTooltip(props: OLTooltipProps) {
const { bs3Props, ...bs5Props } = props
type BS3TooltipProps = React.ComponentProps<typeof BS3Tooltip>
const bs3TooltipProps: BS3TooltipProps = {
children: bs5Props.children,
id: bs5Props.id,
description: bs5Props.description,
tooltipProps: bs5Props.tooltipProps as BS3TooltipProps,
overlayProps: {
placement: bs5Props.overlayProps?.placement,
rootClose: bs5Props.overlayProps?.rootClose,
trigger: bs5Props.overlayProps?.trigger,
},
...bs3Props,
}
if ('hidden' in bs5Props) {
bs3TooltipProps.hidden = bs5Props.hidden
}
const delay = bs5Props.overlayProps?.delay
if (delay && typeof delay !== 'number') {
bs3TooltipProps.overlayProps = {
...bs3TooltipProps.overlayProps,
delayShow: delay.show,
delayHide: delay.hide,
}
}
return (
<BootstrapVersionSwitcher
bs3={<BS3Tooltip {...bs3TooltipProps} />}
bs5={<Tooltip {...bs5Props} />}
/>
)
function OLTooltip(props: React.ComponentProps<typeof Tooltip>) {
return <Tooltip {...props} />
}
export default OLTooltip
@@ -1,26 +0,0 @@
import getMeta from '@/utils/meta'
// The reason this is a function is to ensure that the meta tag is read before
// any isBootstrap5 check is performed
export const isBootstrap5 = () => getMeta('ol-bootstrapVersion') === 5
/* eslint-disable no-redeclare */
export function bsVersion<A>({ bs5 }: { bs5: A }): A | undefined
export function bsVersion<B>({ bs3 }: { bs3: B }): B | undefined
export function bsVersion<A, B>({ bs5, bs3 }: { bs5: A; bs3: B }): A | B
export function bsVersion({ bs5, bs3 }: { bs5?: unknown; bs3?: unknown }) {
return isBootstrap5() ? bs5 : bs3
}
// get all `aria-*` and `data-*` attributes
export const getAriaAndDataProps = (obj: Record<string, unknown>) => {
return Object.entries(obj).reduce(
(acc, [key, value]) => {
if (key.startsWith('aria-') || key.startsWith('data-')) {
acc[key] = value
}
return acc
},
{} as Record<string, unknown>
)
}
@@ -1,30 +0,0 @@
import { useCallback } from 'react'
import { Modal, ModalProps } from 'react-bootstrap'
// A wrapper for the v0.33 React Bootstrap Modal component,
// which ensures that the `aria-hidden` attribute is not set on the modal when it's visible,
// and that role="dialog" is not duplicated.
// https://github.com/react-bootstrap/react-bootstrap/issues/4790
// There are other ARIA attributes on these modals which could be improved,
// but this at least makes them accessible for tests.
function AccessibleModal(props: ModalProps) {
const modalRef = useCallback(
element => {
const modalNode = element?._modal?.modalNode
if (modalNode) {
if (props.show) {
modalNode.removeAttribute('role')
modalNode.removeAttribute('aria-hidden')
} else {
// NOTE: possibly not ever used, as the modal is only rendered when shown
modalNode.setAttribute('aria-hidden', 'true')
}
}
},
[props.show]
)
return <Modal {...props} ref={modalRef} />
}
export default AccessibleModal
@@ -1,29 +0,0 @@
import { Label } from 'react-bootstrap'
import classnames from 'classnames'
import { MergeAndOverride } from '../../../../types/utils'
type BadgeProps = MergeAndOverride<
React.ComponentProps<'span'>,
{
prepend?: React.ReactNode
children: React.ReactNode
className?: string
bsStyle?: React.ComponentProps<typeof Label>['bsStyle'] | null
}
>
function Badge({ prepend, children, bsStyle, className, ...rest }: BadgeProps) {
const classNames =
bsStyle === null
? className
: classnames('label', `label-${bsStyle}`, className)
return (
<span className={classNames} {...rest}>
{prepend && <span className="badge-tag-bs3-prepend">{prepend}</span>}
<span className="badge-tag-bs3-content">{children}</span>
</span>
)
}
export default Badge
@@ -1,14 +1,11 @@
import type { FC } from 'react'
import classnames from 'classnames'
import MaterialIcon from '@/shared/components/material-icon'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import OLBadge from '@/features/ui/components/ol/ol-badge'
function BS5BetaBadgeIcon({
badgeClass,
}: {
badgeClass: ReturnType<typeof chooseBadgeClass>
}) {
const BetaBadgeIcon: FC<{
phase?: string
}> = ({ phase = 'beta' }) => {
const badgeClass = chooseBadgeClass(phase)
if (badgeClass === 'info-badge') {
return <MaterialIcon type="info" className="align-middle info-badge" />
} else if (badgeClass === 'alpha-badge') {
@@ -26,18 +23,6 @@ function BS5BetaBadgeIcon({
}
}
const BetaBadgeIcon: FC<{
phase?: string
}> = ({ phase = 'beta' }) => {
const badgeClass = chooseBadgeClass(phase)
return (
<BootstrapVersionSwitcher
bs3={<span className={classnames('badge', badgeClass)} />}
bs5={<BS5BetaBadgeIcon badgeClass={badgeClass} />}
/>
)
}
function chooseBadgeClass(phase?: string) {
switch (phase) {
case 'release':
@@ -1,6 +1,5 @@
import type { FC, MouseEventHandler, ReactNode } from 'react'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import { bsVersion } from '@/features/utils/bootstrap-5'
import BetaBadgeIcon from '@/shared/components/beta-badge-icon'
type TooltipProps = {
@@ -40,9 +39,7 @@ const BetaBadge: FC<{
href={href || defaultHref}
{...linkProps}
>
<span className={bsVersion({ bs5: 'visually-hidden', bs3: 'sr-only' })}>
{description || tooltip?.text}
</span>
<span className="visually-hidden">{description || tooltip?.text}</span>
<BetaBadgeIcon phase={phase} />
</a>
)
@@ -1,6 +1,5 @@
import { useTranslation } from 'react-i18next'
import MaterialIcon from '@/shared/components/material-icon'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
type CloseProps = {
onDismiss: React.MouseEventHandler<HTMLButtonElement>
@@ -16,15 +15,10 @@ function Close({ onDismiss, variant = 'light' }: CloseProps) {
className={`close pull-right ${variant}`}
onClick={onDismiss}
>
<BootstrapVersionSwitcher
bs3={<span aria-hidden="true">&times;</span>}
bs5={
<MaterialIcon
type="close"
className="align-text-bottom"
accessibilityLabel={t('close')}
/>
}
<MaterialIcon
type="close"
className="align-text-bottom"
accessibilityLabel={t('close')}
/>
<span className="sr-only">{t('close')}</span>
</button>
@@ -1,58 +0,0 @@
import React, {
Children,
cloneElement,
type FC,
isValidElement,
useCallback,
} from 'react'
import { Dropdown, DropdownProps } from 'react-bootstrap'
import useDropdown from '../hooks/use-dropdown'
type ControlledDropdownProps = DropdownProps & {
defaultOpen?: boolean
onMainButtonClick?: (dropdownOpen: boolean) => void
}
const ControlledDropdown: FC<ControlledDropdownProps> = ({
defaultOpen,
onMainButtonClick,
...props
}) => {
const { onClick, ...dropdownProps } = useDropdown(Boolean(defaultOpen))
const handleClick = useCallback(
(e: React.MouseEvent<Dropdown, MouseEvent>) => {
onClick(e)
if (onMainButtonClick) {
onMainButtonClick(dropdownProps.open)
}
},
[onClick, onMainButtonClick, dropdownProps.open]
)
return (
<Dropdown {...props} {...dropdownProps} onClick={handleClick}>
{Children.map(props.children, child => {
if (!isValidElement(child)) {
return child
}
// Dropdown.Menu
if ('open' in child.props) {
return cloneElement<any>(child, { open: dropdownProps.open })
}
// Overlay
if ('show' in child.props) {
return cloneElement<any>(child, { show: dropdownProps.open })
}
// anything else
return cloneElement(child)
})}
</Dropdown>
)
}
export default ControlledDropdown
@@ -3,7 +3,6 @@ import { useTranslation } from 'react-i18next'
import OLButton from '@/features/ui/components/ol/ol-button'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
import { bsVersion } from '@/features/utils/bootstrap-5'
export const CopyToClipboard = memo<{
content: string
@@ -39,7 +38,6 @@ export const CopyToClipboard = memo<{
size="sm"
variant="secondary"
className="copy-button"
bs3Props={{ bsSize: 'xsmall' }}
>
{t('copy')}
</OLButton>
@@ -50,11 +48,7 @@ export const CopyToClipboard = memo<{
size="sm"
accessibilityLabel={t('copy')}
className="copy-button"
bs3Props={{ bsSize: 'xsmall' }}
icon={bsVersion({
bs5: copied ? 'check' : 'content_copy',
bs3: copied ? 'check' : 'clipboard',
})}
icon={copied ? 'check' : 'content_copy'}
/>
)}
</OLTooltip>
@@ -1,9 +1,7 @@
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import Icon from './icon'
import { useLocation } from '../hooks/use-location'
import { DefaultMessage } from './default-message'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import MaterialIcon from './material-icon'
import OLButton from '@/features/ui/components/ol/ol-button'
@@ -13,25 +11,12 @@ export const GenericErrorBoundaryFallback: FC = ({ children }) => {
return (
<div className="error-boundary-container">
<BootstrapVersionSwitcher
bs3={
<Icon
accessibilityLabel={`${t('generic_something_went_wrong')} ${t(
'please_refresh'
)}`}
type="exclamation-triangle fa-2x"
fw
/>
}
bs5={
<MaterialIcon
accessibilityLabel={`${t('generic_something_went_wrong')} ${t(
'please_refresh'
)}`}
type="warning"
size="2x"
/>
}
<MaterialIcon
accessibilityLabel={`${t('generic_something_went_wrong')} ${t(
'please_refresh'
)}`}
type="warning"
size="2x"
/>
{children || (
<div className="error-message">
@@ -1,5 +1,4 @@
import classNames from 'classnames'
import { bsVersion } from '@/features/utils/bootstrap-5'
type IconOwnProps = {
type: string
@@ -36,9 +35,7 @@ function Icon({
<>
<i className={iconClassName} aria-hidden="true" {...rest} />
{accessibilityLabel && (
<span className={bsVersion({ bs5: 'visually-hidden', bs3: 'sr-only' })}>
{accessibilityLabel}
</span>
<span className="visually-hidden">{accessibilityLabel}</span>
)}
</>
)
@@ -5,7 +5,6 @@ import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import { postJSON } from '@/infrastructure/fetch-json'
import OLButton from '@/features/ui/components/ol/ol-button'
import getMeta from '@/utils/meta'
import { isBootstrap5 } from '@/features/utils/bootstrap-5'
type IntegrationLinkingWidgetProps = {
logo: ReactNode
@@ -117,17 +116,14 @@ function ActionButton({
</OLButton>
)
} else if (disabled) {
const tooltipableButton = isBootstrap5() ? (
const tooltipableButton = (
<div className="d-inline-block">
<OLButton variant="primary" disabled>
{t('turn_on')}
</OLButton>
</div>
) : (
<OLButton variant="primary" disabled>
{t('turn_on')}
</OLButton>
)
return (
<OLTooltip
id="experiment-disabled"
@@ -3,7 +3,6 @@ import { useEffect, useState } from 'react'
import OLSpinner, {
OLSpinnerSize,
} from '@/features/ui/components/ol/ol-spinner'
import { isBootstrap5 } from '@/features/utils/bootstrap-5'
import classNames from 'classnames'
function LoadingSpinner({
@@ -43,12 +42,14 @@ function LoadingSpinner({
return null
}
const extraClasses = isBootstrap5()
? [align === 'left' ? 'align-items-start' : 'align-items-center']
: null
return (
<div className={classNames('loading', className, extraClasses)}>
<div
className={classNames(
'loading',
className,
align === 'left' ? 'align-items-start' : 'align-items-center'
)}
>
<OLSpinner size={size} />
&nbsp;
{loadingText || `${t('loading')}`}
@@ -1,6 +1,5 @@
import classNames from 'classnames'
import React from 'react'
import { bsVersion } from '@/features/utils/bootstrap-5'
import unfilledIconTypes from '../../../fonts/material-symbols/unfilled-symbols.mjs'
export type AvailableUnfilledIcon = (typeof unfilledIconTypes)[number]
@@ -43,9 +42,7 @@ function MaterialIcon({
{type}
</span>
{accessibilityLabel && (
<span className={bsVersion({ bs5: 'visually-hidden', bs3: 'sr-only' })}>
{accessibilityLabel}
</span>
<span className="visually-hidden">{accessibilityLabel}</span>
)}
</>
)
@@ -10,9 +10,7 @@ import {
} from 'react'
import classNames from 'classnames'
import { useSelect } from 'downshift'
import Icon from './icon'
import { useTranslation } from 'react-i18next'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { Form, Spinner } from 'react-bootstrap-5'
import FormControl from '@/features/ui/components/bootstrap-5/form/form-control'
import MaterialIcon from '@/shared/components/material-icon'
@@ -150,150 +148,73 @@ export const Select = <T,>({
} else {
value = defaultText
}
return (
<BootstrapVersionSwitcher
bs3={
<div className="select-wrapper" ref={rootRef}>
<div>
{label ? (
<label {...getLabelProps()}>
{label}{' '}
{optionalLabel && (
<span className="select-optional-label text-muted">
({t('optional')})
</span>
)}{' '}
{loading && (
<Icon data-testid="spinner" fw type="spinner" spin />
)}
</label>
) : null}
<div
className={classNames({ disabled }, 'select-trigger')}
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
tabIndex={0}
{...getToggleButtonProps({
disabled,
onKeyDown,
})}
>
<div>{value}</div>
<div>
{isOpen ? (
<Icon type="chevron-up" fw />
) : (
<Icon type="chevron-down" fw />
)}
</div>
</div>
</div>
<ul
className={classNames({ hidden: !isOpen }, 'select-items')}
{...getMenuProps({ disabled, onKeyDown: handleMenuKeyDown })}
>
{isOpen &&
items?.map((item, index) => {
const isDisabled = itemToDisabled && itemToDisabled(item)
return (
<li
className={classNames({
'select-highlighted': highlightedIndex === index,
'selected-active': selectedItem === item,
'select-icon': selectedIcon,
'select-disabled': isDisabled,
})}
key={itemToKey(item)}
{...getItemProps({ item, index, disabled: isDisabled })}
>
<span className="select-item-title">
{selectedIcon && (
<div className="select-item-icon">
{(selectedItem === item ||
(!selectedItem && defaultItem === item)) && (
<Icon type="check" fw />
)}
</div>
)}
{itemToString(item)}
</span>
{itemToSubtitle ? (
<span className="text-muted select-item-subtitle">
{itemToSubtitle(item)}
</span>
) : null}
</li>
)
})}
</ul>
</div>
}
bs5={
<div className="select-wrapper" ref={rootRef}>
{label ? (
<Form.Label {...getLabelProps()}>
{label}{' '}
{optionalLabel && (
<span className="fw-normal">({t('optional')})</span>
)}{' '}
{loading && (
<span data-testid="spinner">
<Spinner
animation="border"
aria-hidden="true"
as="span"
role="status"
size="sm"
/>
</span>
)}
</Form.Label>
) : null}
<FormControl
data-testid={dataTestId}
{...getToggleButtonProps({
disabled,
onKeyDown,
className: 'select-trigger',
})}
value={value}
readOnly
append={
<MaterialIcon
type={isOpen ? 'keyboard_arrow_up' : 'keyboard_arrow_down'}
className="align-text-bottom"
return (
<div className="select-wrapper" ref={rootRef}>
{label ? (
<Form.Label {...getLabelProps()}>
{label}{' '}
{optionalLabel && (
<span className="fw-normal">({t('optional')})</span>
)}{' '}
{loading && (
<span data-testid="spinner">
<Spinner
animation="border"
aria-hidden="true"
as="span"
role="status"
size="sm"
/>
}
</span>
)}
</Form.Label>
) : null}
<FormControl
data-testid={dataTestId}
{...getToggleButtonProps({
disabled,
onKeyDown,
className: 'select-trigger',
})}
value={value}
readOnly
append={
<MaterialIcon
type={isOpen ? 'keyboard_arrow_up' : 'keyboard_arrow_down'}
className="align-text-bottom"
/>
<ul
{...getMenuProps({ disabled, onKeyDown: handleMenuKeyDown })}
className={classNames('dropdown-menu w-100', { show: isOpen })}
>
{isOpen &&
items?.map((item, index) => {
const isDisabled = itemToDisabled && itemToDisabled(item)
return (
<li role="none" key={itemToKey(item)}>
<DropdownItem
as="button"
className={classNames({
'select-highlighted': highlightedIndex === index,
})}
active={selectedItem === item}
trailingIcon={selectedItem === item ? 'check' : undefined}
description={
itemToSubtitle ? itemToSubtitle(item) : undefined
}
{...getItemProps({ item, index, disabled: isDisabled })}
>
{itemToString(item)}
</DropdownItem>
</li>
)
})}
</ul>
</div>
}
/>
}
/>
<ul
{...getMenuProps({ disabled, onKeyDown: handleMenuKeyDown })}
className={classNames('dropdown-menu w-100', { show: isOpen })}
>
{isOpen &&
items?.map((item, index) => {
const isDisabled = itemToDisabled && itemToDisabled(item)
return (
<li role="none" key={itemToKey(item)}>
<DropdownItem
as="button"
className={classNames({
'select-highlighted': highlightedIndex === index,
})}
active={selectedItem === item}
trailingIcon={
selectedIcon && selectedItem === item ? 'check' : undefined
}
description={
itemToSubtitle ? itemToSubtitle(item) : undefined
}
{...getItemProps({ item, index, disabled: isDisabled })}
>
{itemToString(item)}
</DropdownItem>
</li>
)
})}
</ul>
</div>
)
}
@@ -1,56 +0,0 @@
import { useTranslation } from 'react-i18next'
import { Label } from 'react-bootstrap'
import { MergeAndOverride } from '../../../../types/utils'
import classnames from 'classnames'
type TagProps = MergeAndOverride<
React.ComponentProps<'span'>,
{
prepend?: React.ReactNode
children: React.ReactNode
contentProps?: React.ComponentProps<'button'>
closeBtnProps?: React.ComponentProps<'button'>
className?: string
bsStyle?: React.ComponentProps<typeof Label>['bsStyle'] | null
}
>
function Tag({
prepend,
children,
contentProps,
closeBtnProps,
bsStyle,
className,
...rest
}: TagProps) {
const { t } = useTranslation()
return (
<span className={classnames('badge-tag-bs3', className)} {...rest}>
<span
{...contentProps}
className={classnames(
'badge-tag-bs3-content-wrapper',
{ clickable: Boolean(contentProps?.onClick) },
contentProps?.className
)}
>
{prepend && <span className="badge-tag-bs3-prepend">{prepend}</span>}
<span className="badge-tag-bs3-content">{children}</span>
</span>
{closeBtnProps && (
<button
type="button"
className="badge-tag-bs3-close"
aria-label={t('remove_tag', { tagName: children })}
{...closeBtnProps}
>
<span aria-hidden="true">&times;</span>
</button>
)}
</span>
)
}
export default Tag
@@ -1,57 +0,0 @@
import { cloneElement } from 'react'
import {
OverlayTrigger,
OverlayTriggerProps,
Tooltip as BSTooltip,
} from 'react-bootstrap'
import { callFnsInSequence } from '../../utils/functions'
type OverlayProps = Omit<OverlayTriggerProps, 'overlay'> & {
shouldUpdatePosition?: boolean // Not officially documented https://stackoverflow.com/a/43138470
}
export type TooltipProps = {
description: React.ReactNode
id: string
overlayProps?: OverlayProps
tooltipProps?: BSTooltip.TooltipProps
hidden?: boolean
children: React.ReactElement
}
function Tooltip({
id,
description,
children,
tooltipProps,
overlayProps,
hidden,
}: TooltipProps) {
const hideTooltip = (e: React.MouseEvent) => {
if (e.currentTarget instanceof HTMLElement) {
e.currentTarget.blur()
}
}
return (
<OverlayTrigger
overlay={
<BSTooltip
id={`${id}-tooltip`}
{...tooltipProps}
style={{ display: hidden ? 'none' : 'block' }}
>
{description}
</BSTooltip>
}
{...overlayProps}
placement={overlayProps?.placement || 'top'}
>
{cloneElement(children, {
onClick: callFnsInSequence(children.props.onClick, hideTooltip),
})}
</OverlayTrigger>
)
}
export default Tooltip
@@ -1,16 +1,9 @@
import Icon from './icon'
import MaterialIcon from '@/shared/components/material-icon'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { useTranslation } from 'react-i18next'
import { memo } from 'react'
function Check() {
return (
<BootstrapVersionSwitcher
bs3={<Icon type="check" />}
bs5={<MaterialIcon type="check" />}
/>
)
return <MaterialIcon type="check" />
}
function UpgradeBenefits() {
@@ -3,40 +3,10 @@ import useFetchMock from './hooks/use-fetch-mock'
import ContactUsModal from '../../modules/support/frontend/js/components/contact-us-modal'
import fixedHelpSuggestionSearch from '../../modules/support/test/frontend/util/fixed-help-suggestion-search'
import { ScopeDecorator } from './decorators/scope'
import { StoryObj } from '@storybook/react'
import OLButton from '@/features/ui/components/ol/ol-button'
import { bsVersionDecorator } from '../../.storybook/utils/with-bootstrap-switcher'
type Story = StoryObj<typeof ContactUsModal>
type ContactUsModalProps = ComponentProps<typeof ContactUsModal>
function bootstrap3Story(render: Story['render']): Story {
return {
render,
decorators: [
story => {
return ScopeDecorator(story)
},
],
}
}
function bootstrap5Story(render: Story['render']): Story {
return {
render,
decorators: [
story => {
return ScopeDecorator(story, undefined, {
'ol-bootstrapVersion': 5,
})
},
],
parameters: {
bootstrap5: true,
},
}
}
function GenericContactUsModal(args: ContactUsModalProps) {
useFetchMock(fetchMock => {
fetchMock.post('/support', { status: 200 }, { delay: 1000 })
@@ -50,13 +20,9 @@ function GenericContactUsModal(args: ContactUsModalProps) {
)
}
export const Generic: Story = bootstrap3Story(args => (
export const Generic = (args: ContactUsModalProps) => (
<GenericContactUsModal {...args} />
))
export const GenericBootstrap5: Story = bootstrap5Story(args => (
<GenericContactUsModal {...args} />
))
)
const ContactUsModalWithRequestError = (args: ContactUsModalProps) => {
useFetchMock(fetchMock => {
@@ -71,18 +37,10 @@ const ContactUsModalWithRequestError = (args: ContactUsModalProps) => {
)
}
const renderContactUsModalWithRequestError = (args: ContactUsModalProps) => (
export const RequestError = (args: ContactUsModalProps) => (
<ContactUsModalWithRequestError {...args} />
)
export const RequestError: Story = bootstrap3Story(
renderContactUsModalWithRequestError
)
export const RequestErrorBootstrap5: Story = bootstrap5Story(
renderContactUsModalWithRequestError
)
const ContactUsModalWithAcknowledgement = (
args: Omit<ContactUsModalProps, 'show' | 'handleHide'>
) => {
@@ -110,19 +68,11 @@ const ContactUsModalWithAcknowledgement = (
)
}
const renderContactUsModalWithAcknowledgement = (args: ContactUsModalProps) => {
export const WithAcknowledgement = (args: ContactUsModalProps) => {
const { show, handleHide, ...rest } = args
return <ContactUsModalWithAcknowledgement {...rest} />
}
export const WithAcknowledgement: Story = bootstrap3Story(
renderContactUsModalWithAcknowledgement
)
export const WithAcknowledgementBootstrap5: Story = bootstrap5Story(
renderContactUsModalWithAcknowledgement
)
export default {
title: 'Shared / Modals / Contact Us',
component: ContactUsModal,
@@ -133,6 +83,6 @@ export default {
},
argTypes: {
handleHide: { action: 'close modal' },
...bsVersionDecorator.argTypes,
},
decorators: [ScopeDecorator],
}
@@ -1,45 +0,0 @@
import { Dropdown, MenuItem } from 'react-bootstrap'
import ControlledDropdown from '../js/shared/components/controlled-dropdown'
export const Customized = args => {
return (
<ControlledDropdown
pullRight={args.pullRight}
defaultOpen={args.defaultOpen}
id="dropdown-story"
>
<Dropdown.Toggle
noCaret={args.noCaret}
className={args.className}
bsStyle={args.bsStyle}
>
{args.title}
</Dropdown.Toggle>
<Dropdown.Menu>
<MenuItem eventKey="1">Action</MenuItem>
<MenuItem eventKey="2">Another action</MenuItem>
<MenuItem eventKey="3" active>
Active Item
</MenuItem>
<MenuItem divider />
<MenuItem eventKey="4">Separated link</MenuItem>
</Dropdown.Menu>
</ControlledDropdown>
)
}
Customized.args = {
title: 'Toggle & Menu used separately',
}
export default {
title: 'Shared / Components / Dropdown',
component: ControlledDropdown,
args: {
bsStyle: 'default',
title: 'Dropdown',
pullRight: false,
noCaret: false,
className: '',
defaultOpen: true,
},
}
@@ -1,6 +1,5 @@
import { ScopeDecorator } from './decorators/scope'
import { FeedbackBadge } from '@/shared/components/feedback-badge'
import { bsVersionDecorator } from '../../.storybook/utils/with-bootstrap-switcher'
export const WithDefaultText = () => {
return (
@@ -33,7 +32,4 @@ export default {
title: 'Shared / Components / Feedback Badge',
component: FeedbackBadge,
decorators: [ScopeDecorator],
argTypes: {
...bsVersionDecorator.argTypes,
},
}
@@ -1,5 +1,4 @@
import OLFormSwitch from '@/features/ui/components/ol/ol-form-switch'
import { bsVersionDecorator } from '../../.storybook/utils/with-bootstrap-switcher'
import { disableControlsOf } from './utils/arg-types'
export const Unchecked = () => {
@@ -22,10 +21,6 @@ export default {
title: 'Shared / Components / Input Switch',
component: OLFormSwitch,
argTypes: {
...bsVersionDecorator.argTypes,
...disableControlsOf('inputRef', 'bs3Props'),
},
args: {
...bsVersionDecorator.args,
...disableControlsOf('inputRef'),
},
}
@@ -1,7 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react'
import { LoadingUI } from '@/features/ide-react/components/loading'
import { EditorProviders } from '../../../test/frontend/helpers/editor-providers'
import { bsVersionDecorator } from '../../../.storybook/utils/with-bootstrap-switcher'
import { PartialMeta } from '@/utils/meta'
const meta: Meta<typeof LoadingUI> = {
@@ -19,7 +18,6 @@ const meta: Meta<typeof LoadingUI> = {
],
},
progress: { control: { type: 'range', min: 0, max: 100 } },
...bsVersionDecorator.argTypes,
},
}
@@ -30,9 +30,6 @@ const meta: Meta<typeof MenuBar> = {
title: 'Shared / Components / MenuBar',
component: MenuBar,
argTypes: {},
parameters: {
bootstrap5: true,
},
}
export default meta
@@ -1,18 +1,11 @@
import { Meta, StoryObj } from '@storybook/react'
import { UnsavedDocsLockedAlert } from '@/features/ide-react/components/unsaved-docs/unsaved-docs-locked-alert'
import { ScopeDecorator } from '../decorators/scope'
import { bsVersionDecorator } from '../../../.storybook/utils/with-bootstrap-switcher'
export default {
title: 'Editor / Modals / Unsaved Docs Locked',
component: UnsavedDocsLockedAlert,
decorators: [Story => ScopeDecorator(Story)],
argTypes: {
...bsVersionDecorator.argTypes,
},
parameters: {
bootstrap5: true,
},
} satisfies Meta
type Story = StoryObj<typeof UnsavedDocsLockedAlert>
@@ -1,6 +1,6 @@
import { useCallback, useMemo, useState } from 'react'
import useFetchMock from './hooks/use-fetch-mock'
import { Button } from 'react-bootstrap'
import OLButton from '@/features/ui/components/ol/ol-button'
import PdfPreviewPane from '../js/features/pdf-preview/components/pdf-preview-pane'
import PdfPreview from '../js/features/pdf-preview/components/pdf-preview'
import PdfFileList from '../js/features/pdf-preview/components/pdf-file-list'
@@ -123,10 +123,12 @@ export const Interactive = () => {
margin: '10px 0',
}}
>
<Button onClick={dispatchDocChanged}>trigger doc change</Button>
<Button onClick={toggleLintingError}>
<OLButton onClick={dispatchDocChanged}>
trigger doc change
</OLButton>
<OLButton onClick={toggleLintingError}>
toggle linting error
</Button>
</OLButton>
</div>
</div>
</div>
@@ -26,7 +26,4 @@ export const Add = (args: any) => {
export default {
title: 'Project List / Affiliation',
component: AddAffiliation,
parameters: {
bootstrap5: true,
},
}
@@ -12,7 +12,4 @@ export const Select = (args: any) => {
export default {
title: 'Project List / Color Picker',
component: ColorPicker,
parameters: {
bootstrap5: true,
},
}
@@ -62,7 +62,4 @@ export default {
</div>
),
],
parameters: {
bootstrap5: true,
},
}
@@ -72,7 +72,4 @@ export const PausedPlan = (args: any) => {
export default {
title: 'Project List / Current Plan Widget',
component: CurrentPlanWidget,
parameters: {
bootstrap5: true,
},
}
@@ -7,7 +7,4 @@ export const Default = () => {
export default {
title: 'Project List / INR Banner',
component: INRBanner,
parameters: {
bootstrap5: true,
},
}
@@ -104,7 +104,4 @@ export const Error = () => {
export default {
title: 'Project List / New Project Button',
component: NewProjectButton,
parameters: {
bootstrap5: true,
},
}
@@ -343,7 +343,4 @@ export const ReconfirmedAffiliationSuccess = (args: any) => {
export default {
title: 'Project List / Notifications',
component: UserNotifications,
parameters: {
bootstrap5: true,
},
}
@@ -57,7 +57,4 @@ export default {
</div>
),
],
parameters: {
bootstrap5: true,
},
}
@@ -24,7 +24,4 @@ export default {
args: {
inputValue: '',
},
parameters: {
bootstrap5: true,
},
}
@@ -28,7 +28,4 @@ export const EmptySurvey = (args: any) => {
export default {
title: 'Project List / Survey Widget',
component: SurveyWidget,
parameters: {
bootstrap5: true,
},
}

Some files were not shown because too many files have changed in this diff Show More