-
-
+
-
-
+
+
{position !== null && (
@@ -437,36 +456,29 @@ const CodeMirrorSearchForm: FC = () => {
{showReplace && (
-
+
-
+
)}
-
{t('close')} (Esc)>}>
-
-
+
{t('close')} (Esc)>}>
+ closeSearchPanel(view)} />
+
)
diff --git a/services/web/frontend/js/features/source-editor/extensions/search.ts b/services/web/frontend/js/features/source-editor/extensions/search.ts
index 1a5d15c4c4..c27dd3692d 100644
--- a/services/web/frontend/js/features/source-editor/extensions/search.ts
+++ b/services/web/frontend/js/features/source-editor/extensions/search.ts
@@ -257,21 +257,29 @@ export const search = () => {
const searchFormTheme = EditorView.theme({
'.ol-cm-search-form': {
- padding: '10px',
+ '--ol-cm-search-form-gap': '10px',
+ '--ol-cm-search-form-button-margin': '3px',
+ padding: 'var(--ol-cm-search-form-gap)',
display: 'flex',
- gap: '10px',
- background: 'var(--ol-blue-gray-1)',
+ gap: 'var(--ol-cm-search-form-gap)',
+ background: 'var(--neutral-20)',
'--ol-cm-search-form-focus-shadow':
'inset 0 1px 1px rgb(0 0 0 / 8%), 0 0 8px rgb(102 175 233 / 60%)',
'--ol-cm-search-form-error-shadow':
- 'inset 0 1px 1px rgb(0 0 0 / 8%), 0 0 8px var(--input-shadow-danger-color)',
+ 'inset 0 1px 1px rgb(0 0 0 / 8%), 0 0 8px var(--red-50)',
containerType: 'inline-size',
},
+ '&.bootstrap-5 .ol-cm-search-form': {
+ '--ol-cm-search-form-gap': 'var(--spacing-05)',
+ '--ol-cm-search-form-button-margin': 'var(--spacing-02)',
+ '--input-border': 'var(--border-primary)',
+ '--input-border-focus': 'var(--border-active)',
+ },
'.ol-cm-search-controls': {
display: 'grid',
gridTemplateColumns: 'auto auto',
gridTemplateRows: 'auto auto',
- gap: '10px',
+ gap: 'var(--ol-cm-search-form-gap)',
},
'@container (max-width: 450px)': {
'.ol-cm-search-controls': {
@@ -280,12 +288,12 @@ const searchFormTheme = EditorView.theme({
},
'.ol-cm-search-form-row': {
display: 'flex',
- gap: '10px',
+ gap: 'var(--ol-cm-search-form-gap)',
justifyContent: 'space-between',
},
'.ol-cm-search-form-group': {
display: 'flex',
- gap: '10px',
+ gap: 'var(--ol-cm-search-form-gap)',
alignItems: 'center',
},
'.ol-cm-search-input-group': {
@@ -294,6 +302,8 @@ const searchFormTheme = EditorView.theme({
background: 'white',
width: '100%',
maxWidth: '25em',
+ display: 'inline-flex',
+ alignItems: 'center',
'& input[type="text"]': {
background: 'none',
boxShadow: 'none',
@@ -303,18 +313,18 @@ const searchFormTheme = EditorView.theme({
boxShadow: 'none',
},
'& .btn.btn': {
- background: 'var(--ol-blue-gray-0)',
- color: 'var(--ol-blue-gray-3)',
+ background: 'var(--neutral-10)',
+ color: 'var(--neutral-60)',
borderRadius: '50%',
height: '2em',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
width: '2em',
- marginRight: '3px',
+ marginRight: 'var(--ol-cm-search-form-button-margin)',
'&.checked': {
- color: '#fff',
- backgroundColor: 'var(--ol-blue)',
+ color: 'var(--white)',
+ backgroundColor: 'var(--blue-50)',
},
'&:active': {
boxShadow: 'none',
@@ -331,7 +341,7 @@ const searchFormTheme = EditorView.theme({
boxShadow: 'var(--ol-cm-search-form-error-shadow)',
},
},
- '.input-group .ol-cm-search-form-input': {
+ '.ol-cm-search-form-input': {
border: 'none',
},
'.ol-cm-search-input-button': {
@@ -355,7 +365,9 @@ const searchFormTheme = EditorView.theme({
left: '-10000px',
},
'.ol-cm-search-form-close': {
- flex: 1,
+ marginLeft: 'auto',
+ display: 'flex',
+ alignItems: 'start',
},
'.ol-cm-search-replace-input': {
order: 3,
diff --git a/services/web/frontend/js/features/source-editor/extensions/theme.ts b/services/web/frontend/js/features/source-editor/extensions/theme.ts
index 105bb9efd0..4988743f00 100644
--- a/services/web/frontend/js/features/source-editor/extensions/theme.ts
+++ b/services/web/frontend/js/features/source-editor/extensions/theme.ts
@@ -2,6 +2,7 @@ import { EditorView } from '@codemirror/view'
import { Annotation, Compartment, TransactionSpec } from '@codemirror/state'
import { syntaxHighlighting } from '@codemirror/language'
import { classHighlighter } from './class-highlighter'
+import classNames from 'classnames'
const optionsThemeConf = new Compartment()
const selectedThemeConf = new Compartment()
@@ -16,6 +17,7 @@ type Options = {
fontFamily: FontFamily
lineHeight: LineHeight
overallTheme: OverallTheme
+ bootstrapVersion: 3 | 5
}
export const theme = (options: Options) => [
@@ -67,13 +69,17 @@ const createThemeFromOptions = ({
fontFamily = 'monaco',
lineHeight = 'normal',
overallTheme = '',
+ bootstrapVersion = 3,
}: Options) => {
/**
* Theme styles that depend on settings.
*/
return [
EditorView.editorAttributes.of({
- class: overallTheme === '' ? 'overall-theme-dark' : 'overall-theme-light',
+ class: classNames(
+ overallTheme === '' ? 'overall-theme-dark' : 'overall-theme-light',
+ 'bootstrap-' + bootstrapVersion
+ ),
style: Object.entries({
'--font-size': `${fontSize}px`,
'--source-font-family': fontFamilies[fontFamily]?.join(', '),
diff --git a/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts b/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts
index d44be37b3b..5ee52286bd 100644
--- a/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts
+++ b/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts
@@ -66,6 +66,7 @@ import { useRangesContext } from '@/features/review-panel-new/context/ranges-con
import { updateRanges } from '@/features/source-editor/extensions/ranges'
import { useThreadsContext } from '@/features/review-panel-new/context/threads-context'
import { useHunspell } from '@/features/source-editor/hooks/use-hunspell'
+import { isBootstrap5 } from '@/features/utils/bootstrap-5'
function useCodeMirrorScope(view: EditorView) {
const { fileTreeData } = useFileTreeData()
@@ -141,6 +142,7 @@ function useCodeMirrorScope(view: EditorView) {
lineHeight,
overallTheme,
editorTheme,
+ bootstrapVersion: 3 as 3 | 5,
})
useEffect(() => {
@@ -150,6 +152,7 @@ function useCodeMirrorScope(view: EditorView) {
lineHeight,
overallTheme,
editorTheme,
+ bootstrapVersion: isBootstrap5() ? 5 : 3,
}
view.dispatch(
@@ -158,6 +161,7 @@ function useCodeMirrorScope(view: EditorView) {
fontSize,
lineHeight,
overallTheme,
+ bootstrapVersion: themeRef.current.bootstrapVersion,
})
)
diff --git a/services/web/frontend/js/features/ui/components/ol/ol-close-button.tsx b/services/web/frontend/js/features/ui/components/ol/ol-close-button.tsx
new file mode 100644
index 0000000000..7034392949
--- /dev/null
+++ b/services/web/frontend/js/features/ui/components/ol/ol-close-button.tsx
@@ -0,0 +1,35 @@
+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'
+
+const OLCloseButton = forwardRef
(
+ (props, ref) => {
+ const { t } = useTranslation()
+
+ const bs3CloseButtonProps: React.ButtonHTMLAttributes = {
+ className: classNames('close', props.className),
+ onClick: props.onClick,
+ onMouseOver: props.onMouseOver,
+ onMouseOut: props.onMouseOut,
+
+ 'aria-label': t('close'),
+ }
+
+ return (
+
+ ×
+
+ }
+ bs5={}
+ />
+ )
+ }
+)
+
+OLCloseButton.displayName = 'OLCloseButton'
+
+export default OLCloseButton
diff --git a/services/web/frontend/js/features/ui/components/ol/ol-form-control.tsx b/services/web/frontend/js/features/ui/components/ol/ol-form-control.tsx
index 1b9ea18ed8..3aba265cac 100644
--- a/services/web/frontend/js/features/ui/components/ol/ol-form-control.tsx
+++ b/services/web/frontend/js/features/ui/components/ol/ol-form-control.tsx
@@ -1,4 +1,4 @@
-import { forwardRef, ComponentProps } from 'react'
+import { forwardRef, ComponentProps, useCallback } from 'react'
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
import FormControl from '@/features/ui/components/bootstrap-5/form/form-control'
import BS3FormControl from '@/features/ui/components/bootstrap-3/form/form-control'
@@ -15,8 +15,22 @@ const OLFormControl = forwardRef(
(props, ref) => {
const { bs3Props, ...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,
@@ -37,13 +51,6 @@ const OLFormControl = forwardRef(
onFocus: rest.onFocus as BS3FormControlProps['onFocus'],
onBlur: rest.onBlur as BS3FormControlProps['onBlur'],
onInvalid: rest.onInvalid as BS3FormControlProps['onInvalid'],
- inputRef: (inputElement: HTMLInputElement) => {
- if (typeof ref === 'function') {
- ref(inputElement)
- } else if (ref) {
- ref.current = inputElement
- }
- },
prepend: rest.prepend,
append: rest.append,
...bs3Props,