ui(component): Tooltip component ui-next (#4635)

Co-authored-by: sedghi <ar.sedghi@gmail.com>
This commit is contained in:
Dan Rukas 2025-01-08 13:10:57 -05:00 committed by GitHub
parent 03ad79eaed
commit b54f2075ec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 342 additions and 100 deletions

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Icon, Tooltip, ViewportActionButton } from '@ohif/ui'; import { ViewportActionButton } from '@ohif/ui';
import { Icons } from '@ohif/ui-next'; import { Icons, Tooltip, TooltipTrigger, TooltipContent } from '@ohif/ui-next';
export default function _getStatusComponent({ isHydrated, onStatusClick }) { export default function _getStatusComponent({ isHydrated, onStatusClick }) {
let ToolTipMessage = null; let ToolTipMessage = null;
@ -10,7 +10,6 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) {
switch (isHydrated) { switch (isHydrated) {
case true: case true:
StatusIcon = () => <Icons.ByName name="status-alert" />; StatusIcon = () => <Icons.ByName name="status-alert" />;
ToolTipMessage = () => <div>This Segmentation is loaded in the segmentation panel</div>; ToolTipMessage = () => <div>This Segmentation is loaded in the segmentation panel</div>;
break; break;
case false: case false:
@ -20,7 +19,6 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) {
name="status-untracked" name="status-untracked"
/> />
); );
ToolTipMessage = () => <div>Click LOAD to load RTSTRUCT.</div>; ToolTipMessage = () => <div>Click LOAD to load RTSTRUCT.</div>;
} }
@ -44,14 +42,18 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) {
return ( return (
<> <>
{ToolTipMessage && ( {ToolTipMessage && (
<Tooltip <Tooltip>
content={<ToolTipMessage />} <TooltipTrigger asChild>
position="bottom-left" <span>
> <StatusArea />
<StatusArea /> </span>
</TooltipTrigger>
<TooltipContent side="bottom">
<ToolTipMessage />
</TooltipContent>
</Tooltip> </Tooltip>
)} )}
{!ToolTipMessage && <StatusArea />} {!ToolTipMessage && <StatusArea />}
</> </>
); );
} }

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Tooltip, ViewportActionButton } from '@ohif/ui'; import { ViewportActionButton } from '@ohif/ui';
import { Icons } from '@ohif/ui-next'; import { Icons, Tooltip, TooltipTrigger, TooltipContent } from '@ohif/ui-next';
export default function _getStatusComponent({ isHydrated, onStatusClick }) { export default function _getStatusComponent({ isHydrated, onStatusClick }) {
let ToolTipMessage = null; let ToolTipMessage = null;
@ -10,7 +10,6 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) {
switch (isHydrated) { switch (isHydrated) {
case true: case true:
StatusIcon = () => <Icons.ByName name="status-alert" />; StatusIcon = () => <Icons.ByName name="status-alert" />;
ToolTipMessage = () => <div>This Segmentation is loaded in the segmentation panel</div>; ToolTipMessage = () => <div>This Segmentation is loaded in the segmentation panel</div>;
break; break;
case false: case false:
@ -20,7 +19,6 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) {
name="status-untracked" name="status-untracked"
/> />
); );
ToolTipMessage = () => <div>Click LOAD to load segmentation.</div>; ToolTipMessage = () => <div>Click LOAD to load segmentation.</div>;
} }
@ -44,14 +42,18 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) {
return ( return (
<> <>
{ToolTipMessage && ( {ToolTipMessage && (
<Tooltip <Tooltip>
content={<ToolTipMessage />} <TooltipTrigger asChild>
position="bottom-left" <span>
> <StatusArea />
<StatusArea /> </span>
</TooltipTrigger>
<TooltipContent side="bottom">
<ToolTipMessage />
</TooltipContent>
</Tooltip> </Tooltip>
)} )}
{!ToolTipMessage && <StatusArea />} {!ToolTipMessage && <StatusArea />}
</> </>
); );
} }

View File

