ui(components): New Dialog and Modal components for ui-next (#4772)
Co-authored-by: sedghi <ar.sedghi@gmail.com>
This commit is contained in:
1 parent
62b4fa3e89
commit
a3b51aa8db
90 files changed
+3459
-1357
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 169 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 332 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 298 KiB |
@@ -0,0 +1,314 @@
|
||||
---
|
||||
title: uiDialogService
|
||||
---
|
||||
|
||||
|
||||
## DialogService
|
||||
|
||||
This guide details the migration steps for the `uiDialogService` API changes, based on the provided diff. The most significant change is a shift from methods like `.create()` and `.dismiss()` to `.show()` and `.hide()`, alongside structural changes in how dialogs are defined and rendered. The changes aim for a more streamlined and flexible dialog management, leveraging React context for state management.
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* **`uiDialogService.create()` and `uiDialogService.dismiss()` are deprecated.** They have been replaced with `uiDialogService.show()` and `uiDialogService.hide()`. This change
|
||||
makes it consistent with the `uiModalService` and `uiNotificationService` APIs.
|
||||
* **`content` property now expects a React Component Type**, not an instance. Props for the content component are passed via `contentProps`.
|
||||
* **The `dialogId` is now consistently passed as `id` within the options** to `uiDialogService.show()`.
|
||||
|
||||
|
||||
### Props Kept same as before
|
||||
|
||||
| Prop | Description |
|
||||
|------|-------------|
|
||||
| `id` | Still required, but we don't return it from the `show` method anymore. |
|
||||
| `content` | This is now expected to be a *React component type* (a function or class that returns JSX) |
|
||||
| `contentProps` | This continues to be the way to pass data *to* your custom dialog component. However, several specific props that *used* to be passed here (like `onClose`, `actions`) are no longer valid. |
|
||||
| `isDraggable` | Controls whether the dialog can be moved by dragging. |
|
||||
| `defaultPosition` | Allows you to specify an initial `{ x, y }` position for the dialog. |
|
||||
| `title` | The title text to display in the dialog header. |
|
||||
| `showOverlay` | default true - if the dialog is draggable the overlay is not shown by default |
|
||||
|
||||
|
||||
### Removed Props:
|
||||
|
||||
| Prop | Description |
|
||||
|------|-------------|
|
||||
| `centralize` | Dialogs are now centered by default via CSS if you don't want center you pass defaultPosition |
|
||||
| `preservePosition` | Work in progress and will be available in future |
|
||||
| `contentDimensions` | Removed - should be specified directly in dialogs |
|
||||
| `onStart` | Removed |
|
||||
| `onDrag` | Removed |
|
||||
| `onStop` | Removed |
|
||||
| `onClickOutside` | Removed - if you want to close the dialog on click outside, you can use the `shouldCloseOnOverlayClick` prop |
|
||||
|
||||
|
||||
### Renamed Props:
|
||||
|
||||
| Prop | Description |
|
||||
|------|-------------|
|
||||
| `containerDimensions` | renamed to `containerClassName` |
|
||||
|
||||
|
||||
|
||||
|
||||
### New Props:
|
||||
|
||||
| Prop | Description |
|
||||
|------|-------------|
|
||||
| `unstyled` | A boolean prop to render the dialog without the default styling. It is used for context menu dialogs |
|
||||
| `shouldCloseOnEsc` | Default off for dialogs - Controls whether pressing the Escape key will close the dialog. |
|
||||
| `shouldCloseOnOverlayClick` | Default off for dialogs - Controls whether clicking the overlay background will close the dialog. |
|
||||
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Replace `.create()` with `.show()`:**
|
||||
|
||||
```diff
|
||||
- const dialogId = uiDialogService.create({
|
||||
- id: 'my-dialog',
|
||||
- content: MyDialogComponent,
|
||||
- contentProps: { prop1: 'value1' },
|
||||
- // ... other options ...
|
||||
- });
|
||||
|
||||
+ uiDialogService.show({
|
||||
+ id: 'my-dialog',
|
||||
+ content: MyDialogComponent,
|
||||
+ contentProps: { prop1: 'value1' },
|
||||
+ // ... other options ...
|
||||
+ });
|
||||
```
|
||||
|
||||
2. Rename `containerDimensions` to `containerClassName`
|
||||
|
||||
```diff
|
||||
- containerDimensions: 'w-[70%] max-w-[900px]',
|
||||
+ containerClassName: 'w-[70%] max-w-[900px]',
|
||||
```
|
||||
|
||||
3. **Replace `.dismiss({ id: dialogId })` with `.hide(dialogId)`:**
|
||||
|
||||
```diff
|
||||
- uiDialogService.dismiss({ id: dialogId });
|
||||
|
||||
+ uiDialogService.hide(dialogId);
|
||||
```
|
||||
|
||||
4. **Replace `dismissAll` with `hideAll`**
|
||||
```diff
|
||||
- uiDialogService.dismissAll();
|
||||
+ uiDialogService.hideAll();
|
||||
```
|
||||
5. **Update Dialog Content:**
|
||||
|
||||
* Ensure your dialog content is defined as a React component (functional or class-based).
|
||||
* Pass props to the component via `contentProps`.
|
||||
* You don't need to pass in `onClose` or `hide` as they are now handled passed in automatically.
|
||||
|
||||
```javascript
|
||||
// Example: MyDialogComponent.tsx
|
||||
function MyDialogComponent({ prop1, hide }) {
|
||||
return (
|
||||
<div>
|
||||
<p>Value of prop1: {prop1}</p>
|
||||
<button onClick={hide}>Close</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
**Example: Updating a Simple Alert Dialog**
|
||||
|
||||
```javascript
|
||||
// Before (using deprecated API)
|
||||
let dialogId;
|
||||
const showAlert = (message) => {
|
||||
dialogId = uiDialogService.create({
|
||||
centralize: true,
|
||||
isDraggable: false,
|
||||
content: Dialog,
|
||||
contentProps: {
|
||||
title: 'Alert',
|
||||
body: () => <p>{message}</p>,
|
||||
onClose: () => uiDialogService.dismiss({ id: dialogId }),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// After (using new API)
|
||||
function AlertDialog({ message, hide }) {
|
||||
return (
|
||||
<div>
|
||||
<p>{message}</p>
|
||||
<button onClick={hide}>OK</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const showAlert = (message) => {
|
||||
uiDialogService.show({
|
||||
id: 'alert-dialog',
|
||||
title: 'Alert',
|
||||
content: AlertDialog,
|
||||
contentProps: { message },
|
||||
});
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Components
|
||||
|
||||
### CreateReportDialogPrompt
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* **Function Signature Update:** The function now accepts an object with `servicesManager`, `extensionManager`, `title`(optional).
|
||||
* **Return Value Structure:** The function now returns an object containing `value` (the report name), `dataSourceName` (the selected data source, if applicable), and `action` (indicating the user's choice).
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Update Function Call:**
|
||||
|
||||
Previously, the function was called with separate arguments. You should now pass an object:
|
||||
|
||||
```diff
|
||||
- const promptResult = await createReportDialogPrompt(uiDialogService, {
|
||||
- extensionManager,
|
||||
- });
|
||||
|
||||
+ const promptResult = await createReportDialogPrompt({
|
||||
+ servicesManager,
|
||||
+ extensionManager,
|
||||
+ title: 'Store Segmentation', // Optional title
|
||||
+ });
|
||||
|
||||
```
|
||||
|
||||
### promptSaveReport
|
||||
|
||||
Not changed, just a javascript to typescript migration.
|
||||
|
||||
### callLabelAutocompleteDialog
|
||||
|
||||
`callLabelAutocompleteDialog` is deprecated and has been replaced by `callInputDialogAutoComplete`. This new function simplifies the asynchronous handling of user input by using `uiDialogService.show()` and returning a promise.
|
||||
|
||||
|
||||
|
||||
### showLabelAnnotationPopup
|
||||
|
||||
`showLabelAnnotationPopup` has been replaced with `callInputDialogAutoComplete`. This update also uses `uiDialogService.show()` and promises, and it removes the callback function.
|
||||
|
||||
- The function now expects an object with `measurement`, `uiDialogService`, `labelConfig`, and `renderContent`.
|
||||
|
||||
```diff
|
||||
- const value = await showLabelAnnotationPopup(
|
||||
- measurement,
|
||||
- servicesManager.services.uiDialogService,
|
||||
- labelConfig,
|
||||
- renderContent
|
||||
- );
|
||||
+ const value = await callInputDialogAutoComplete({
|
||||
+ measurement,
|
||||
+ uiDialogService,
|
||||
+ labelConfig,
|
||||
+ renderContent,
|
||||
+ });
|
||||
```
|
||||
|
||||
### callInputDialog
|
||||
|
||||
- expects an objects now and returns the value of the input which you can then use for actions
|
||||
|
||||
|
||||
```diff
|
||||
|
||||
- callInputDialog(
|
||||
- uiDialogService,
|
||||
- {
|
||||
- text: '',
|
||||
- label: `${length}`,
|
||||
- },
|
||||
- (value, id) => {
|
||||
- if (id === 'save') {
|
||||
- adjustCalibration(Number.parseFloat(value));
|
||||
- resolve(true);
|
||||
- } else {
|
||||
- reject('cancel');
|
||||
- }
|
||||
- },
|
||||
- false,
|
||||
- {
|
||||
- dialogTitle: 'Calibration',
|
||||
- inputLabel: 'Actual Physical distance (mm)',
|
||||
- validateFunc: val => {
|
||||
- const v = Number.parseFloat(val);
|
||||
- return !isNaN(v) && v !== 0.0;
|
||||
- },
|
||||
- }
|
||||
- );
|
||||
+ callInputDialog({
|
||||
+ uiDialogService,
|
||||
+ title: 'Calibration',
|
||||
+ placeholder: 'Actual Physical distance (mm)',
|
||||
+ defaultValue: `${length}`,
|
||||
+ }).then(newValue => {
|
||||
+ adjustCalibration(Number.parseFloat(newValue));
|
||||
+ resolve(true);
|
||||
+ });
|
||||
```
|
||||
|
||||
or another one
|
||||
|
||||
```diff
|
||||
- callInputDialog(
|
||||
- uiDialogService,
|
||||
- { text: '', label: 'Enter description' },
|
||||
- (value, action) => {
|
||||
- if (action === 'save') {
|
||||
- saveFunction(value);
|
||||
- }
|
||||
- }
|
||||
- );
|
||||
+ callInputDialog({
|
||||
+ uiDialogService,
|
||||
+ title: 'Enter description of the Series',
|
||||
+ defaultValue: '',
|
||||
+ }).then(value => {
|
||||
+ saveFunction(value);
|
||||
+ });
|
||||
```
|
||||
|
||||
|
||||
### colorPickerDialog
|
||||
|
||||
Instead of calling `colorPickerDialog(uiDialogService, rgbaColor, callback)`, use `uiDialogService.show()` with `ColorPickerDialog` as the content.
|
||||
|
||||
|
||||
```diff
|
||||
- colorPickerDialog(uiDialogService, rgbaColor, (newRgbaColor, actionId) => {
|
||||
- if (actionId === 'cancel') {
|
||||
- return;
|
||||
- }
|
||||
- const color = [newRgbaColor.r, newRgbaColor.g, newRgbaColor.b, newRgbaColor.a * 255.0];
|
||||
- segmentationService.setSegmentColor(viewportId, segmentationId, segmentIndex, color);
|
||||
- });
|
||||
|
||||
// after
|
||||
|
||||
+ uiDialogService.show({
|
||||
+ content: ColorPickerDialog,
|
||||
+ title: 'Segment Color',
|
||||
+ contentProps: {
|
||||
+ value: rgbaColor,
|
||||
+ onSave: newRgbaColor => {
|
||||
+ const color = [newRgbaColor.r, newRgbaColor.g, newRgbaColor.b, newRgbaColor.a * 255.0];
|
||||
+ segmentationService.setSegmentColor(viewportId, segmentationId, segmentIndex, color);
|
||||
+ },
|
||||
+ },
|
||||
+ });
|
||||
```
|
||||
@@ -0,0 +1,104 @@
|
||||
---
|
||||
title: uiModalService
|
||||
---
|
||||
|
||||
|
||||
## ModalService
|
||||
|
||||
|
||||
### Props Kept same as before
|
||||
|
||||
| Prop | Description |
|
||||
|------|-------------|
|
||||
| `content` | This is now expected to be a *React component type* (a function or class that returns JSX) |
|
||||
| `contentProps` | This continues to be the way to pass data *to* your custom dialog component. However, several specific props that *used* to be passed here (like `onClose`, `actions`) are no longer valid. |
|
||||
| `title` | The title text to display in the dialog header. |
|
||||
| `shouldCloseOnEsc` | Allows closing the modal when the escape key is pressed. |
|
||||
| `shouldCloseOnOverlayClick` | Allows closing the modal when the overlay is clicked. |
|
||||
|
||||
### Renamed Props:
|
||||
|
||||
| Prop | Description |
|
||||
|------|-------------|
|
||||
| `containerDimensions` | renamed to `containerClassName` |
|
||||
|
||||
|
||||
|
||||
### Removed Props:
|
||||
|
||||
| Prop | Description |
|
||||
|------|-------------|
|
||||
| `movable` | It's removed because modals shouldn't be movable. If you need to move a dialog, use `uidDialogService` and `dialogs` instead. |
|
||||
| `isOpen` | always assumed `true` when `show` is called. |
|
||||
| `contentDimensions` | Removed, it is now component's responsibility to set the size for the content |
|
||||
| `customClassName` | renamed to `className` |
|
||||
| `closeButton` | The component now manages modal closing internally. If you need a close button, you can add one, perhaps by checking out the `FooterActions` component. |
|
||||
|
||||
|
||||
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
|
||||
### Rename of `containerDimensions` to `containerClassName` and removal of `contentDimensions`
|
||||
|
||||
|
||||
Before
|
||||
|
||||
```js
|
||||
uiModalService.show({
|
||||
title: 'Download High-Quality Image',
|
||||
content: CornerstoneViewportDownloadForm,
|
||||
contentProps: {
|
||||
activeViewportId,
|
||||
},
|
||||
containerDimensions: 'w-[70%] max-w-[900px]',
|
||||
contentDimensions: 'h-[493px] w-[460px] pl-[12px] pr-[12px]',
|
||||
});
|
||||
```
|
||||
|
||||
After: the component is responsible for setting the size
|
||||
|
||||
```js
|
||||
function CornerstoneViewportDownloadForm({ activeViewportId }) {
|
||||
return (
|
||||
<div className="h-[493px] w-[460px] pl-[12px] pr-[12px]">
|
||||
<h2 className="text-lg font-bold">Download Image</h2>
|
||||
<p>Viewport ID: {activeViewportId}</p>
|
||||
<button className="mt-4 bg-blue-500 text-white p-2 rounded">Download</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Show the modal
|
||||
uiModalService.show({
|
||||
title: 'Download High-Quality Image',
|
||||
content: CornerstoneViewportDownloadForm,
|
||||
contentProps: { activeViewportId },
|
||||
containerClassName: 'w-[70%] max-w-[900px]',
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### onClose
|
||||
Previously, you had to pass in the `onClose` as `hide` function automatically added to the component.
|
||||
|
||||
```diff
|
||||
- uiModalService.show({
|
||||
- title: 'Untrack Series',
|
||||
- content: UntrackSeriesModal,
|
||||
- contentProps: { onConfirm },
|
||||
- onClose: () => uiModalService.hide(),
|
||||
- });
|
||||
|
||||
+ uiModalService.show({
|
||||
+ title: 'Untrack Series',
|
||||
+ content: UntrackSeriesModal,
|
||||
+ contentProps: {
|
||||
+ onConfirm,
|
||||
+ hide, // passed in automatically in the background
|
||||
+ },
|
||||
+ });
|
||||
```
|
||||
+139
-23
@@ -16,6 +16,7 @@ import loadingIndicatorProgress from '../../../assets/img/loading-indicator-icon
|
||||
import loadingIndicatorPercent from '../../../assets/img/loading-indicator-percent.png';
|
||||
import viewportActionCorners from '../../../assets/img/viewport-action-corners.png';
|
||||
import contextMenu from '../../../assets/img/context-menu.jpg';
|
||||
import viewportDownloadWarning from '../../../assets/img/viewport-download-warning.png';
|
||||
import segmentationOverlay from '../../../assets/img/segmentation-overlay.png';
|
||||
|
||||
import segDisplayEditingTrue from '../../../assets/img/segDisplayEditingTrue.png';
|
||||
@@ -24,6 +25,8 @@ import thumbnailMenuItemsImage from '../../../assets/img/thumbnailMenuItemsImage
|
||||
import studyMenuItemsImage from '../../../assets/img/studyMenuItemsImage.png';
|
||||
import windowLevelActionMenu from '../../../assets/img/windowLevelActionMenu.png';
|
||||
import viewPortNotificationImage from '../../../assets/img/viewport-notification.png';
|
||||
import captureViewportModal from '../../../assets/img/captureViewportModal.png';
|
||||
import aboutModal from '../../../assets/img/aboutModal.png';
|
||||
|
||||
export const viewportOverlayCustomizations = [
|
||||
{
|
||||
@@ -269,24 +272,6 @@ window.config = {
|
||||
default: 'The CinePlayer component in the UI',
|
||||
configuration: null,
|
||||
},
|
||||
{
|
||||
id: 'cornerstone.windowLevelActionMenu',
|
||||
description: 'Window level action menu for the cornerstone viewport.',
|
||||
image: windowLevelActionMenu,
|
||||
default: null,
|
||||
configuration: `
|
||||
window.config = {
|
||||
// rest of window config
|
||||
customizationService: [
|
||||
{
|
||||
'cornerstone.windowLevelActionMenu': {
|
||||
$set: CustomizedComponent,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
`,
|
||||
},
|
||||
{
|
||||
id: 'cornerstone.windowLevelPresets',
|
||||
description: 'Window level presets for the cornerstone viewport.',
|
||||
@@ -785,10 +770,142 @@ window.config = {
|
||||
};
|
||||
`,
|
||||
},
|
||||
{
|
||||
id: 'viewportDownload.warningMessage',
|
||||
description: 'Customizes the warning message for the viewport download form.',
|
||||
image: viewportDownloadWarning,
|
||||
default: {
|
||||
enabled: true,
|
||||
value: 'Not For Diagnostic Use',
|
||||
},
|
||||
configuration: `
|
||||
window.config = {
|
||||
// rest of window config
|
||||
customizationService: [
|
||||
{
|
||||
'viewportDownload.warningMessage': {
|
||||
$set: {
|
||||
enabled: true,
|
||||
value: 'Careful! This is not for diagnostic use.',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
`,
|
||||
},
|
||||
{
|
||||
id: 'ohif.captureViewportModal',
|
||||
description: 'The modal for capturing the viewport image.',
|
||||
image: captureViewportModal,
|
||||
default: 'Our own default component',
|
||||
configuration: `
|
||||
window.config = {
|
||||
// rest of window config
|
||||
|
||||
// You can use the component from ImageModal and FooterAction
|
||||
// to build your own custom component
|
||||
customizationService: [
|
||||
{
|
||||
'ohif.captureViewportModal': {
|
||||
$set: CustomizedComponent,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
`,
|
||||
},
|
||||
{
|
||||
id: 'ohif.aboutModal',
|
||||
description: 'The About modal',
|
||||
image: aboutModal,
|
||||
default: 'Our own default component',
|
||||
configuration: `
|
||||
window.config = {
|
||||
// rest of window config
|
||||
|
||||
// You can use the component from AboutModal
|
||||
// to build your own custom component
|
||||
customizationService: [
|
||||
{
|
||||
'ohif.aboutModal': {
|
||||
$set: CustomizedComponent,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
`,
|
||||
},
|
||||
{
|
||||
id: 'viewportDownload.warningMessage',
|
||||
description: 'Customizes the warning message for the viewport download form.',
|
||||
image: viewportDownloadWarning,
|
||||
default: {
|
||||
enabled: true,
|
||||
value: 'Not For Diagnostic Use',
|
||||
},
|
||||
configuration: `
|
||||
window.config = {
|
||||
// rest of window config
|
||||
customizationService: [
|
||||
{
|
||||
'viewportDownload.warningMessage': {
|
||||
$set: {
|
||||
enabled: true,
|
||||
value: 'Careful! This is not for diagnostic use.',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
`,
|
||||
},
|
||||
{
|
||||
id: 'ohif.captureViewportModal',
|
||||
description: 'The modal for capturing the viewport image.',
|
||||
image: captureViewportModal,
|
||||
default: 'Our own default component',
|
||||
configuration: `
|
||||
window.config = {
|
||||
// rest of window config
|
||||
|
||||
// You can use the component from ImageModal and FooterAction
|
||||
// to build your own custom component
|
||||
customizationService: [
|
||||
{
|
||||
'ohif.captureViewportModal': {
|
||||
$set: CustomizedComponent,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
`,
|
||||
},
|
||||
{
|
||||
id: 'ohif.aboutModal',
|
||||
description: 'The About modal',
|
||||
image: aboutModal,
|
||||
default: 'Our own default component',
|
||||
configuration: `
|
||||
window.config = {
|
||||
// rest of window config
|
||||
|
||||
// You can use the component from AboutModal
|
||||
// to build your own custom component
|
||||
customizationService: [
|
||||
{
|
||||
'ohif.aboutModal': {
|
||||
$set: CustomizedComponent,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
`,
|
||||
},
|
||||
{
|
||||
id: 'viewportActionMenu.windowLevelActionMenu',
|
||||
description:
|
||||
'Configures the display and location of the window level action menu in the viewport.',
|
||||
'Configures the display and location of the window level action menu in the viewport.',
|
||||
image: windowLevelActionMenu,
|
||||
default: null,
|
||||
configuration: `
|
||||
@@ -797,9 +914,8 @@ window.config = {
|
||||
customizationService: [
|
||||
{
|
||||
'viewportActionMenu.windowLevelActionMenu': {
|
||||
$set: {
|
||||
enabled: true,
|
||||
location: 1, // Set the location of the menu in the viewport.
|
||||
$merge: {
|
||||
location: 0, // Set the location of the menu in the viewport.
|
||||
// 0: topLeft
|
||||
// 1: topRight
|
||||
// 2: bottomLeft
|
||||
@@ -822,7 +938,7 @@ window.config = {
|
||||
customizationService: [
|
||||
{
|
||||
'viewportActionMenu.segmentationOverlay': {
|
||||
$set: {
|
||||
$merge: {
|
||||
enabled: true,
|
||||
location: 1, // Set the location of the overlay in the viewport.
|
||||
// 0: topLeft
|
||||
|
||||
@@ -136,6 +136,7 @@ module.exports = {
|
||||
prism: {
|
||||
theme: require('prism-react-renderer').themes.github,
|
||||
darkTheme: require('prism-react-renderer').themes.dracula,
|
||||
additionalLanguages: ['diff'],
|
||||
},
|
||||
algolia: {
|
||||
appId: 'EFLT6YIHHZ',
|
||||
|
||||
@@ -233,11 +233,6 @@ input[type='number'] {
|
||||
-moz-appearance: textfield; /* For Firefox */
|
||||
}
|
||||
|
||||
.navbar__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navbar__item svg {
|
||||
margin-right: 5px;
|
||||
display: inline-block;
|
||||
@@ -642,6 +637,7 @@ html[data-theme='dark'] .markdown hr {
|
||||
/* Markdown code block styling */
|
||||
.markdown pre {
|
||||
font-size: 0.9rem;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.theme-code-block {
|
||||
@@ -725,3 +721,7 @@ a.dropdown__link[href='/3.9/migration-guide/3p8-to-3p9/']::after {
|
||||
font-size: 0.9em;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
li:first-child {
|
||||
margin-top: 0.5em; /* Adjust '1em' to your desired spacing */
|
||||
}
|
||||
Reference in new issue
Block a user