@ -5,10 +5,11 @@ import { ExtensionManager, useToolbar } from '@ohif/core';
import { setTrackingUniqueIdentifiersForElement } from '../tools/modules/dicomSRModule'; import { setTrackingUniqueIdentifiersForElement } from '../tools/modules/dicomSRModule';
import { Icon, Tooltip, useViewportGrid, ViewportActionArrows } from '@ohif/ui';
import createReferencedImageDisplaySet from '../utils/createReferencedImageDisplaySet'; import createReferencedImageDisplaySet from '../utils/createReferencedImageDisplaySet';
import { usePositionPresentationStore } from '@ohif/extension-cornerstone'; import { usePositionPresentationStore } from '@ohif/extension-cornerstone';
import { Icons } from '@ohif/ui-next'; import { useViewportGrid, ViewportActionArrows } from '@ohif/ui';
import { Icons, Tooltip, TooltipTrigger, TooltipContent } from '@ohif/ui-next';
const MEASUREMENT_TRACKING_EXTENSION_ID = '@ohif/extension-measurement-tracking'; const MEASUREMENT_TRACKING_EXTENSION_ID = '@ohif/extension-measurement-tracking';
const SR_TOOLGROUP_BASE_NAME = 'SRToolGroup'; const SR_TOOLGROUP_BASE_NAME = 'SRToolGroup';
@ -467,11 +468,18 @@ function _getStatusComponent({
return ( return (
<> <>
{ToolTipMessage && ( {ToolTipMessage && (
<Tooltip <Tooltip>
content={<ToolTipMessage />} <TooltipTrigger asChild>
position="bottom-left" <span>
> <StatusArea />
<StatusArea /> </span>
</TooltipTrigger>
<TooltipContent
side="bottom"
align="start"
>
<ToolTipMessage />
</TooltipContent>
</Tooltip> </Tooltip>
)} )}
{!ToolTipMessage && <StatusArea />} {!ToolTipMessage && <StatusArea />}

View File

@ -1,8 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Button, PanelSection, ButtonGroup, IconButton, InputNumber, Tooltip } from '@ohif/ui'; import { Button, PanelSection, ButtonGroup, IconButton, InputNumber } from '@ohif/ui';
import { DoubleSlider, Icons, Tooltip, TooltipTrigger, TooltipContent } from '@ohif/ui-next';
import { DoubleSlider, Icons } from '@ohif/ui-next';
import { Enums } from '@cornerstonejs/core'; import { Enums } from '@cornerstonejs/core';
const controlClassNames = { const controlClassNames = {
@ -13,16 +11,18 @@ const controlClassNames = {
const Header = ({ title, tooltip }) => ( const Header = ({ title, tooltip }) => (
<div className="flex items-center space-x-1"> <div className="flex items-center space-x-1">
<Tooltip <Tooltip>
content={<div className="text-white">{tooltip}</div>} <TooltipTrigger asChild>
position="bottom-left" <span>
tight={true} <Icons.ByName
tooltipBoxClassName="max-w-xs p-2" name="info-link"
> className="text-primary-active h-[14px] w-[14px]"
<Icons.ByName />
name="info-link" </span>
className="text-primary-active h-[14px] w-[14px]" </TooltipTrigger>
/> <TooltipContent sideOffset={4} className="max-w-xs p-2 bg-primary-dark text-white">
<div>{tooltip}</div>
</TooltipContent>
</Tooltip> </Tooltip>
<span className="text-aqua-pale text-[11px] uppercase">{title}</span> <span className="text-aqua-pale text-[11px] uppercase">{title}</span>
</div> </div>
@ -45,9 +45,7 @@ const DynamicVolumeControls = ({
onDynamicClick, onDynamicClick,
}) => { }) => {
const [computedView, setComputedView] = useState(false); const [computedView, setComputedView] = useState(false);
const [computeViewMode, setComputeViewMode] = useState(Enums.DynamicOperatorType.SUM); const [computeViewMode, setComputeViewMode] = useState(Enums.DynamicOperatorType.SUM);
const [sliderRangeValues, setSliderRangeValues] = useState([0, framesLength - 1]); const [sliderRangeValues, setSliderRangeValues] = useState([0, framesLength - 1]);
const handleSliderChange = newValues => { const handleSliderChange = newValues => {
@ -113,15 +111,14 @@ const DynamicVolumeControls = ({
<div> <div>
Operation Buttons (SUM, AVERAGE, SUBTRACT): Select the mathematical operation to be Operation Buttons (SUM, AVERAGE, SUBTRACT): Select the mathematical operation to be
applied to the data set. applied to the data set.
<br></br> Range Slider: Choose the numeric range within which the operation will be <br /> Range Slider: Choose the numeric range within which the operation will be
performed. performed.
<br></br>Generate Button: Execute the chosen operation on the specified range of <br />Generate Button: Execute the chosen operation on the specified range of data.
data.{' '}
</div> </div>
} }
/> />
<ButtonGroup <ButtonGroup
className={`mt-2 w-full`} className="mt-2 w-full"
separated={true} separated={true}
> >
<button <button
@ -190,8 +187,8 @@ function FrameControls({
title="4D Controls" title="4D Controls"
tooltip={ tooltip={
<div> <div>
Play/Pause Button: Begin or pause the animation of the 4D visualization. <br></br> Frame Play/Pause Button: Begin or pause the animation of the 4D visualization. <br /> Frame
Selector: Navigate through individual frames of the 4D data. <br></br> FPS (Frames Per Selector: Navigate through individual frames of the 4D data. <br /> FPS (Frames Per
Second) Selector: Adjust the playback speed of the animation. Second) Selector: Adjust the playback speed of the animation.
</div> </div>
} }
@ -225,4 +222,4 @@ function FrameControls({
</div> </div>
</div> </div>
); );
} }

View File

@ -1,8 +1,8 @@
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Tooltip, ViewportActionArrows, useViewportGrid } from '@ohif/ui'; import { ViewportActionArrows, useViewportGrid } from '@ohif/ui';
import { Icons } from '@ohif/ui-next'; import { Icons, Tooltip, TooltipTrigger, TooltipContent } from '@ohif/ui-next';
import { annotation } from '@cornerstonejs/tools'; import { annotation } from '@cornerstonejs/tools';
import { useTrackedMeasurements } from './../getContextModule'; import { useTrackedMeasurements } from './../getContextModule';
@ -235,11 +235,9 @@ function TrackedCornerstoneViewport(
}; };
return ( return (
<> <div className="relative flex h-full w-full flex-row overflow-hidden">
<div className="relative flex h-full w-full flex-row overflow-hidden"> {getCornerstoneViewport()}
{getCornerstoneViewport()} </div>
</div>
</>
); );
} }
@ -312,7 +310,7 @@ const _getArrowsComponent = (isTracked, switchMeasurement, isActiveViewport) =>
<ViewportActionArrows <ViewportActionArrows
onArrowsClick={direction => switchMeasurement(direction)} onArrowsClick={direction => switchMeasurement(direction)}
className={isActiveViewport ? 'visible' : 'invisible group-hover/pane:visible'} className={isActiveViewport ? 'visible' : 'invisible group-hover/pane:visible'}
></ViewportActionArrows> />
); );
}; };
@ -322,33 +320,23 @@ function _getStatusComponent(isTracked, t) {
} }
return ( return (
<div className="relative"> <Tooltip>
<Tooltip <TooltipTrigger asChild>
position="bottom-left" <span>
content={ <Icons.StatusTracking className="text-aqua-pale" />
<div className="flex py-2"> </span>
<div className="flex pt-1"> </TooltipTrigger>
<Icons.InfoLink className="text-primary-main w-4" /> <TooltipContent
</div> align="start"
<div className="ml-4 flex"> side="bottom"
<span className="text-common-light text-base">
{isTracked ? (
<>{t('Series is tracked and can be viewed in the measurement panel')}</>
) : (
<>
{t(
'Measurements for untracked series will not be shown in the measurements panel'
)}
</>
)}
</span>
</div>
</div>
}
> >
<Icons.StatusTracking className="text-aqua-pale" /> {isTracked ? (
</Tooltip> <>{t('Series is tracked and can be viewed in the measurement panel')}</>
</div> ) : (
<>{t('Measurements for untracked series will not be shown in the measurements panel')}</>
)}
</TooltipContent>
</Tooltip>
); );
} }

View File

@ -14,13 +14,11 @@ import { utils, hotkeys } from '@ohif/core';
import publicUrl from '../../utils/publicUrl'; import publicUrl from '../../utils/publicUrl';
import { import {
Icon,
StudyListExpandedRow, StudyListExpandedRow,
EmptyStudies, EmptyStudies,
StudyListTable, StudyListTable,
StudyListPagination, StudyListPagination,
StudyListFilter, StudyListFilter,
TooltipClipboard,
useModal, useModal,
AboutModal, AboutModal,
UserPreferences, UserPreferences,
@ -31,12 +29,20 @@ import {
ButtonEnums, ButtonEnums,
} from '@ohif/ui'; } from '@ohif/ui';
import { Header, Icons } from '@ohif/ui-next'; import {
Header,
Icons,
Tooltip,
TooltipTrigger,
TooltipContent,
Clipboard,
Onboarding,
ScrollArea,
} from '@ohif/ui-next';
import { Types } from '@ohif/ui'; import { Types } from '@ohif/ui';
import i18n from '@ohif/i18n'; import i18n from '@ohif/i18n';
import { Onboarding, ScrollArea } from '@ohif/ui-next';
const PatientInfoVisibility = Types.PatientInfoVisibility; const PatientInfoVisibility = Types.PatientInfoVisibility;
@ -274,22 +280,37 @@ function WorkList({
t('Common:localTimeFormat', 'hh:mm A') t('Common:localTimeFormat', 'hh:mm A')
); );
const makeCopyTooltipCell = textValue => {
if (!textValue) {
return '';
}
return (
<Tooltip>
<TooltipTrigger asChild>
<span className="cursor-pointer truncate">{textValue}</span>
</TooltipTrigger>
<TooltipContent side="bottom">
<div className="flex items-center justify-between gap-2">
{textValue}
<Clipboard>{textValue}</Clipboard>
</div>
</TooltipContent>
</Tooltip>
);
};
return { return {
dataCY: `studyRow-${studyInstanceUid}`, dataCY: `studyRow-${studyInstanceUid}`,
clickableCY: studyInstanceUid, clickableCY: studyInstanceUid,
row: [ row: [
{ {
key: 'patientName', key: 'patientName',
content: patientName ? ( content: patientName ? makeCopyTooltipCell(patientName) : null,
<TooltipClipboard>{patientName}</TooltipClipboard>
) : (
<span className="text-gray-700">(Empty)</span>
),
gridCol: 4, gridCol: 4,
}, },
{ {
key: 'mrn', key: 'mrn',
content: <TooltipClipboard>{mrn}</TooltipClipboard>, content: makeCopyTooltipCell(mrn),
gridCol: 3, gridCol: 3,
}, },
{ {
@ -305,7 +326,7 @@ function WorkList({
}, },
{ {
key: 'description', key: 'description',
content: <TooltipClipboard>{description}</TooltipClipboard>, content: makeCopyTooltipCell(description),
gridCol: 4, gridCol: 4,
}, },
{ {
@ -316,7 +337,7 @@ function WorkList({
}, },
{ {
key: 'accession', key: 'accession',
content: <TooltipClipboard>{accession}</TooltipClipboard>, content: makeCopyTooltipCell(accession),
gridCol: 3, gridCol: 3,
}, },
{ {
@ -392,6 +413,7 @@ function WorkList({
query.append('configUrl', filterValues.configUrl); query.append('configUrl', filterValues.configUrl);
} }
query.append('StudyInstanceUIDs', studyInstanceUid); query.append('StudyInstanceUIDs', studyInstanceUid);
return ( return (
mode.displayName && ( mode.displayName && (
<Link <Link

View File

@ -0,0 +1,15 @@
---
title: General
---
## HTML Template Update
We have modified the `template.html` file so if you are using a custom template, you will need to update it.
Here are the key changes needed in the migration:
1. Added `window.PUBLIC_URL` declaration:
```javascript
window.PUBLIC_URL = '<%= PUBLIC_URL %>';
```
Was added before the `<!-- EXTENSIONS -->` comment block.

View File

@ -1,6 +1,5 @@
--- ---
sidebar_position: 2 title: Icons
sidebar_label: Icons
--- ---
## Migration Guide: Icon Component Updates ## Migration Guide: Icon Component Updates

View File

@ -0,0 +1,62 @@
---
title: Tooltip
---
## Tooltip Updates
### Changes:
- Updated Tooltip structure to use `Tooltip`, `TooltipTrigger`, and `TooltipContent`.
- Removed deprecated `TooltipClipboard` and inline `content`/`position` properties.
### Migration Steps:
1. Replace imports:
```tsx
// Before
import { Tooltip } from '@ohif/ui';
import { TooltipClipboard } from '@ohif/ui';
// After
import { Tooltip, TooltipTrigger, TooltipContent } from '@ohif/ui-next';
```
2. Update Tooltip usage:
```tsx
// Before
<Tooltip content={<div>Tooltip Message</div>} position="bottom-left">
<Component />
</Tooltip>
// After
<Tooltip>
<TooltipTrigger asChild>
<Component />
</TooltipTrigger>
<TooltipContent side="bottom">
Tooltip Message
</TooltipContent>
</Tooltip>
```
3. TooltipClipboard Replacement:
The `TooltipClipboard` component has been removed. Instead, use the `Clipboard` component inside `TooltipContent` for copying text functionality.
#### Before:
```tsx
<TooltipClipboard>{text}</TooltipClipboard>
```
#### After:
```tsx
<Tooltip>
<TooltipTrigger asChild>
<span className="cursor-pointer truncate">{text}</span>
</TooltipTrigger>
<TooltipContent side="bottom">
<div className="flex items-center justify-between gap-2">
{text}
<Clipboard>{text}</Clipboard>
</div>
</TooltipContent>
</Tooltip>
```

View File

@ -4,3 +4,6 @@ sidebar_label: 3.9 -> 3.10 beta
--- ---
# Migration Guide # Migration Guide
## General

View File

@ -0,0 +1,55 @@
import React, { ReactNode } from 'react';
import { Button } from '../Button';
import { Icons } from '../Icons';
interface ClipboardProps {
children: ReactNode;
}
const Clipboard: React.FC<ClipboardProps> = ({ children }) => {
const [copyState, setCopyState] = React.useState<'idle' | 'success' | 'error'>('idle');
const copyText = React.useMemo(() => {
if (typeof children === 'string') {
return children.trim();
}
return '';
}, [children]);
const handleCopy = React.useCallback(async () => {
if (!copyText) {
return;
}
try {
await navigator.clipboard.writeText(copyText);
setCopyState('success');
} catch {
setCopyState('error');
} finally {
setTimeout(() => setCopyState('idle'), 1500); // Reset state after feedback
}
}, [copyText]);
return (
<Button
variant="ghost"
size="icon"
onClick={e => {
e.stopPropagation();
handleCopy();
}}
className="text-white"
title="Copy"
>
{copyState === 'idle' && <Icons.Copy className="h-6 w-6" />}
{copyState === 'success' && <Icons.FeedbackComplete className="h-6 w-6 text-white" />}
{copyState === 'error' && (
<Icons.ByName
name="Error"
className="h-6 w-6 text-white"
/>
)}
</Button>
);
};
export { Clipboard };

View File

@ -0,0 +1,3 @@
import { Clipboard } from './Clipboard';
export { Clipboard };

View File

@ -7,6 +7,7 @@ import ChevronOpen from './Sources/ChevronOpen';
import Code from './Sources/Code'; import Code from './Sources/Code';
import ColorChange from './Sources/ColorChange'; import ColorChange from './Sources/ColorChange';
import Controls from './Sources/Controls'; import Controls from './Sources/Controls';
import Copy from './Sources/Copy';
import Delete from './Sources/Delete'; import Delete from './Sources/Delete';
import DicomTagBrowser from './Sources/DicomTagBrowser'; import DicomTagBrowser from './Sources/DicomTagBrowser';
import DisplayFillAndOutline from './Sources/DisplayFillAndOutline'; import DisplayFillAndOutline from './Sources/DisplayFillAndOutline';
@ -16,6 +17,7 @@ import Download from './Sources/Download';
import Export from './Sources/Export'; import Export from './Sources/Export';
import EyeHidden from './Sources/EyeHidden'; import EyeHidden from './Sources/EyeHidden';
import EyeVisible from './Sources/EyeVisible'; import EyeVisible from './Sources/EyeVisible';
import FeedbackComplete from './Sources/FeedbackComplete';
import GearSettings from './Sources/GearSettings'; import GearSettings from './Sources/GearSettings';
import Hide from './Sources/Hide'; import Hide from './Sources/Hide';
import IconMPR from './Sources/IconMPR'; import IconMPR from './Sources/IconMPR';
@ -418,6 +420,7 @@ export const Icons = {
Code, Code,
ColorChange, ColorChange,
Controls, Controls,
Copy,
Delete, Delete,
DicomTagBrowser, DicomTagBrowser,
DisplayFillAndOutline, DisplayFillAndOutline,
@ -430,6 +433,7 @@ export const Icons = {
Export, Export,
EyeHidden, EyeHidden,
EyeVisible, EyeVisible,
FeedbackComplete,
GearSettings, GearSettings,
Hide, Hide,
IconMPR, IconMPR,

View File

@ -0,0 +1,44 @@
import React from 'react';
import type { IconProps } from '../types';
export const Copy = (props: IconProps) => (
<svg
width="24px"
height="24px"
viewBox="0 0 24 24"
{...props}
>
<g
id="Copy"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<rect
id="Rectangle"
x="0"
y="0"
width="24"
height="24"
></rect>
<rect
id="Rectangle"
stroke="currentColor"
x="8.95205173"
y="4.5"
width="10"
height="10"
rx="2"
></rect>
<path
d="M7.05569885,9.5 L5.5,9.5 C4.67157288,9.5 4,10.1715729 4,11 L4,17.8271183 C4,18.6555454 4.67157288,19.3271183 5.5,19.3271183 L12.4520517,19.3271183 C13.2804789,19.3271183 13.9520517,18.6555454 13.9520517,17.8271183 L13.9520517,16.3489489 L13.9520517,16.3489489"
id="Path-4"
stroke="currentColor"
strokeLinecap="round"
></path>
</g>
</svg>
);
export default Copy;

View File

@ -0,0 +1,35 @@
import React from 'react';
import type { IconProps } from '../types';
export const FeedbackComplete = (props: IconProps) => (
<svg
width="24px"
height="24px"
viewBox="0 0 24 24"
{...props}
>
<g
id="FeedbackComplete"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<rect
id="Rectangle"
x="0"
y="0"
width="24"
height="24"
></rect>
<path
d="M6.5,12.5497794 L10.0873523,16.1371317 C10.4778766,16.527656 11.1110415,16.527656 11.5015658,16.1371317 C11.5482206,16.0904769 11.5901435,16.0393192 11.6267216,15.9844066 L17.6113609,7 L17.6113609,7"
id="Path-3"
stroke="currentColor"
strokeLinecap="round"
></path>
</g>
</svg>
);
export default FeedbackComplete;

View File

@ -17,7 +17,7 @@ const TooltipContent = React.forwardRef<
ref={ref} ref={ref}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
'bg-primary-dark border-secondary-light text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 overflow-hidden rounded border px-3 py-1.5 text-sm', 'bg-primary-dark border-secondary-light text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 overflow-hidden rounded border px-2 py-1.5 text-sm',
className className
)} )}
{...props} {...props}

View File

@ -24,7 +24,7 @@ import {
DialogDescription, DialogDescription,
} from './Dialog'; } from './Dialog';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './Select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './Select';
import { Clipboard } from './Clipboard';
import { Combobox } from './Combobox'; import { Combobox } from './Combobox';
import { Popover, PopoverContent, PopoverTrigger, PopoverAnchor } from './Popover'; import { Popover, PopoverContent, PopoverTrigger, PopoverAnchor } from './Popover';
import { Calendar } from './Calendar'; import { Calendar } from './Calendar';
@ -183,4 +183,5 @@ export {
CardTitle, CardTitle,
CardDescription, CardDescription,
CardContent, CardContent,
Clipboard,
}; };

View File

@ -12,6 +12,7 @@ import {
Tabs, Tabs,
TabsContent, TabsContent,
TabsList, TabsList,
Clipboard,
TabsTrigger, TabsTrigger,
Toggle, Toggle,
toggleVariants, toggleVariants,
@ -99,6 +100,7 @@ export {
ThemeWrapper, ThemeWrapper,
Calendar, Calendar,
DatePickerWithRange, DatePickerWithRange,
Clipboard,
// contextProviders // contextProviders
NotificationProvider, NotificationProvider,
useNotification, useNotification,