Compare commits

...

4 Commits

Author SHA1 Message Date
Manika Malhotra
e8fba3e0b6 fix: remove sonner shim and use toast 2026-10-13 13:44:10 +05:30
Manika Malhotra
fd0f5cddb3 chore: bump packages, integrate alert-strip and resizable 2026-10-12 17:26:05 +05:30
Manika Malhotra
693401dbbb feat(frontend): adopt reworked @signozhq/ui components across remaining areas 2026-10-08 18:12:17 +05:30
Manika Malhotra
c9f0cc18d2 fix: restore onboarding 2 column layout 2026-10-07 18:44:51 +05:30
369 changed files with 3279 additions and 2991 deletions

View File

@@ -0,0 +1,191 @@
# Popover migration analysis
The new `@signozhq/ui` Popover is click-only. This note maps the current app onto that API so a later migration does not break open state, focus, or the global `.ant-popover` overrides. It does not change any call site.
The component this describes lives in the design-system repo (`packages/ui/src/popover`). Storybook stories for it are `Playground`, `SaveViewForm`, `ControlledOpen`, `InsideDialog`, `MaxHeightScrolling`, and `AnchoredTypeahead`.
## The API to migrate onto
```tsx
<Popover
nativeButton
side="bottom"
align="start"
trigger={<button type="button">Open</button>}
open={open}
onOpenChange={setOpen}
>
{content}
</Popover>
```
| Today | New Popover |
| --- | --- |
| antd `children` (the trigger) | `trigger` (required) |
| antd `content`, or Radix `<PopoverContent>` | `children` |
| antd `trigger="click"` | the only mode; there is no `openOn` |
| antd `trigger="hover"` | not this component; a Tooltip, or a click popover when the body is interactive |
| `placement="bottomRight"` | `side="bottom"` `align="end"` |
| `placement="bottomLeft"` | `side="bottom"` `align="start"` |
| `open` / `onOpenChange` | kept; `onOpenChange` receives Base UI's reason and `details.cancel()` |
| `defaultOpen` | removed; nothing in the app starts open |
| `overlayClassName`, `overlayStyle`, `overlayInnerStyle`, `className` | rejected on the shell; size is `width` / `maxWidth` / `maxHeight` |
| `getPopupContainer` | `container` |
| `destroyTooltipOnHide` | the only behavior; the popup unmounts on close |
| `arrow` / `showArrow` | rejected; the shell has no arrow |
| Radix `PopoverAnchor` | `anchor`, and the trigger is still required |
| Radix `withPortal={false}` | `container` (inside a Dialog the popup already portals into the panel) |
| Radix `onInteractOutside` | `onOpenChange` + `details.cancel()` when `reason === 'outside-press'` |
| `sideOffset` / `alignOffset` | not props; fixed at 4px and 0 |
`nativeButton` is required. `true` when `trigger` renders a `<button>` (including `Button`). `false` for anything else.
A node that renders nothing (`null`, `false`, `''`) renders the trigger alone, with no listeners and no portal.
## What is in the app today
Counted from `frontend/src` on this branch:
- **29 files** import `Popover` from `antd`.
- **7 files** import `Popover` from `@signozhq/ui` (the Radix wrapper this rework replaces). Those seven break as soon as the package ships, because `children` stops being the trigger.
- **18 SCSS files** override `.ant-popover*`.
antd `Dropdown` is already on `@signozhq/ui/dropdown`. antd `Tooltip` (on the order of 100 importers) is a separate migration.
### antd files
`src/components/CustomTimePicker/CustomTimePicker.tsx`
`src/components/DownloadOptionsMenu/DownloadOptionsMenu.tsx`
`src/components/ErrorPopover/ErrorPopover.tsx`
`src/components/ExplorerCard/ExplorerCard.tsx`
`src/components/HeaderRightSection/HeaderRightSection.tsx`
`src/components/Logs/AddToQueryHOC.tsx`
`src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.tsx`
`src/components/QueryBuilderV2/QueryV2/QueryAggregation/QueryAggregationSelect.tsx`
`src/components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch.tsx`
`src/components/QueryBuilderV2/QueryV2/TraceOperator/TraceOperatorEditor.tsx`
`src/components/TableRenderer/LabelColumn.tsx`
`src/components/WarningPopover/WarningPopover.tsx`
`src/container/AlertHistory/AlertPopover/AlertPopover.tsx`
`src/container/CreateAlertV2/EvaluationSettings/EvaluationSettings.tsx`
`src/container/Home/Home.tsx`
`src/container/MetricsExplorer/MetricDetails/AllAttributes.tsx`
`src/container/MetricsExplorer/MetricDetails/AllAttributesValue.tsx`
`src/container/MetricsExplorer/Summary/MetricNameSearch.tsx`
`src/container/OptionsMenu/index.tsx`
`src/container/TopNav/AutoRefreshV2/index.tsx`
`src/pages/DashboardsListPage/components/ListHeader/ListHeader.tsx`
`src/pages/MetricsApplication/ApDex/ApDexApplication.tsx`
`src/pages/PublicDashboard/PublicDashboardView/PublicAutoRefresh/PublicAutoRefresh.tsx`
`src/pages/TraceDetailsV3/SpanDetailsPanel/Events/components/AttributeWithExpandablePopover.tsx`
`src/pages/TracesFunnelDetails/components/FunnelConfiguration/FunnelStepPopover.tsx`
`src/pages/TracesFunnels/components/FunnelsList/FunnelItemPopover.tsx`
`src/pages/TracesFunnels/components/SearchBar/SearchBar.tsx`
`src/periscope/components/ContextMenu/index.tsx`
`src/periscope/components/SeeMore/SeeMore.tsx`
### Existing `@signozhq/ui` popovers
`src/components/ExportMenu/ExportMenu.tsx`
`src/components/MarkdownEditor/MarkdownHelp.tsx`
`src/components/TanStackTableView/TanStackHeaderRow.tsx`
`src/container/AIAssistant/components/ChatInput/ChatInput.tsx`
`src/container/ListAlertRules/components/ColumnSelector.tsx`
`src/pages/DashboardPage/DashboardContainer/PanelEditor/ConfigPane/sections/ContextLinksSection/VariablesPopover.tsx`
`src/pages/DashboardsListPage/components/ViewsRail/ViewNamePopover.tsx`
### SCSS that overrides `.ant-popover`
`src/components/DownloadOptionsMenu/DownloadOptionsMenu.styles.scss`
`src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.styles.scss`
`src/components/QueryBuilderV2/QueryV2/QueryAggregation/QueryAggregation.styles.scss`
`src/components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch.styles.scss`
`src/container/AlertHistory/AlertPopover/AlertPopover.styles.scss`
`src/container/AlertHistory/Statistics/TopContributorsCard/TopContributorsCard.styles.scss`
`src/container/CreateAlertV2/EvaluationSettings/styles.scss`
`src/container/Home/Home.styles.scss`
`src/container/MetricsExplorer/MetricDetails/MetricDetails.styles.scss`
`src/container/MetricsExplorer/Summary/Summary.styles.scss`
`src/container/TopNav/AutoRefreshV2/AutoRefreshV2.styles.scss`
`src/container/TopNav/DateTimeSelectionV2/DateTimeSelectionV2.styles.scss`
`src/container/WidgetCard/WidgetCard.styles.scss`
`src/pages/DashboardsListPage/components/ListHeader/ListHeader.module.scss`
`src/pages/TracesFunnels/TracesFunnels.styles.scss`
`src/pages/TracesFunnels/components/FunnelsList/FunnelsList.styles.scss`
`src/pages/TracesFunnels/components/SearchBar/SearchBar.styles.scss`
`src/periscope/components/ContextMenu/styles.scss`
Three of these are unscoped and restyle every antd popover on the page, not just their own:
- `src/container/MetricsExplorer/Summary/Summary.styles.scss`
- `src/container/CreateAlertV2/EvaluationSettings/styles.scss`
- `src/container/AlertHistory/Statistics/TopContributorsCard/TopContributorsCard.styles.scss`
Scope or delete those three before moving any nearby popover, or a migrated popup and a leftover antd popup will share rules that only one of them understands.
Most of the other files repeat one recipe: padding 0, 4px radius, a 1px border, a translucent background, the arrow hidden. The new shell already has zero padding, `--radius-2`, and no arrow. Per-site size (fixed widths around 187–500px, max-width 600px, max-height 254px) becomes `width` / `maxWidth` / `maxHeight`. Do not re-apply the recipe as a `className`; the component rejects it.
## What should not become this Popover
**Hover descriptions.** `AddToQueryHOC`, `LabelColumn`, the three Query Builder validation popovers, `SeeMore`, and `AttributeWithExpandablePopover` open on hover. Those are Tooltips. `AttributeWithExpandablePopover` shows a value the user copies; if Tooltip cannot hold that, make it a click popover instead of keeping hover.
**WarningPopover** (imported from about 10 files) is hover today and contains docs links. A tooltip cannot host those links. Either the links move out and the summary becomes a Tooltip, or the wrapper becomes a click Popover with `maxWidth` around 600.
**Menus.** Funnel item and funnel step action lists, the sort lists in `ListHeader` and Traces Funnels `SearchBar`, `AutoRefreshV2` / `PublicAutoRefresh` interval lists, and `AlertPopover`'s two links are rows that close when one is picked. Those are Dropdowns. `AllAttributes` per-row actions are the same shape.
**ContextMenu** (`src/periscope/components/ContextMenu/index.tsx`, imported across the dashboard drilldown) opens at chart-click coordinates. There is no trigger element. The new Popover requires `trigger`, so this stays a context menu. Do not fold the phantom span, `position: fixed` overlay, `zIndex: 10000`, or `calculatePopoverPosition.ts` into `anchor`. `anchor` is for a popover that already has a trigger and should position against something else (the typeahead input row).
**Inspect charts.** `MetricsExplorer/Inspect` `GraphPopover` and `HoverPopover` are absolutely positioned cards, not antd Popover. Leave them until there is a context-menu or anchor design that matches them.
**PanelStatusPopover** is a Tooltip despite the name.
## What should become this Popover
Click panels whose content is a form, a picker, or a composed layout:
- `CustomTimePicker` — the most visible one. Controlled `open`, `container` for `getPopupContainer`, and it already unmounts on hide. It opens from both input focus and click; that logic stays in the caller, with `onOpenChange`.
- `EvaluationSettings` / `EvaluationWindowPopover` — controlled, wide (`width` / `maxWidth`), and sitting on a global `.ant-popover` override. Migrate the override in the same change.
- `HeaderRightSection` — three controlled forms (feedback, announcements, share).
- `ExplorerCard` save-view form and `ViewNamePopover` — close only after the save succeeds, so `open` stays controlled. `ViewNamePopover` is already on the library and will not compile against the new props.
- `ApDex` threshold form.
- `Home` welcome checklist — drop the transparent-shell override; the shell is the component's.
- `OptionsMenu`, `DownloadOptionsMenu`, `LogsFormatOptionsMenu` — click panels. `LogsFormatOptionsMenu` nests a submenu; the inner popup must be portalled, and the outer `onOpenChange` must `cancel()` an outside press that lands in the inner popup.
- `MetricNameSearch` — controlled search panel. Scope the global padding override in `Summary.styles.scss` in the same change.
- `AllAttributesValue` — one popover open at a time in a list. That is controlled `open` keyed by row, which the API still allows.
- `ChatInput` context picker — already on the library; `side="top"` `align="end"` plus `width`.
- `VariablesPopover` — already on the library. Keep `anchor` on the input row, `initialFocus={false}` so the caret stays, and `details.cancel()` when the outside press is the anchor. `withPortal={false}` becomes the default dialog `container`, not an unportalled popup.
- `ColumnSelector`, `MarkdownHelp`, `TanStackHeaderRow`, `ExportMenu` — Radix `asChild` trigger plus content. The trigger moves to the `trigger` prop, content moves to `children`, and `className` on the shell becomes `width` / `maxWidth`.
`ErrorPopover` is a thin wrapper. Re-point it at the new props and its consumer follows.
## Order
Before any change: screenshot every current popover, in both themes, so each call site's pattern is on record and a component swap is reviewable against it. Flag the misusages to design as they turn up — links inside hover surfaces above all. Swapping the component during migration is expected where the old call site was on the wrong one; antd call sites stay untouched until their turn.
1. Scope the three global `.ant-popover` files so later migrations cannot restyle leftover antd popovers.
2. Move the seven existing `@signozhq/ui` call sites first. They break at the package bump; the 29 antd files do not, until each one is switched.
3. Migrate shared wrappers that are actually popovers (`ErrorPopover`, then `WarningPopover` once the links decision is made). Leave `ContextMenu` on antd until a context menu exists.
4. Migrate click forms (`CustomTimePicker`, evaluation window, header menus, save-view, ApDex).
5. Send hover descriptions to Tooltip and row menus to Dropdown, instead of forcing them through Popover.
6. Delete the matching `.ant-popover` rules in the same change as each call site, so a half-migrated page is not styled twice.
## Risks
- **Controlled `open` is load-bearing.** Save forms close from inside the popup only when the mutation succeeds. A list keeps one popover open at a time. Dropping `open` and driving everything from `onOpenChange` alone cannot express either. Keep the controlled prop at those sites.
- **Focus moves in.** Click popovers focus the first tabbable child. Typeaheads (`VariablesPopover`, and any filter that must keep the caret) need `initialFocus={false}`.
- **Outside press and anchors.** A press on the anchor looks outside. Call `details.cancel()` or the typeahead closes when the user clicks the input it is anchored to.
- **Dialogs.** Inside a Dialog or Drawer the popup portals into the panel so the focus trap can reach it. Do not set `container={document.body}` there.
- **Z-index.** The shell starts at `--popover-z-index` (50) and lifts above the layer its trigger sits in. A Tooltip inside a row must still paint above the popup. The app ladder today is tooltip 1070, dropdown 1050, popover 1030, modal 1000. Check a truncated row tooltip inside a popover after the first real migration.
- **Scroll locking.** Chart popovers measure the wrong box when a modal has locked scroll. This popover does not lock scroll (`modal` is rejected). Re-check Infrastructure Monitoring charts if a surrounding modal is open.
- **`nativeButton` mismatches.** Base UI logs an error when the flag does not match the element. Every non-button trigger (a badge, a custom row, an input) needs `nativeButton={false}`.
## `--popover-foreground` audit
The shell reads `--popover-background`, `--popover-foreground`, `--popover-border`, and `--popover-shadow`. Those semantic values belong to the token package, not to call sites.
Two legacy shadcn variables predate those hooks:
- `--popover-foreground` is the foreground hook the shell uses. It should stay pointed at the same foreground as the rest of the light and dark themes (`--l1-foreground` today).
- `--popover` (no suffix) is the old background. `--calendar-select-background` still reads it. Do not repoint `--popover` at `--popover-background` or at `surface-3` until the calendar token is audited, or date selection changes color with the popover shell.
Call sites that pass antd's `color` (a gradient string on `AlertPopover`, a raw token on `SeeMore`) do not map to a prop. The shell color comes from the tokens. Content that needs its own background paints it inside `children`.

View File

@@ -52,9 +52,9 @@
"@monaco-editor/react": "^4.7.0",
"@sentry/react": "10.57.0",
"@sentry/vite-plugin": "5.3.0",
"@signozhq/design-tokens": "2.2.1",
"@signozhq/design-tokens": "2.3.0",
"@signozhq/icons": "0.4.0",
"@signozhq/ui": "0.2.0",
"@signozhq/ui": "0.3.0",
"@tanstack/react-table": "8.21.3",
"@tanstack/react-virtual": "3.13.22",
"@uiw/codemirror-theme-copilot": "4.23.11",

450
frontend/pnpm-lock.yaml generated
View File

@@ -72,14 +72,14 @@ importers:
specifier: 5.3.0
version: 5.3.0
'@signozhq/design-tokens':
specifier: 2.2.1
version: 2.2.1
specifier: 2.3.0
version: 2.3.0
'@signozhq/icons':
specifier: 0.4.0
version: 0.4.0
'@signozhq/ui':
specifier: 0.2.0
version: 0.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(date-fns@4.1.0)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.6(react@18.2.0))(react@18.2.0)
specifier: 0.3.0
version: 0.3.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(date-fns@4.1.0)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.6(react@18.2.0))(react@18.2.0)
'@tanstack/react-table':
specifier: 8.21.3
version: 8.21.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -2928,9 +2928,6 @@ packages:
'@posthog/core@1.6.0':
resolution: {integrity: sha512-Tbh8UACwbb7jFdDC7wwXHtfNzO+4wKh3VbyMHmp2UBe6w1jliJixexTJNfkqdGZm+ht3M10mcKvGGPnoZ2zLBg==}
'@radix-ui/number@1.1.3':
resolution: {integrity: sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==}
'@radix-ui/primitive@1.1.7':
resolution: {integrity: sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==}
@@ -2947,19 +2944,6 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-collection@1.1.15':
resolution: {integrity: sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-compose-refs@1.1.5':
resolution: {integrity: sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==}
peerDependencies:
@@ -2991,15 +2975,6 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-direction@1.1.4':
resolution: {integrity: sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-dismissable-layer@1.1.19':
resolution: {integrity: sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==}
peerDependencies:
@@ -3114,71 +3089,6 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-progress@1.1.16':
resolution: {integrity: sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-radio-group@1.4.7':
resolution: {integrity: sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-roving-focus@1.1.19':
resolution: {integrity: sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-select@2.3.7':
resolution: {integrity: sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-slider@1.4.7':
resolution: {integrity: sha512-mTSLf1GC/C0moWjTbvCM6Qn/gBjvlFt1azuWF2v7MN5C3Zq2U2J2lN3ZEYkpujuOU5Ro7A28wkviSxaKnG0BYg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-slot@1.3.3':
resolution: {integrity: sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==}
peerDependencies:
@@ -3188,19 +3098,6 @@ packages:
'@types/react':
optional: true
'@radix-ui/react-tabs@1.1.21':
resolution: {integrity: sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-use-callback-ref@1.1.4':
resolution: {integrity: sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==}
peerDependencies:
@@ -3228,15 +3125,6 @@ packages:
'@types/react':
optional: true
'@radix-ui/react-use-is-hydrated@0.1.3':
resolution: {integrity: sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-use-layout-effect@1.1.4':
resolution: {integrity: sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==}
peerDependencies:
@@ -3246,15 +3134,6 @@ packages:
'@types/react':
optional: true
'@radix-ui/react-use-previous@1.1.4':
resolution: {integrity: sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-use-rect@1.1.4':
resolution: {integrity: sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==}
peerDependencies:
@@ -3273,19 +3152,6 @@ packages:
'@types/react':
optional: true
'@radix-ui/react-visually-hidden@1.2.11':
resolution: {integrity: sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/rect@1.1.3':
resolution: {integrity: sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==}
@@ -3608,14 +3474,14 @@ packages:
'@sideway/pinpoint@2.0.0':
resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==}
'@signozhq/design-tokens@2.2.1':
resolution: {integrity: sha512-ulf6lhWUP6qxqsyemQySUMfOIKRvefvZ5rBRv+wlhOpPfYPk3uatPmPkKEhAjISpX8f9g1Gg8wgRZxUCysf20Q==}
'@signozhq/design-tokens@2.3.0':
resolution: {integrity: sha512-gVTPc2uJwBsTF9VMzPghgsnv6eioK8oxzES1CLDxedxpcfwbWltuzxVPwhPuoC54E2vItDVZhR9RFI/JZ50ldw==}
'@signozhq/icons@0.4.0':
resolution: {integrity: sha512-QctNvNPlQlsefFdIVmSy/P/7a1IcQ3IryKP0KPMqtoqew3q89FGUmFa5IfEJ7Z3CHhtG1zaXwBKqP72QkLyA3g==}
'@signozhq/ui@0.2.0':
resolution: {integrity: sha512-j20ldiOkR150ON0BCDhlq6fXGoPN++7w6e4t2HtM2XMM4buJO7aHzPzNKvDRZ2UUf/LGPULPAW8w6sg6gUVHyw==}
'@signozhq/ui@0.3.0':
resolution: {integrity: sha512-Khzr3/YC/QACwBi1zfQwt+h88iyuIu9hm5hkcIkuCTteD9P+r13US/NN4kkbJe8IHyZXCoDkoIpmQxzbrA3/SA==}
peerDependencies:
'@signozhq/icons': 0.3.0
react: ^18.2.0
@@ -4985,12 +4851,6 @@ packages:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
cmdk@1.1.1:
resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==}
peerDependencies:
react: ^18 || ^19 || ^19.0.0-rc
react-dom: ^18 || ^19 || ^19.0.0-rc
co@4.6.0:
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
@@ -7593,12 +7453,6 @@ packages:
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
next-themes@0.4.6:
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
peerDependencies:
react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
no-case@3.0.4:
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
@@ -8589,8 +8443,8 @@ packages:
'@types/react':
optional: true
react-resizable-panels@4.12.3:
resolution: {integrity: sha512-GHMJWnDXui/3RX4bT+cgBP+N3N2nkwcoZATr/2xLFpqQQe7TlBrE0cGM0dUa9ceT7A90tUrSRsiqgRG+g0/2AA==}
react-resizable-panels@4.12.2:
resolution: {integrity: sha512-NwY5LCo4WrxVvDh0xoMML6EMLPONP/8ckKcIdpnojxexoatZdjLiRqLJQjQK5CPkd4SYiB/2M5BVrjZBQtOO7Q==}
peerDependencies:
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
@@ -9093,12 +8947,6 @@ packages:
resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==}
engines: {node: '>=20'}
sonner@2.0.7:
resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==}
peerDependencies:
react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
sort-asc@0.1.0:
resolution: {integrity: sha512-jBgdDd+rQ+HkZF2/OHCmace5dvpos/aWQpcxuyRs9QUbPRnkEJmYVo81PIGpjIdpOcsnJ4rGjStfDHsbn+UVyw==}
engines: {node: '>=0.10.0'}
@@ -10120,7 +9968,7 @@ snapshots:
'@ant-design/cssinjs@1.17.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@emotion/hash': 0.8.0
'@emotion/unitless': 0.7.5
classnames: 2.3.2
@@ -10136,7 +9984,7 @@ snapshots:
dependencies:
'@ant-design/colors': 7.0.0
'@ant-design/icons-svg': 4.3.1
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -10144,7 +9992,7 @@ snapshots:
'@ant-design/react-slick@1.0.2(react@18.2.0)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
json2mq: 0.2.0
react: 18.2.0
@@ -12670,8 +12518,6 @@ snapshots:
dependencies:
cross-spawn: 7.0.6
'@radix-ui/number@1.1.3': {}
'@radix-ui/primitive@1.1.7': {}
'@radix-ui/react-arrow@1.1.15(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
@@ -12683,18 +12529,6 @@ snapshots:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-collection@1.1.15(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-slot': 1.3.3(@types/react@18.0.26)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-compose-refs@1.1.5(@types/react@18.0.26)(react@18.2.0)':
dependencies:
react: 18.2.0
@@ -12730,12 +12564,6 @@ snapshots:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-direction@1.1.4(@types/react@18.0.26)(react@18.2.0)':
dependencies:
react: 18.2.0
optionalDependencies:
'@types/react': 18.0.26
'@radix-ui/react-dismissable-layer@1.1.19(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/primitive': 1.1.7
@@ -12846,101 +12674,6 @@ snapshots:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-progress@1.1.16(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-radio-group@1.4.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/primitive': 1.1.7
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-direction': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-presence': 1.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.2.6(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-size': 1.1.4(@types/react@18.0.26)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-roving-focus@1.1.19(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/primitive': 1.1.7
'@radix-ui/react-collection': 1.1.15(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-direction': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-id': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.2.6(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.1.4(@types/react@18.0.26)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-select@2.3.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/number': 1.1.3
'@radix-ui/primitive': 1.1.7
'@radix-ui/react-collection': 1.1.15(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-direction': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-focus-guards': 1.1.6(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-id': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-popper': 1.3.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-portal': 1.1.17(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-presence': 1.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-slot': 1.3.3(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.2.6(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-previous': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
aria-hidden: 1.2.6
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-remove-scroll: 2.7.2(@types/react@18.0.26)(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-slider@1.4.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/number': 1.1.3
'@radix-ui/primitive': 1.1.7
'@radix-ui/react-collection': 1.1.15(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-direction': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.2.6(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-previous': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-size': 1.1.4(@types/react@18.0.26)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-slot@1.3.3(@types/react@18.0.26)(react@18.2.0)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
@@ -12948,22 +12681,6 @@ snapshots:
optionalDependencies:
'@types/react': 18.0.26
'@radix-ui/react-tabs@1.1.21(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/primitive': 1.1.7
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-direction': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-id': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-presence': 1.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.2.6(@types/react@18.0.26)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-use-callback-ref@1.1.4(@types/react@18.0.26)(react@18.2.0)':
dependencies:
react: 18.2.0
@@ -12986,24 +12703,12 @@ snapshots:
optionalDependencies:
'@types/react': 18.0.26
'@radix-ui/react-use-is-hydrated@0.1.3(@types/react@18.0.26)(react@18.2.0)':
dependencies:
react: 18.2.0
optionalDependencies:
'@types/react': 18.0.26
'@radix-ui/react-use-layout-effect@1.1.4(@types/react@18.0.26)(react@18.2.0)':
dependencies:
react: 18.2.0
optionalDependencies:
'@types/react': 18.0.26
'@radix-ui/react-use-previous@1.1.4(@types/react@18.0.26)(react@18.2.0)':
dependencies:
react: 18.2.0
optionalDependencies:
'@types/react': 18.0.26
'@radix-ui/react-use-rect@1.1.4(@types/react@18.0.26)(react@18.2.0)':
dependencies:
'@radix-ui/rect': 1.1.3
@@ -13018,20 +12723,11 @@ snapshots:
optionalDependencies:
'@types/react': 18.0.26
'@radix-ui/react-visually-hidden@1.2.11(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/rect@1.1.3': {}
'@rc-component/color-picker@1.4.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@ctrl/tinycolor': 3.6.1
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -13051,7 +12747,7 @@ snapshots:
'@rc-component/mutate-observer@1.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -13067,7 +12763,7 @@ snapshots:
'@rc-component/tour@1.10.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/portal': 1.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@rc-component/trigger': 1.18.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
@@ -13077,7 +12773,7 @@ snapshots:
'@rc-component/trigger@1.18.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/portal': 1.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -13327,41 +13023,33 @@ snapshots:
'@sideway/pinpoint@2.0.0': {}
'@signozhq/design-tokens@2.2.1': {}
'@signozhq/design-tokens@2.3.0': {}
'@signozhq/icons@0.4.0': {}
'@signozhq/ui@0.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(date-fns@4.1.0)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.6(react@18.2.0))(react@18.2.0)':
'@signozhq/ui@0.3.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(date-fns@4.1.0)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.6(react@18.2.0))(react@18.2.0)':
dependencies:
'@base-ui/react': 1.7.0(@date-fns/tz@1.4.1)(@types/react@18.0.26)(date-fns@4.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@chenglou/pretext': 0.0.8
'@radix-ui/react-dialog': 1.1.23(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-icons': 1.3.2(react@18.2.0)
'@radix-ui/react-popover': 1.1.23(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-progress': 1.1.16(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-radio-group': 1.4.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-select': 2.3.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-slider': 1.4.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-slot': 1.3.3(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-tabs': 1.1.21(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@signozhq/icons': 0.4.0
'@tanstack/react-table': 8.21.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@tanstack/react-virtual': 3.14.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@types/lodash-es': 4.17.12
clsx: 2.1.1
cmdk: 1.1.1(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
copy-text-to-clipboard: 3.2.2
dayjs: 1.11.23
lodash-es: 4.18.1
motion: 11.18.2(@emotion/is-prop-valid@1.2.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
next-themes: 0.4.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
nuqs: 2.9.6(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.6(react@18.2.0))(react@18.2.0)
react: 18.2.0
react-compiler-runtime: 1.0.0(react@18.2.0)
react-day-picker: 9.9.0(react@18.2.0)
react-dom: 18.2.0(react@18.2.0)
react-resizable-panels: 4.12.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
sonner: 2.0.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react-resizable-panels: 4.12.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
transitivePeerDependencies:
- '@date-fns/tz'
- '@emotion/is-prop-valid'
@@ -14638,7 +14326,7 @@ snapshots:
better-xlsx@0.7.6:
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
jszip: 3.10.1
kind-of: 6.0.3
@@ -14675,7 +14363,7 @@ snapshots:
broadcast-channel@3.7.0:
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
detect-node: 2.1.0
js-sha3: 0.8.0
microseconds: 0.2.0
@@ -14893,18 +14581,6 @@ snapshots:
clsx@2.1.1: {}
cmdk@1.1.1(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-dialog': 1.1.23(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-id': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
transitivePeerDependencies:
- '@types/react'
- '@types/react-dom'
co@4.6.0: {}
codemirror@6.0.1:
@@ -17528,7 +17204,7 @@ snapshots:
match-sorter@6.3.1:
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
remove-accents: 0.4.2
math-intrinsics@1.1.0: {}
@@ -18044,11 +17720,6 @@ snapshots:
neo-async@2.6.2: {}
next-themes@0.4.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
no-case@3.0.4:
dependencies:
lower-case: 2.0.2
@@ -18696,7 +18367,7 @@ snapshots:
rc-cascader@3.20.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
array-tree-filter: 2.1.0
classnames: 2.3.2
rc-select: 14.10.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18707,7 +18378,7 @@ snapshots:
rc-checkbox@3.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18715,7 +18386,7 @@ snapshots:
rc-collapse@3.7.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18724,7 +18395,7 @@ snapshots:
rc-dialog@9.3.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/portal': 1.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18734,7 +18405,7 @@ snapshots:
rc-drawer@6.5.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/portal': 1.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18744,7 +18415,7 @@ snapshots:
rc-dropdown@4.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/trigger': 1.18.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18753,7 +18424,7 @@ snapshots:
rc-field-form@1.40.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
async-validator: 4.2.5
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18761,7 +18432,7 @@ snapshots:
rc-image@7.3.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/portal': 1.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-dialog: 9.3.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18772,7 +18443,7 @@ snapshots:
rc-input-number@8.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/mini-decimal': 1.0.1
classnames: 2.3.2
rc-input: 1.3.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18782,7 +18453,7 @@ snapshots:
rc-input@1.3.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18790,7 +18461,7 @@ snapshots:
rc-mentions@2.9.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/trigger': 1.18.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-input: 1.3.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18802,7 +18473,7 @@ snapshots:
rc-menu@9.12.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/trigger': 1.18.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18813,7 +18484,7 @@ snapshots:
rc-motion@2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18821,7 +18492,7 @@ snapshots:
rc-notification@5.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18830,7 +18501,7 @@ snapshots:
rc-overflow@1.3.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18839,7 +18510,7 @@ snapshots:
rc-pagination@3.7.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18847,7 +18518,7 @@ snapshots:
rc-picker@3.14.6(date-fns@4.1.0)(dayjs@1.11.20)(moment@2.30.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/trigger': 1.18.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18860,7 +18531,7 @@ snapshots:
rc-progress@3.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18868,7 +18539,7 @@ snapshots:
rc-rate@2.12.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18876,7 +18547,7 @@ snapshots:
rc-resize-observer@1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18885,7 +18556,7 @@ snapshots:
rc-segmented@2.2.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18906,7 +18577,7 @@ snapshots:
rc-slider@10.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18914,7 +18585,7 @@ snapshots:
rc-steps@6.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18922,7 +18593,7 @@ snapshots:
rc-switch@4.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18930,7 +18601,7 @@ snapshots:
rc-table@7.35.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/context': 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18941,7 +18612,7 @@ snapshots:
rc-tabs@12.13.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-dropdown: 4.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-menu: 9.12.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18953,7 +18624,7 @@ snapshots:
rc-textarea@1.5.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-input: 1.3.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18963,7 +18634,7 @@ snapshots:
rc-tooltip@6.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/trigger': 1.18.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
react: 18.2.0
@@ -18971,7 +18642,7 @@ snapshots:
rc-tree-select@5.15.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-select: 14.10.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-tree: 5.8.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18981,7 +18652,7 @@ snapshots:
rc-tree@5.8.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18991,7 +18662,7 @@ snapshots:
rc-upload@4.3.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18999,14 +18670,14 @@ snapshots:
rc-util@5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-is: 18.3.1
rc-virtual-list@3.11.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -19246,7 +18917,7 @@ snapshots:
optionalDependencies:
'@types/react': 18.0.26
react-resizable-panels@4.12.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
react-resizable-panels@4.12.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
@@ -19297,7 +18968,7 @@ snapshots:
react-router@5.3.4(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
history: 4.10.1
hoist-non-react-statics: 3.3.2
loose-envify: 1.4.0
@@ -19834,11 +19505,6 @@ snapshots:
ansi-styles: 6.2.3
is-fullwidth-code-point: 5.1.0
sonner@2.0.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
sort-asc@0.1.0: {}
sort-desc@0.1.1: {}

View File

@@ -15,6 +15,5 @@
}
.divider {
--divider-color: var(--l1-border);
--divider-margin: 10px 0 16px 0;
--divider-border: var(--l1-border);
}

View File

@@ -28,7 +28,11 @@ function AlertBreadcrumb({
items={breadcrumbItems}
data-testid={testId}
/>
{showDivider && <Divider className={styles.divider} />}
{showDivider && (
<div className={styles.divider}>
<Divider spacing="10px 16px" />
</div>
)}
</>
);
}

View File

@@ -1,6 +1,6 @@
import { Copy } from '@signozhq/icons';
import { Badge, type BadgeColorType } from '@signozhq/ui/badge';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { Tooltip } from '@signozhq/ui/tooltip';
import cx from 'classnames';
import { useLayoutEffect, useRef, useState } from 'react';

View File

@@ -1,6 +1,6 @@
import { Copy } from '@signozhq/icons';
import { Badge, type BadgeColorType } from '@signozhq/ui/badge';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { Tooltip } from '@signozhq/ui/tooltip';
import { useCopyToClipboard } from 'react-use';

View File

@@ -56,22 +56,15 @@ function ProgressRender(item: string | number): JSX.Element {
const percent = Number(Number(item).toFixed(1));
return (
<div className="progress-container">
<Progress
percent={percent}
strokeLinecap="butt"
showInfo
strokeColor={((): string => {
const cpuPercent = percent;
if (cpuPercent >= 90) {
return Color.BG_SAKURA_500;
<div className="progress-bar">
<Progress
percent={percent}
showInfo
color={
percent >= 90 ? 'highlight-danger' : percent >= 60 ? 'warning' : 'success'
}
if (cpuPercent >= 60) {
return Color.BG_AMBER_500;
}
return Color.BG_FOREST_500;
})()}
className="progress-bar"
/>
/>
</div>
</div>
);
}

View File

@@ -72,7 +72,7 @@ export default function CeleryTaskDetail({
endTime ? `- ${formatTimestamp(endTime)}` : ''
}`}
</Typography.Text>
<Divider type="vertical" />
<Divider orientation="vertical" />
<Typography.Text className="subtitle">{taskData.value}</Typography.Text>
</div>
</div>

View File

@@ -42,6 +42,16 @@
gap: var(--spacing-1);
margin-bottom: var(--spacing-4);
// Input restyle (the old .create-sa-form__input class, as --input-* tokens)
--input-foreground: var(--l1-foreground);
--input-background: var(--l2-background);
--input-border: var(--l1-border);
--input-font-size: var(--paragraph-base-400-font-size);
--input-border-radius: 2px;
--input-placeholder: var(--l3-foreground);
--input-placeholder-opacity: 1;
--input-focus-ring: var(--primary);
> label {
font-size: var(--paragraph-base-400-font-size);
font-weight: var(--paragraph-base-400-font-weight);
@@ -50,25 +60,6 @@
}
}
&__input {
height: 32px;
color: var(--l1-foreground);
background-color: var(--l2-background);
border-color: var(--l1-border);
font-size: var(--paragraph-base-400-font-size);
border-radius: 2px;
width: 100%;
&::placeholder {
color: var(--l3-foreground);
}
&:focus {
border-color: var(--primary);
box-shadow: none;
}
}
&__error {
font-size: var(--paragraph-small-400-font-size);
color: var(--destructive);

View File

@@ -7,7 +7,7 @@ import { AuthZGuardContent } from 'lib/authz/components/AuthZGuard/AuthZGuardCon
import { SACreatePermission } from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
import { DialogFooter, DialogWrapper } from '@signozhq/ui/dialog';
import { Input } from '@signozhq/ui/input';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
invalidateListServiceAccounts,
@@ -116,11 +116,10 @@ function CreateServiceAccountModal(): JSX.Element {
<Input
id="sa-name"
placeholder="Enter a name"
className="create-sa-form__input"
value={field.value}
onChange={field.onChange}
onBlur={field.onBlur}
data-testid="create-sa-name-input"
testId="create-sa-name-input"
/>
)}
/>

View File

@@ -1,4 +1,4 @@
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import {
setupAuthzAdmin,
setupAuthzDenyAll,
@@ -10,9 +10,9 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import CreateServiceAccountModal from '../CreateServiceAccountModal';
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
toast: { success: jest.fn(), error: jest.fn() },
jest.mock('@signozhq/ui/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
toast: { success: jest.fn(), danger: jest.fn() },
}));
const mockToast = jest.mocked(toast);
@@ -58,7 +58,7 @@ describe('CreateServiceAccountModal', () => {
// The form only renders once the create check resolves, and the name field
// registers its `required` rule on mount, so the empty-form invalid state
// settles a tick later.
await screen.findByTestId('create-sa-name-input');
await screen.findByTestId('create-sa-name-input-field');
await waitFor(() =>
expect(screen.getByTestId('create-sa-submit-btn')).toHaveAttribute(
@@ -72,7 +72,7 @@ describe('CreateServiceAccountModal', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderModal();
const nameInput = await screen.findByTestId('create-sa-name-input');
const nameInput = await screen.findByTestId('create-sa-name-input-field');
const submitBtn = await screen.findByTestId('create-sa-submit-btn');
await user.type(nameInput, 'test');
@@ -90,7 +90,7 @@ describe('CreateServiceAccountModal', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderModal();
const nameInput = await screen.findByTestId('create-sa-name-input');
const nameInput = await screen.findByTestId('create-sa-name-input-field');
await user.type(nameInput, 'Deploy Bot');
const submitBtn = screen.getByTestId('create-sa-submit-btn');
@@ -128,7 +128,7 @@ describe('CreateServiceAccountModal', () => {
renderModal();
const nameInput = await screen.findByTestId<HTMLInputElement>(
'create-sa-name-input',
'create-sa-name-input-field',
);
await user.type(nameInput, 'Deploy Bot');
const submitBtn = screen.getByTestId('create-sa-submit-btn');
@@ -160,7 +160,7 @@ describe('CreateServiceAccountModal', () => {
renderModal();
const nameInput = await screen.findByTestId('create-sa-name-input');
const nameInput = await screen.findByTestId('create-sa-name-input-field');
await user.type(nameInput, 'Dupe Bot');
const submitBtn = screen.getByTestId('create-sa-submit-btn');
@@ -209,7 +209,9 @@ describe('CreateServiceAccountModal', () => {
screen.findByText(/is not authorized to perform/i),
).resolves.toBeInTheDocument();
expect(screen.queryByTestId('create-sa-name-input')).not.toBeInTheDocument();
expect(
screen.queryByTestId('create-sa-name-input-field'),
).not.toBeInTheDocument();
expect(
screen.getByTestId('create-service-account-modal'),
).toBeInTheDocument();
@@ -244,7 +246,7 @@ describe('CreateServiceAccountModal', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderModal();
const nameInput = await screen.findByTestId('create-sa-name-input');
const nameInput = await screen.findByTestId('create-sa-name-input-field');
await user.type(nameInput, 'Bot');
await user.clear(nameInput);

View File

@@ -34,34 +34,19 @@ $item-spacing: 8px;
@include flex-center;
gap: 6px;
width: -webkit-fill-available;
}
&__input {
@include text-style-base;
width: 100%;
background: transparent;
border: none;
border-radius: 0;
box-shadow: none;
outline: none;
height: auto;
color: var(--l1-foreground);
font-size: 14px;
line-height: 20px;
letter-spacing: -0.07px;
padding: 0;
&:focus,
&:focus-visible,
&:hover {
border: none;
box-shadow: none;
outline: none;
}
&::placeholder {
color: var(--l2-foreground);
}
// Restyle the unstyled search Input (old .timezone-picker__input rules,
// as --input-* tokens picked up by the Input frame inside)
--input-height: auto;
--input-padding: 0;
--input-border-width: 0;
--input-border-radius: 0;
--input-foreground: var(--l1-foreground);
--input-font-size: 14px;
--input-line-height: 20px;
--input-letter-spacing: -0.07px;
--input-placeholder: var(--l2-foreground);
--input-placeholder-opacity: 1;
}
&__esc-key {

View File

@@ -65,7 +65,8 @@ function SearchBar({
/>
<Input
type="text"
className="timezone-picker__input"
variant="unstyled"
noFocusRing
placeholder="Search timezones..."
value={value}
onChange={(e): void => onChange(e.target.value)}

View File

@@ -5,7 +5,7 @@ import { Badge } from '@signozhq/ui/badge';
import { Button } from '@signozhq/ui/button';
import { DrawerWrapper } from '@signozhq/ui/drawer';
import { Input } from '@signozhq/ui/input';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { Skeleton, Tooltip } from 'antd';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import type { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
@@ -409,7 +409,8 @@ function EditMemberDrawer({
setShowResetLinkDialog(true);
onClose();
} else {
toast.error('Failed to generate password reset link', {
toast.danger('Failed to generate password reset link', {
action: { label: 'Dismiss' },
position: 'top-right',
});
}
@@ -438,7 +439,7 @@ function EditMemberDrawer({
useEffect(() => {
if (copyState.error) {
toast.error('Failed to copy link');
toast.danger('Failed to copy link', { action: { label: 'Dismiss' } });
}
}, [copyState.error]);
@@ -471,20 +472,19 @@ function EditMemberDrawer({
<label className="edit-member-drawer__label" htmlFor="member-name">
Name
</label>
<Tooltip title={isRootUser ? ROOT_USER_TOOLTIP : undefined}>
<Input
id="member-name"
value={localDisplayName}
onChange={(e): void => {
setLocalDisplayName(e.target.value);
setSaveErrors((prev) =>
prev.filter((err) => err.context !== 'Name update'),
);
}}
placeholder="Enter name"
disabled={isRootUser || isDeleted}
/>
</Tooltip>
<Input
id="member-name"
value={localDisplayName}
onChange={(e): void => {
setLocalDisplayName(e.target.value);
setSaveErrors((prev) =>
prev.filter((err) => err.context !== 'Name update'),
);
}}
placeholder="Enter name"
disabled={isRootUser || isDeleted}
disabledTooltip={isRootUser ? ROOT_USER_TOOLTIP : undefined}
/>
</div>
<div className="edit-member-drawer__field">

View File

@@ -1,5 +1,5 @@
import type { ReactNode } from 'react';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
useCreateResetPasswordToken,
@@ -82,11 +82,11 @@ jest.mock('@signozhq/ui/dialog', () => ({
),
}));
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
jest.mock('@signozhq/ui/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
toast: {
success: jest.fn(),
error: jest.fn(),
danger: jest.fn(),
},
}));

View File

@@ -32,12 +32,16 @@
}
}
// Wraps the search Input; restyles it via its --input-* tokens
// (the Input is size="large" for the 40px height).
.searchInput {
background-color: var(--l1-background);
height: 40px;
border-radius: 0;
border-left: none;
border-right: none;
--input-background: var(--l1-background);
--input-border-radius: 0;
[data-slot='input'] {
border-left: none;
border-right: none;
}
}
.section {

View File

@@ -1,5 +1,5 @@
import { useCallback, useMemo, useState } from 'react';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { Button } from '@signozhq/ui/button';
import { Input } from '@signozhq/ui/input';
import useDebouncedFn from 'hooks/useDebouncedFunction';
@@ -140,10 +140,10 @@ function FieldsSelectorContent({
<X className={styles.closeIcon} size={16} onClick={onClose} />
</div>
<section>
<section className={styles.searchInput}>
<Input
className={styles.searchInput}
type="text"
size="large"
value={inputValue}
placeholder="Search for a field..."
onChange={handleInputChange}

View File

@@ -13,9 +13,9 @@ jest.mock('hooks/querySuggestions/useFieldKeysSuggestion', () => ({
})),
}));
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
toast: { success: jest.fn(), error: jest.fn() },
jest.mock('@signozhq/ui/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
toast: { success: jest.fn(), danger: jest.fn() },
}));
// FloatingPanel is a react-rnd/portal shell — presentation only. Render its

View File

@@ -1,6 +1,6 @@
import { useCallback, useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { Button, Input } from 'antd';
import { ToggleGroup } from '@signozhq/ui/toggle-group';
import { Typography } from '@signozhq/ui/typography';
@@ -39,7 +39,8 @@ function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
})
.catch(() => {
console.error(`Failed to submit ${entityName}`);
toast.error(`Failed to submit ${entityName}`, {
toast.danger(`Failed to submit ${entityName}`, {
action: { label: 'Dismiss' },
position: 'top-right',
});
})

View File

@@ -1,6 +1,6 @@
// Mock dependencies before imports
import { useLocation } from 'react-router-dom';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { logEventMock } from '__tests__/logEventMock';
@@ -14,11 +14,11 @@ jest.mock('react-router-dom', () => ({
useLocation: jest.fn(),
}));
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
jest.mock('@signozhq/ui/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
toast: {
success: jest.fn(),
error: jest.fn(),
danger: jest.fn(),
},
}));
@@ -50,7 +50,7 @@ describe('FeedbackModal', () => {
isCloudUser: false,
});
mockToast.success.mockClear();
mockToast.error.mockClear();
mockToast.danger.mockClear();
});
it('should render feedback modal with all tabs', () => {

View File

@@ -30,63 +30,41 @@
font-weight: var(--font-weight-light);
}
// Wrapper around the Input; restyles the frame inside via --input-* tokens.
.input {
flex: 1;
min-width: 150px;
font-family: 'Space Mono', monospace !important;
--input-border-radius: 0px;
border: 1px solid var(--input-with-label-border-color, var(--l2-border));
background: var(--input-with-label-background-color, var(--l2-background));
color: var(--input-with-label-color, var(--l2-foreground));
font-size: 12px !important;
line-height: 25px;
position: relative;
--input-font-family: 'Space Mono', monospace;
--input-font-size: 12px;
--input-border-radius: 0px;
--input-border: var(--input-with-label-border-color, var(--l2-border));
--input-background: var(
--input-with-label-background-color,
var(--l2-background)
);
--input-foreground: var(--input-with-label-color, var(--l2-foreground));
--input-placeholder: var(--input-with-label-color, var(--l3-foreground));
--input-placeholder-opacity: 1;
&:hover,
&:focus {
z-index: 1;
}
.ant-select-selector {
position: relative;
border-radius: inherit;
}
.ant-select:hover .ant-select-selector,
.ant-select-focused .ant-select-selector {
&:focus-within {
z-index: 1;
}
&.input__has-label-after {
margin-left: -1px;
.ant-select-selector {
margin-left: -1px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
&.input__has-close-button {
.ant-select-selector {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
~ .close-btn {
margin-left: -1px;
}
}
&::placeholder {
color: var(--input-with-label-color, var(--l3-foreground)) !important;
font-size: 12px !important;
}
&[type='number']::-webkit-inner-spin-button,
&[type='number']::-webkit-outer-spin-button {
input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
@@ -109,17 +87,8 @@
}
&.labelAfter {
.input {
border-radius: 2px;
border: 1px solid var(--input-with-label-border-color, var(--l2-border));
background: var(--input-with-label-background-color, var(--l2-background));
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
.ant-select-selector {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.input [data-slot='input'] {
border-radius: 2px 0 0 2px;
}
.label {

View File

@@ -46,19 +46,23 @@ function InputWithLabel({
})}
>
{!labelAfter && <Typography.Text className="label">{label}</Typography.Text>}
<Input
<div
className={cx('input', {
'input__has-label-after': !labelAfter,
'input__has-close-button': !!onClose,
})}
placeholder={placeholder}
type={type}
value={inputValue}
onChange={handleChange}
disabled={disabled}
name={label.toLowerCase()}
data-testid={`input-${label}`}
/>
>
<Input
placeholder={placeholder}
type={type}
value={inputValue}
onChange={handleChange}
disabled={disabled}
disabledTooltip={undefined}
name={label.toLowerCase()}
testId={`input-${label}`}
/>
</div>
{labelAfter && <Typography.Text className="label">{label}</Typography.Text>}
{onClose && (
<Button

View File

@@ -59,18 +59,6 @@
--invite-members-field-background,
var(--l2-background)
);
--input-hover-background: var(
--invite-members-field-background,
var(--l2-background)
);
--input-focus-background: var(
--invite-members-field-background,
var(--l2-background)
);
--input-disabled-background: var(
--invite-members-field-background,
var(--l2-background)
);
input::placeholder {
color: var(--l3-foreground);

View File

@@ -1,4 +1,4 @@
import { CircleAlert, Plus, Trash2 } from '@signozhq/icons';
import { CircleAlert, Plus, SolidCheckCircle2, Trash2 } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { Callout } from '@signozhq/ui/callout';
import { Input } from '@signozhq/ui/input';
@@ -97,7 +97,7 @@ function InviteMembers({
onChange={(e): void => updateEmail(row.id, e.target.value)}
name={`invite-email-${row.id}`}
autoComplete="email"
data-testid={`invite-email-${row.id}`}
testId={`invite-email-${row.id}`}
/>
{emailValidity[row.id] === false && row.email.trim() !== '' && (
<Typography.Text size="small" className={styles.errorText}>
@@ -152,61 +152,62 @@ function InviteMembers({
</div>
{hasValidationErrors && (
<Callout
type="error"
size="small"
showIcon
icon={<CircleAlert size={12} />}
className={styles.callout}
data-testid="invite-validation-error"
>
{getValidationErrorMessage()}
</Callout>
<div className={styles.callout}>
<Callout
color="danger"
size="sm"
icon={<CircleAlert />}
testId="invite-validation-error"
>
{getValidationErrorMessage()}
</Callout>
</div>
)}
{hasResults && hasFailures && (
<Callout
type="error"
size="small"
showIcon
icon={<CircleAlert size={12} />}
className={styles.callout}
data-testid="invite-api-error"
>
<div className={styles.results}>
{hasSuccesses && (
<div className={styles.callout}>
<Callout
color="danger"
size="sm"
icon={<CircleAlert />}
testId="invite-api-error"
>
<div className={styles.results}>
{hasSuccesses && (
<Typography.Text size="small">
{successResults.length} invite(s) sent successfully.
</Typography.Text>
)}
<Typography.Text size="small">
{successResults.length} invite(s) sent successfully.
{failedResults.length} invite(s) failed:
</Typography.Text>
)}
<Typography.Text size="small">
{failedResults.length} invite(s) failed:
</Typography.Text>
<ul className={styles.resultsList}>
{failedResults.map((result) => (
<li key={result.email}>
<Typography.Text size="small">
{result.email}: {result.error}
</Typography.Text>
</li>
))}
</ul>
</div>
</Callout>
<ul className={styles.resultsList}>
{failedResults.map((result) => (
<li key={result.email}>
<Typography.Text size="small">
{result.email}: {result.error}
</Typography.Text>
</li>
))}
</ul>
</div>
</Callout>
</div>
)}
{hasResults && !hasFailures && hasSuccesses && (
<Callout
type="success"
size="small"
showIcon
className={styles.callout}
data-testid="invite-success"
>
<Typography.Text size="small">
{successResults.length} invite(s) sent successfully!
</Typography.Text>
</Callout>
<div className={styles.callout}>
<Callout
color="success"
size="sm"
icon={<SolidCheckCircle2 />}
testId="invite-success"
>
<Typography.Text size="small">
{successResults.length} invite(s) sent successfully!
</Typography.Text>
</Callout>
</div>
)}
{renderFooter?.({

View File

@@ -12,11 +12,6 @@
gap: 8px;
}
.divider {
height: 16px;
margin: 0;
}
.timestamp {
font-family: 'Geist Mono', monospace;
font-size: var(--font-size-sm);

View File

@@ -5,7 +5,7 @@ import { Typography } from '@signozhq/ui/typography';
import { Tooltip } from '@signozhq/ui/tooltip';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { aggregateAttributesResourcesToString } from 'container/LogDetailedView/utils';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
import {
ChevronDown,
@@ -76,7 +76,7 @@ function LogDetailsHeader({
return (
<div className={styles.header} data-log-detail-ignore="true">
<div className={styles.leftSection}>
<Divider type="vertical" className={styles.divider} />
<Divider orientation="vertical" height={16} spacing={0} />
<Typography.Text
className={styles.timestamp}
data-testid="log-details-header-timestamp"

View File

@@ -1,4 +1,4 @@
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { LOCALSTORAGE } from 'constants/localStorage';
import { render, screen, userEvent } from 'tests/test-utils';
import { ILog } from 'types/api/logs/log';
@@ -7,8 +7,8 @@ import LogDetail from '..';
import { VIEW_TYPES } from '../constants';
import { LogDetailProps } from '../LogDetail.interfaces';
jest.mock('@signozhq/ui/sonner', () => ({
toast: { success: jest.fn(), error: jest.fn() },
jest.mock('@signozhq/ui/toast', () => ({
toast: { success: jest.fn(), danger: jest.fn() },
}));
// DataViewer pulls in react-json-tree (ESM) + Monaco; mock it (as trace's tests

View File

@@ -202,46 +202,19 @@
.max-lines-per-row-input {
display: flex;
.ant-input-number-handler-wrap {
display: none;
}
--input-height: 26px;
--input-border-radius: 0;
--input-border: var(--l1-border);
.ant-input-number {
[data-testid='max-lines-per-row-field'] {
min-width: 36px;
width: auto;
border-right: none;
border-left: none;
border-top: 1px solid var(--l1-border);
border-bottom: 1px solid var(--l1-border);
text-align: center;
height: 26px;
border-radius: 0;
&:active,
&:focus {
border: none;
box-shadow: none;
}
}
.ant-input-number-focused {
box-shadow: none !important;
}
.ant-input-number-input-wrap {
input {
text-align: center;
font-size: 13px;
&:active,
&:focus {
border: none;
}
}
&:active,
&:focus {
border: none;
}
}

View File

@@ -1,5 +1,6 @@
import { useCallback, useEffect, useState } from 'react';
import { Button, InputNumber, Popover, Tooltip } from 'antd';
import { Input } from '@signozhq/ui/input';
import { Button, Popover, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import cx from 'classnames';
import { LogViewMode } from 'container/OptionsMenu/types';
@@ -207,11 +208,14 @@ function OptionsMenu({
{' '}
<Minus size={12} />{' '}
</button>
<InputNumber
<Input.Number
min={1}
max={10}
value={maxLinesPerRow}
onChange={handleLinesPerRowChange}
controls={false}
noFocusRing
testId="max-lines-per-row-field"
/>
<button
type="button"

View File

@@ -1,13 +0,0 @@
.banner {
height: var(--spacing-20);
a {
color: var(--callout-warning-title);
text-decoration: underline;
&:hover {
color: var(--callout-warning-title);
opacity: 0.8;
}
}
}

View File

@@ -1,25 +1,22 @@
import { PersistedAnnouncementBanner } from '@signozhq/ui/announcement-banner';
import styles from './NoAuthBanner.module.scss';
import { AlertStrip } from '@signozhq/ui/alert-strip';
export function NoAuthBanner(): JSX.Element {
return (
<PersistedAnnouncementBanner
type="warning"
<AlertStrip.CloseablePersisted
color="warning"
side="top"
storageKey="no-auth-banner-v1"
testId="no-auth-banner"
className={styles.banner}
>
Impersonation mode: authentication is disabled. Anyone with access to this
instance has admin privileges.{' '}
<a
<AlertStrip.Link
href="https://signoz.io/docs/manage/administrator-guide/configuration/impersonation-mode/"
target="_blank"
rel="noreferrer"
>
Learn more
</a>
</PersistedAnnouncementBanner>
</AlertStrip.Link>
</AlertStrip.CloseablePersisted>
);
}

View File

@@ -19,6 +19,6 @@ describe('NoAuthBanner', () => {
render(<NoAuthBanner />);
const link = screen.getByRole('link', { name: /learn more/i });
expect(link).toHaveAttribute('target', '_blank');
expect(link).toHaveAttribute('rel', 'noreferrer');
expect(link).toHaveAttribute('rel', expect.stringContaining('noreferrer'));
});
});

View File

@@ -68,11 +68,7 @@
// Meant to fix the query builder colors
--input-background: var(--l2-background);
--input-hover-background: var(--l2-background);
--input-focus-background: var(--l2-background);
--input-border-color: var(--l2-border);
--input-hover-border-color: var(--internal-ant-border-color-hover);
--input-focus-border-color: var(--internal-ant-border-color-hover);
}
&-aggregation-container {

View File

@@ -168,7 +168,7 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
expect(limitInput).toBeInTheDocument();
expect(limitInput).toHaveAttribute('type', 'number');
expect(limitInput).toHaveAttribute('name', 'limit');
expect(limitInput).toHaveAttribute('data-testid', 'input-Limit');
expect(limitInput).toHaveAttribute('data-testid', 'input-Limit-field');
});
it('Cmd+Enter on an input triggers handleRunQuery via container handler', async () => {

View File

@@ -159,7 +159,7 @@ describe('QueryAddOns', () => {
/>,
);
const input = screen.getByTestId('input-Limit') as HTMLInputElement;
const input = screen.getByTestId('input-Limit-field') as HTMLInputElement;
expect(screen.getByTestId('limit-content')).toBeInTheDocument();
expect(input.value).toBe('5');
@@ -185,7 +185,9 @@ describe('QueryAddOns', () => {
);
expect(screen.getByTestId('order-by-content')).toBeInTheDocument();
const limitInput = screen.getByTestId('input-Limit') as HTMLInputElement;
const limitInput = screen.getByTestId(
'input-Limit-field',
) as HTMLInputElement;
expect(screen.getByTestId('limit-content')).toBeInTheDocument();
expect(limitInput.value).toBe('7');
});

View File

@@ -6,18 +6,15 @@
border-bottom: 1px solid var(--l1-border);
.search {
input {
--input-background: var(--l2-background);
--input-hover-background: var(--l2-background);
--input-focus-background: var(--l2-background);
&::placeholder {
color: var(--l3-foreground);
}
--input-font-size: 14px;
--input-border-color: var(--l1-border);
--input-focus-border-color: var(--primary-background);
--input-focus-outline-width: 0;
--input-focus-outline-offset: 0;
// Set on the wrapper so the Input frame inside picks them up.
--input-background: var(--l2-background);
--input-font-size: 14px;
--input-border-color: var(--l1-border);
--input-focus-ring-color: var(--primary-background);
--input-focus-outline-offset: 0;
input::placeholder {
color: var(--l3-foreground);
}
}

View File

@@ -116,6 +116,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
placeholder="Filter values"
onChange={(e): void => setSearchTextDebounced(e.target.value)}
disabled={isFilterDisabled}
disabledTooltip={FILTER_DISABLED_REASON}
/>
</section>
)}

View File

@@ -46,10 +46,6 @@
.search {
--input-background: var(--l2-background);
--input-hover-background: var(--l2-background);
--input-focus-background: var(--l2-background);
--input-border-color: var(--l2-border);
--input-focus-border-color: var(--l2-border);
}
.searchSpinner {

View File

@@ -19,7 +19,9 @@ import { NON_SELECTED_OPERATORS } from '../checkboxFilterQuery';
import useActiveQueryIndex from 'components/QuickFilters/hooks/useActiveQueryIndex';
import useCheckboxDisclosure from '../useCheckboxDisclosure';
import useCheckboxFilterActions from '../useCheckboxFilterActions';
import useCheckboxFilterState from '../useCheckboxFilterState';
import useCheckboxFilterState, {
FILTER_DISABLED_REASON,
} from '../useCheckboxFilterState';
import { useFieldValues } from './useFieldValues';
import { useExistingQuery } from './useExistingQuery';
import { isKeyMatch } from '../utils';
@@ -195,7 +197,8 @@ export default function CheckboxFilterV2(
placeholder="Filter values"
onChange={(e): void => setSearchTextDebounced(e.target.value)}
disabled={isFilterDisabled}
data-testid="checkbox-filter-search"
disabledTooltip={FILTER_DISABLED_REASON}
testId="checkbox-filter-search"
suffix={
isFetching ? (
<LoaderCircle

View File

@@ -61,7 +61,7 @@ describe('CheckboxFilterV2 - interactions', () => {
expect(screen.getByTestId('checkbox-value-row-staging')).toBeInTheDocument();
await user.click(screen.getByTestId('checkbox-filter-search-toggle'));
const searchInput = screen.getByTestId('checkbox-filter-search');
const searchInput = screen.getByTestId('checkbox-filter-search-field');
await user.type(searchInput, 'prod');
await waitFor(() => {
@@ -145,7 +145,7 @@ describe('CheckboxFilterV2 - interactions', () => {
expect(screen.getByTestId('section-divider-related')).toBeInTheDocument();
await user.click(screen.getByTestId('checkbox-filter-search-toggle'));
const searchInput = screen.getByTestId('checkbox-filter-search');
const searchInput = screen.getByTestId('checkbox-filter-search-field');
await user.type(searchInput, 'prod');
await waitFor(() => {
@@ -195,7 +195,7 @@ describe('CheckboxFilterV2 - interactions', () => {
await screen.findByTestId('checkbox-value-row-prod');
await user.click(screen.getByTestId('checkbox-filter-search-toggle'));
const searchInput = screen.getByTestId('checkbox-filter-search');
const searchInput = screen.getByTestId('checkbox-filter-search-field');
await user.type(searchInput, 'prod');
await waitFor(() => {
@@ -240,7 +240,7 @@ describe('CheckboxFilterV2 - interactions', () => {
await screen.findByTestId('checkbox-value-row-prod');
await user.click(screen.getByTestId('checkbox-filter-search-toggle'));
const searchInput = screen.getByTestId('checkbox-filter-search');
const searchInput = screen.getByTestId('checkbox-filter-search-field');
await user.type(searchInput, 'xyz-no-match');
await waitFor(() => {
@@ -348,7 +348,7 @@ describe('CheckboxFilterV2 - interactions', () => {
await screen.findByTestId('checkbox-value-row-pod-a-v1');
await user.click(screen.getByTestId('checkbox-filter-search-toggle'));
const searchInput = screen.getByTestId('checkbox-filter-search');
const searchInput = screen.getByTestId('checkbox-filter-search-field');
await user.type(searchInput, 'pod-a');
// After search, both sections should still appear with filtered results

View File

@@ -111,7 +111,7 @@ describe('CheckboxFilterV2 - states', () => {
await screen.findByTestId('checkbox-value-row-production');
await user.click(screen.getByTestId('checkbox-filter-search-toggle'));
const searchInput = screen.getByTestId('checkbox-filter-search');
const searchInput = screen.getByTestId('checkbox-filter-search-field');
await user.type(searchInput, 'prod');
await waitFor(() => {

View File

@@ -8,7 +8,7 @@ import {
useState,
} from 'react';
import { Input } from 'antd';
import { Slider } from '@signozhq/ui/slider';
import { Slider, type SliderRangeValue } from '@signozhq/ui/slider';
import { getMs } from 'utils/timeUtils';
import useDebouncedFn from 'hooks/useDebouncedFunction';
@@ -90,13 +90,13 @@ export function DurationSection(props: DurationProps): JSX.Element {
debouncedFunction(min, max);
};
const onRangeHandler = (value: number | number[]): void => {
const [min, max] = value as number[];
const onRangeHandler = (value: SliderRangeValue): void => {
const [min, max] = value;
updateDurationFilter(min.toString(), max.toString());
};
const TipComponent = useCallback(
(value: number) => <div>{`${value.toString()}ms`}</div>,
const formatTooltipValue = useCallback(
(value: number): string => `${value.toString()}ms`,
[],
);
@@ -125,13 +125,14 @@ export function DurationSection(props: DurationProps): JSX.Element {
/>
</div>
<div className="duration-input-slider">
<Slider
<Slider.Range
min={0}
max={100000}
range
tooltip={{ formatter: TipComponent }}
color="primary"
tooltip
formatValue={formatTooltipValue}
onChange={(value): void => {
const [min, max] = value as number[];
const [min, max] = value;
onRangeSliderHandler([String(min), String(max)]);
}}
onAfterChange={onRangeHandler}

View File

@@ -55,8 +55,7 @@
min-width: 70px;
}
.sync-tag,
.select-box {
.sync-tag {
display: flex;
padding: 5px 9px;
flex-direction: column;
@@ -77,17 +76,21 @@
}
.select-box {
display: flex;
padding: 2px 5px;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 3px;
height: 30px;
}
.select-box-item {
min-width: 60px;
text-transform: uppercase;
--combobox-trigger-background: var(--primary-background);
--combobox-trigger-border: color-mix(
in srgb,
var(--primary-background) 20%,
transparent
);
--combobox-trigger-border-hover: var(--primary-background);
--combobox-trigger-label: var(--primary-foreground);
--combobox-trigger-icon: var(--primary-foreground);
--combobox-trigger-icon-hover: var(--primary-foreground);
--combobox-trigger-font-family: 'Geist Mono';
--combobox-trigger-border-radius: 2px;
--combobox-trigger-min-block-size: 30px;
}
}

View File

@@ -6,14 +6,7 @@ import {
RefreshCw,
Settings2 as SettingsIcon,
} from '@signozhq/icons';
import {
Combobox,
ComboboxCommand,
ComboboxContent,
ComboboxItem,
ComboboxList,
ComboboxTrigger,
} from '@signozhq/ui/combobox';
import { Combobox, type ComboboxItemType } from '@signozhq/ui/combobox';
import { Skeleton, Tooltip } from 'antd';
import { Button } from '@signozhq/ui/button';
import { Switch } from '@signozhq/ui/switch';
@@ -83,7 +76,6 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
redirectWithQueryBuilderData,
panelType,
} = useQueryBuilder();
const [open, setOpen] = useState(false);
// Sync lastUsedQuery when queries change (e.g., after deletion)
const validQueryIndex = useMemo(
@@ -110,6 +102,16 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
}));
}, [currentQuery?.builder?.queryData]);
const queryItems = useMemo<ComboboxItemType[]>(
() =>
queryOptions.map((option) => ({
type: 'item',
value: String(option.value),
label: option.label,
})),
[queryOptions],
);
// Show dropdown in ListView only for TRACES_EXPLORER source
const shouldShowDropdownInListView =
isListView && source === QuickFiltersSource.TRACES_EXPLORER;
@@ -186,34 +188,20 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
{queryOptions.length > 1 &&
!isAIObservabilityRowView &&
(!isListView || shouldShowDropdownInListView) ? (
<Combobox open={open} onOpenChange={setOpen}>
<ComboboxTrigger
<div className="select-box">
<Combobox
aria-label="Select a query"
placeholder="Select a query"
value={queryOptions.find((f) => f.value === validQueryIndex)?.label || ''}
className="select-box"
searchInputProps={{ placeholder: 'Search queries' }}
items={queryItems}
value={String(validQueryIndex)}
onChange={(value): void => {
if (value !== undefined) {
handleQueryChange(Number(value));
}
}}
/>
{open && (
<ComboboxContent>
<ComboboxCommand>
<ComboboxList>
{queryOptions.map((option) => (
<ComboboxItem
key={option.value}
value={String(option.value)}
onSelect={(): void => {
handleQueryChange(option.value);
setOpen(false);
}}
isSelected={validQueryIndex === option.value}
>
{option.label}
</ComboboxItem>
))}
</ComboboxList>
</ComboboxCommand>
</ComboboxContent>
)}
</Combobox>
</div>
) : (
displayedQueryName && (
<Tooltip

View File

@@ -1,6 +1,7 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import type { ComponentProps, ComponentType } from 'react';
import { expect, screen, userEvent, waitFor, within } from 'storybook/test';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { withCanvas } from '@/storybook/decorators/withCanvas';
import type { GlobalMockArgs } from '@/storybook/globals';
@@ -15,7 +16,7 @@ import {
queryBuilder,
selectedServiceQueryBuilder,
} from './QuickFilters.stories.mocks';
import { QuickFiltersSource, SignalType } from '../types';
import { FiltersType, QuickFiltersSource, SignalType } from '../types';
const meta = {
title: 'Components/Quick Filters',
@@ -56,10 +57,10 @@ type TooltipsStory = StoryObj<
* silence.
*/
const settingsControl = (canvasElement: HTMLElement): Promise<HTMLElement> =>
waitFor(() => {
waitFor(async () => {
const control = within(canvasElement).getByTestId('settings-icon-container');
expect(control).not.toHaveAttribute('aria-disabled', 'true');
await expect(control).not.toHaveAttribute('aria-disabled', 'true');
return control;
});
@@ -105,6 +106,27 @@ export const SelectedExpandedCheckbox: Story = {
},
};
/** The duration min/max row, which a checkbox-only config never reaches. */
export const DurationFilter: Story = {
args: {
signal: undefined,
source: QuickFiltersSource.TRACES_EXPLORER,
config: [
...checkboxConfig,
{
attributeKey: {
dataType: DataTypes.Float64,
key: 'duration_nano',
type: '',
},
defaultOpen: true,
title: 'Duration',
type: FiltersType.DURATION,
},
],
},
};
/**
* Every tooltip the panel renders, held open: the reveal on each truncated
* filter value. Nothing bounds those values, so the panel is answered with

View File

@@ -202,15 +202,29 @@ describe('Quick Filters', () => {
expect(screen.getByRole('option', { name: 'Query C' })).toBeInTheDocument();
});
// Select Query B
const queryBOption = screen.getByRole('option', { name: 'Query B' });
await user.click(queryBOption);
// Select Query B with the keyboard: jsdom cannot deliver the pointer
// click Base UI expects, so filter through the search row and pick with
// Enter instead
await waitFor(() => {
expect(
screen.getByRole('combobox', { name: 'Search queries' }),
).toHaveFocus();
});
await user.keyboard('Query B');
await waitFor(() => {
expect(
screen.queryByRole('option', { name: 'Query A' }),
).not.toBeInTheDocument();
});
await user.keyboard('{ArrowDown}{Enter}');
// Verify setLastUsedQuery was called with index 1
await waitFor(() => {
expect(setLastUsedQuery).toHaveBeenCalledWith(1);
});
});
// filtering and picking through the search row takes Base UI past the
// default 5s under jsdom
}, 20000);
it('should not display query dropdown in ListView', () => {
(useQueryBuilder as jest.Mock).mockReturnValue({

View File

@@ -1,4 +1,4 @@
import { Check, Copy } from '@signozhq/icons';
import { Check, Copy, SolidInfoCircle } from '@signozhq/icons';
import { Badge } from '@signozhq/ui/badge';
import { Button } from '@signozhq/ui/button';
import { Callout } from '@signozhq/ui/callout';
@@ -44,11 +44,9 @@ function KeyCreatedPhase({
</div>
<div className="add-key-modal__callout-wrapper">
<Callout
type="info"
showIcon
title="Store the key securely. This is the only time it will be displayed."
/>
<Callout color="info" size="sm" icon={<SolidInfoCircle />}>
Store the key securely. This is the only time it will be displayed.
</Callout>
</div>
</div>
);

View File

@@ -53,7 +53,6 @@ function KeyFormPhase({
<Input
id="key-name"
placeholder="Enter key name e.g.: Service Owner"
className="add-key-modal__input"
testId="add-key-name-input"
{...register('keyName', {
required: true,

View File

@@ -3,7 +3,7 @@ import { useForm } from 'react-hook-form';
import { useQueryClient } from 'react-query';
import { useCopyToClipboard } from 'react-use';
import { DialogWrapper } from '@signozhq/ui/dialog';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
invalidateListServiceAccountKeys,
@@ -119,7 +119,7 @@ function AddKeyModal(): JSX.Element {
useEffect(() => {
if (copyState.error) {
toast.error('Failed to copy key');
toast.danger('Failed to copy key', { action: { label: 'Dismiss' } });
}
}, [copyState.error]);

View File

@@ -4,7 +4,7 @@ import { Button } from '@signozhq/ui/button';
import AuthZButton from 'lib/authz/components/AuthZButton/AuthZButton';
import { buildSADeletePermission } from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
import { DialogWrapper } from '@signozhq/ui/dialog';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
getGetServiceAccountQueryKey,

View File

@@ -70,7 +70,6 @@ function EditKeyForm({
) : (
<Input
id="edit-key-name"
className="edit-key-modal__input"
placeholder="Enter key name"
{...register('name')}
/>

View File

@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useQueryClient } from 'react-query';
import { DialogWrapper } from '@signozhq/ui/dialog';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
invalidateListServiceAccountKeys,

View File

@@ -7,7 +7,7 @@ import {
buildSADetachPermission,
} from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
import { DialogWrapper } from '@signozhq/ui/dialog';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
getListServiceAccountKeysQueryKey,

View File

@@ -9,7 +9,7 @@ import { useQueryClient } from 'react-query';
import { Key, LayoutGrid, Plus, Trash2, X } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { DrawerWrapper } from '@signozhq/ui/drawer';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { ToggleGroup } from '@signozhq/ui/toggle-group';
import { Skeleton } from 'antd';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';

View File

@@ -1,4 +1,4 @@
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { buildSAAttachPermission } from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
import {
setupAuthzAdmin,
@@ -12,9 +12,9 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import AddKeyModal from '../AddKeyModal';
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
toast: { success: jest.fn(), error: jest.fn() },
jest.mock('@signozhq/ui/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
toast: { success: jest.fn(), danger: jest.fn() },
}));
const mockCopyToClipboard = jest.fn();
@@ -74,7 +74,8 @@ describe('AddKeyModal', () => {
// The form only renders once the checks resolve, so waiting for it also
// guarantees the button is no longer in its authz-loading state.
const nameInput = await screen.findByTestId('add-key-name-input');
// The Input's testId lands on its frame; the native input is `-field`.
const nameInput = await screen.findByTestId('add-key-name-input-field');
const createBtn = await screen.findByTestId('add-key-submit-btn');
expect(createBtn).toHaveAttribute('aria-disabled', 'true');
@@ -94,7 +95,7 @@ describe('AddKeyModal', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderModal();
const nameInput = await screen.findByTestId('add-key-name-input');
const nameInput = await screen.findByTestId('add-key-name-input-field');
const submitBtn = await screen.findByTestId('add-key-submit-btn');
await user.type(nameInput, 'Deploy Key');
await waitFor(() =>
@@ -118,8 +119,9 @@ describe('AddKeyModal', () => {
);
renderModal();
const nameInput =
await screen.findByTestId<HTMLInputElement>('add-key-name-input');
const nameInput = await screen.findByTestId<HTMLInputElement>(
'add-key-name-input-field',
);
const submitBtn = await screen.findByTestId('add-key-submit-btn');
await user.type(nameInput, 'Deploy Key');
await waitFor(() =>
@@ -139,7 +141,7 @@ describe('AddKeyModal', () => {
renderModal();
const nameInput = await screen.findByTestId('add-key-name-input');
const nameInput = await screen.findByTestId('add-key-name-input-field');
const submitBtn = await screen.findByTestId('add-key-submit-btn');
await user.type(nameInput, 'Deploy Key');
await waitFor(() =>

View File

@@ -1,4 +1,4 @@
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
import { setupAuthzAdmin } from 'lib/authz/utils/authz-test-utils';
import { rest, server } from 'mocks-server/server';
@@ -17,9 +17,9 @@ jest.mock('lib/authz/components/AuthZTooltip/AuthZTooltip', () => ({
}): React.ReactElement => children,
}));
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
toast: { success: jest.fn(), error: jest.fn() },
jest.mock('@signozhq/ui/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
toast: { success: jest.fn(), danger: jest.fn() },
}));
const mockToast = jest.mocked(toast);

View File

@@ -1,4 +1,4 @@
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
import { setupAuthzAdmin } from 'lib/authz/utils/authz-test-utils';
import { rest, server } from 'mocks-server/server';
@@ -16,9 +16,9 @@ jest.mock('lib/authz/components/AuthZTooltip/AuthZTooltip', () => ({
}): React.ReactElement => children,
}));
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
toast: { success: jest.fn(), error: jest.fn() },
jest.mock('@signozhq/ui/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
toast: { success: jest.fn(), danger: jest.fn() },
}));
const mockToast = jest.mocked(toast);

View File

@@ -15,20 +15,18 @@
border: 1px solid var(--l2-border);
}
// Wrapper around the chromeless new-tag Input (the Input itself is
// variant="unstyled"); the placeholder colour comes via the --input-* tokens.
.input {
flex: 1;
min-width: 120px;
border: none;
background: transparent;
&::placeholder {
color: var(--l3-foreground);
}
--input-placeholder: var(--l3-foreground);
--input-placeholder-opacity: 1;
}
// Wrapper around the inline chip-edit Input; width lives on the Input prop.
.editInput {
width: 160px;
height: 24px;
--input-height: 24px;
}
.error {

View File

@@ -9,7 +9,8 @@ type User = ReturnType<typeof userEvent.setup>;
const startEditingFirstChip = async (user: User): Promise<HTMLElement> => {
await user.click(screen.getAllByTestId(`${TID}-chip`)[0]);
return screen.getByTestId(`${TID}-edit`);
// The Input's testId lands on its frame; the native input is `${testId}-field`.
return screen.getByTestId(`${TID}-edit-field`);
};
describe('TagKeyValueInput — inline chip edit', () => {
@@ -101,7 +102,7 @@ describe('TagKeyValueInput — backend-rule validation', () => {
const onTagsChange = jest.fn();
render(<TagKeyValueInput tags={[]} onTagsChange={onTagsChange} />);
const input = screen.getByTestId(TID);
const input = screen.getByTestId(`${TID}-field`);
await user.type(input, 'env:pro d{Enter}');
expect(screen.getByTestId(`${TID}-error`)).toHaveTextContent(

View File

@@ -115,21 +115,22 @@ function TagKeyValueInput({
<div className={styles.field}>
{tags.map((tag, index) =>
index === editIndex ? (
<Input
key={tag}
className={styles.editInput}
value={editValue}
autoFocus
testId={`${testId}-edit`}
onChange={(e: ChangeEvent<HTMLInputElement>): void => {
setEditValue(e.target.value);
if (error) {
setError('');
}
}}
onKeyDown={handleEditKeyDown}
onBlur={(): void => commitEdit(true)}
/>
<span key={tag} className={styles.editInput}>
<Input
width={160}
value={editValue}
autoFocus
testId={`${testId}-edit`}
onChange={(e: ChangeEvent<HTMLInputElement>): void => {
setEditValue(e.target.value);
if (error) {
setError('');
}
}}
onKeyDown={handleEditKeyDown}
onBlur={(): void => commitEdit(true)}
/>
</span>
) : (
<Pill.Closeable
key={tag}
@@ -142,14 +143,17 @@ function TagKeyValueInput({
</Pill.Closeable>
),
)}
<Input
className={styles.input}
value={inputValue}
placeholder={placeholder}
testId={testId}
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
<div className={styles.input}>
<Input
variant="unstyled"
width="100%"
value={inputValue}
placeholder={placeholder}
testId={testId}
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
</div>
</div>
{error && (
<Typography className={styles.error} data-testid={`${testId}-error`}>

View File

@@ -17,7 +17,7 @@ import {
horizontalListSortingStrategy,
SortableContext,
} from '@dnd-kit/sortable';
import { ComboboxSimple } from '@signozhq/ui/combobox';
import { Combobox } from '@signozhq/ui/combobox';
import { Pagination } from '@signozhq/ui/pagination';
import type { Row } from '@tanstack/react-table';
import {
@@ -668,14 +668,16 @@ function TanStackTableInner<TData, TItemKey = string>(
/>
{pagination.showPageSize !== false && (
<div className={viewStyles.paginationPageSize}>
<ComboboxSimple
<Combobox
testId="pagination-page-size"
aria-label="Rows per page"
placeholder="Page size"
searchInputProps={{ placeholder: 'Search page sizes' }}
value={limit?.toString()}
defaultValue="10"
onChange={(value): void => {
value ??= '10';
setLimit(+value);
pagination.onLimitChange?.(+value);
onChange={(value: string | undefined): void => {
const nextValue = value ?? '10';
setLimit(+nextValue);
pagination.onLimitChange?.(+nextValue);
if (page !== 1) {
setPage(1);
pagination.onPageChange?.(1);

View File

@@ -125,7 +125,7 @@
.paginationPageSize {
width: 80px;
--combobox-trigger-height: 2rem;
--combobox-trigger-min-block-size: 2rem;
}
.paginationTotalCount {

View File

@@ -2,7 +2,7 @@ import type { CSSProperties, ReactNode } from 'react';
import type { ColumnDef } from '@tanstack/react-table';
import { RowKeyData, TableColumnDef } from './types';
import { ComboboxSimpleItem } from '@signozhq/ui/combobox';
import type { ComboboxItemType } from '@signozhq/ui/combobox';
export const getColumnId = <TData>(column: TableColumnDef<TData>): string =>
column.id;
@@ -151,11 +151,12 @@ const DEFAULT_PAGE_SIZES = [10, 20, 30, 50, 100];
export function buildPageSizeItems(
calculatedSize?: number | null,
): ComboboxSimpleItem[] {
const items: ComboboxSimpleItem[] = [];
): ComboboxItemType[] {
const items: ComboboxItemType[] = [];
if (calculatedSize) {
items.push({
type: 'item',
value: calculatedSize.toString(),
label: `Auto (${calculatedSize})`,
displayValue: calculatedSize.toString(),
@@ -165,6 +166,7 @@ export function buildPageSizeItems(
for (const size of DEFAULT_PAGE_SIZES) {
if (size !== calculatedSize) {
items.push({
type: 'item',
value: size.toString(),
label: size.toString(),
displayValue: size.toString(),

View File

@@ -1,14 +1,9 @@
import React, { useEffect } from 'react';
import cx from 'classnames';
import { useLocation } from 'react-router-dom';
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandShortcut,
Command,
type CommandActionItemType,
type CommandItemType,
} from '@signozhq/ui/command';
import logEvent from 'api/common/logEvent';
import {
@@ -26,7 +21,10 @@ import { IS_DEV } from 'lib/env';
import history from 'lib/history';
import { ROLES as UserRole } from 'types/roles';
import { createShortcutActions } from '../../constants/shortcutActions';
import {
type CmdAction,
createShortcutActions,
} from '../../constants/shortcutActions';
import { useCmdK } from '../../providers/cmdKProvider';
import './cmdKPalette.scss';
@@ -58,17 +56,6 @@ const openAuthZDevModal = IS_DEV
}
: undefined;
type CmdAction = {
id: string;
name: string;
shortcut?: string[];
keywords?: string;
section?: string;
icon?: React.ReactNode;
roles?: UserRole[];
perform: () => void;
};
export function CmdKPalette({
userRole,
}: {
@@ -76,7 +63,7 @@ export function CmdKPalette({
}): JSX.Element | null {
const { open, setOpen } = useCmdK();
const { setAutoSwitch, setTheme, theme } = useThemeMode();
const { setAutoSwitch, setTheme } = useThemeMode();
const location = useLocation();
const isAIAssistantEnabled = useIsAIAssistantEnabled();
const startNewConversation = useAIAssistantStore(
@@ -146,8 +133,28 @@ export function CmdKPalette({
(a) => !a.roles || a.roles.includes(userRole),
);
const toCommandItem = (action: CmdAction): CommandActionItemType => ({
type: 'item',
value: action.id,
label: action.name,
prefix: action.icon,
searchMetadata: action.keywords,
shortcut:
action.shortcut && action.shortcut.length > 0
? action.shortcut.join(' • ')
: undefined,
onClick: (): void => {
// the palette closes itself after a pick, also when this throws
try {
action.perform();
} catch (e) {
console.error('Error invoking action', e);
}
},
});
// group permitted actions by section
const grouped: [string, CmdAction[]][] = ((): [string, CmdAction[]][] => {
const items: CommandItemType[] = ((): CommandItemType[] => {
const map = new Map<string, CmdAction[]>();
permitted.forEach((a) => {
@@ -161,61 +168,25 @@ export function CmdKPalette({
}
});
return Array.from(map.entries());
return Array.from(map.entries()).map(([section, sectionActions]) => ({
type: 'group',
value: section.toLowerCase().replace(/\s+/g, '-'),
label: section,
items: sectionActions.map(toCommandItem),
}));
})();
const handleInvoke = (action: CmdAction): void => {
try {
action.perform();
} catch (e) {
console.error('Error invoking action', e);
} finally {
setOpen(false);
}
};
return (
<>
<CommandDialog
<Command
label="Command palette"
open={open}
onOpenChange={setOpen}
position="top"
offset={110}
>
<CommandInput placeholder="Search…" className="cmdk-input-wrapper" />
<CommandList className="cmdk-list-scroll">
<CommandEmpty>No results</CommandEmpty>
{grouped.map(([section, items]) => (
<CommandGroup
key={section}
heading={section}
className="cmdk-section-heading"
>
{items.map((it) => (
<CommandItem
key={it.id}
onSelect={(): void => handleInvoke(it)}
value={it.name}
className={theme === 'light' ? 'cmdk-item-light' : 'cmdk-item'}
>
<span
className={cx(
'cmd-item-icon',
it.id === 'ai-assistant' && 'noz-icon',
)}
>
{it.icon}
</span>
{it.name}
{it.shortcut && it.shortcut.length > 0 && (
<CommandShortcut>{it.shortcut.join(' • ')}</CommandShortcut>
)}
</CommandItem>
))}
</CommandGroup>
))}
</CommandList>
</CommandDialog>
items={items}
searchInputProps={{ placeholder: 'Search…' }}
noContent="No results"
testId="cmdk-palette"
/>
{IS_DEV && AuthZDevModal && (
<React.Suspense fallback={null}>
<AuthZDevModal />

View File

@@ -27,7 +27,7 @@ export type CmdAction = {
shortcut?: string[];
keywords?: string;
section?: string;
icon?: React.ReactNode;
icon?: React.ReactElement;
roles?: ROLES[];
perform: () => void;
};

View File

@@ -255,11 +255,6 @@
width: 90%;
}
.contextPopoverSearchInput {
width: 100%;
box-sizing: border-box;
}
.contextPopoverEntities {
display: flex;
flex-direction: column;

View File

@@ -11,7 +11,7 @@ import { Badge } from '@signozhq/ui/badge';
import { Button } from '@signozhq/ui/button';
import { Input } from '@signozhq/ui/input';
import { Popover, PopoverContent, PopoverTrigger } from '@signozhq/ui/popover';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { Tooltip } from '@signozhq/ui/tooltip';
import type { UploadFile } from 'antd';
import getSessionStorage from 'api/browser/sessionstorage/get';
@@ -591,19 +591,23 @@ export default function ChatInput({
});
if (error === 'network') {
markVoiceUnavailable();
toast.error('Voice input unavailable in this browser', {
toast.danger('Voice input unavailable in this browser', {
action: { label: 'Dismiss' },
description:
'This browser cannot reach the speech recognition service. Try Google Chrome or Microsoft Edge.',
});
} else if (error === 'not-allowed') {
markVoiceUnavailable();
toast.error('Microphone access denied', {
toast.danger('Microphone access denied', {
action: { label: 'Dismiss' },
description:
'Grant microphone permission in your browser settings to use voice input.',
});
} else if (error === 'not-supported') {
markVoiceUnavailable();
toast.error('Voice input is not supported in this browser.');
toast.danger('Voice input is not supported in this browser.', {
action: { label: 'Dismiss' },
});
}
// `no-speech` is benign (just silence) — don't toast or hide.
},
@@ -1093,7 +1097,6 @@ export default function ChatInput({
<Input
type="text"
placeholder={`Search ${activeContextCategory.toLowerCase()}…`}
className={styles.contextPopoverSearchInput}
value={pickerSearchQuery}
onChange={(e): void => setPickerSearchQuery(e.target.value)}
prefix={<Search size={12} />}

View File

@@ -88,7 +88,20 @@
margin-left: 2px;
}
.input,
// The free-text Inputs inside .field / .multiSelectFieldset pick up the old
// `.input` look through the Input component's CSS tokens.
.field,
.multiSelectFieldset {
--input-background: var(--l2-background);
--input-border: var(--l2-border);
--input-border-radius: var(--radius-2);
--input-height: 28px;
--input-padding: 5px 8px;
--input-font-size: 13px;
--input-foreground: var(--l1-foreground);
--input-focus-ring: var(--accent-primary);
}
.select {
background: var(--l2-background);
border: 1px solid var(--l2-border);
@@ -108,14 +121,6 @@
cursor: pointer;
}
// Constrain the Radix-based SelectContent popover so it never grows wider
// than the trigger button. `--radix-select-trigger-width` is set by Radix
// at the popper layer when `position: 'popper'` (the default).
.selectContent {
width: var(--radix-select-trigger-width);
min-width: var(--radix-select-trigger-width);
}
.radioGroup,
.checkboxGroup {
display: flex;

View File

@@ -3,12 +3,8 @@ import cx from 'classnames';
import { Button } from '@signozhq/ui/button';
import { Checkbox } from '@signozhq/ui/checkbox';
import { Input } from '@signozhq/ui/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
} from '@signozhq/ui/select';
import { Select } from '@signozhq/ui/select';
import type { SelectItemType } from '@signozhq/ui/select';
import logEvent from 'api/common/logEvent';
import { ClarificationFieldTypeDTO } from 'api/ai-assistant/sigNozAIAssistantAPI.schemas';
import type {
@@ -247,10 +243,7 @@ function FieldInput({ field, value, onChange }: FieldInputProps): JSX.Element {
// "Other (type your own)" entry — a plain `options: null` would
// otherwise fall through to the bare text-input renderer).
if (type === ClarificationFieldTypeDTO.select && (options || allowCustom)) {
const handleSelectChange = (next: string | string[]): void => {
// `multiple` is off → callback receives a single string. The wider
// `string | string[]` typing comes from the shared Select root.
const picked = Array.isArray(next) ? (next[0] ?? '') : next;
const handleSelectChange = (picked: string): void => {
if (picked === CUSTOM_OPTION_SENTINEL) {
setIsCustom(true);
onChange(customValue);
@@ -260,6 +253,23 @@ function FieldInput({ field, value, onChange }: FieldInputProps): JSX.Element {
}
};
const selectItems: SelectItemType[] = [
...(options ?? []).map(
(opt): SelectItemType => ({ type: 'item', value: opt, label: opt }),
),
...(allowCustom
? [
{
type: 'item',
value: CUSTOM_OPTION_SENTINEL,
label: CUSTOM_OPTION_LABEL,
} as SelectItemType,
]
: []),
];
const selectedValue = isCustom ? CUSTOM_OPTION_SENTINEL : String(value ?? '');
return (
<div className={styles.field}>
<label className={styles.label} htmlFor={id}>
@@ -271,34 +281,16 @@ function FieldInput({ field, value, onChange }: FieldInputProps): JSX.Element {
)}
</label>
<Select
value={isCustom ? CUSTOM_OPTION_SENTINEL : String(value ?? '')}
id={id}
items={selectItems}
placeholder="Select…"
aria-required={required || undefined}
value={selectedValue || undefined}
onChange={handleSelectChange}
>
<SelectTrigger
id={id}
placeholder="Select…"
aria-required={required || undefined}
/>
{/* Pin the dropdown width to the trigger via Radix's
`--radix-select-trigger-width`; otherwise the popover
sizes to its widest item and looks misaligned. */}
<SelectContent className={styles.selectContent}>
{options?.map((opt) => (
<SelectItem key={opt} value={opt}>
{opt}
</SelectItem>
))}
{allowCustom && (
<SelectItem value={CUSTOM_OPTION_SENTINEL}>
{CUSTOM_OPTION_LABEL}
</SelectItem>
)}
</SelectContent>
</Select>
/>
{isCustom && (
<Input
type="text"
className={styles.input}
placeholder="Enter a custom value"
value={customValue}
onChange={(e): void => {
@@ -407,7 +399,6 @@ function FieldInput({ field, value, onChange }: FieldInputProps): JSX.Element {
{isCustom && (
<Input
type="text"
className={styles.input}
placeholder="Enter a custom value"
value={customValue}
onChange={(e): void => updateCustomValue(e.target.value)}
@@ -431,7 +422,6 @@ function FieldInput({ field, value, onChange }: FieldInputProps): JSX.Element {
<Input
id={id}
type={type === 'number' ? 'number' : 'text'}
className={styles.input}
value={String(value ?? '')}
aria-required={required || undefined}
onChange={(e): void => {

View File

@@ -62,16 +62,16 @@
opacity: 0.7;
}
// Wrapper around the chromeless rename Input (variant="unstyled"); the accent
// bottom border marks the edit state, and the --input-* tokens keep the field
// as compact as the title row it replaces.
.input {
width: 100%;
background: transparent;
border: none;
border-bottom: 1px solid var(--accent-primary);
outline: none;
color: var(--l1-foreground);
font-size: 12px;
font-family: inherit;
padding: 1px 0;
--input-height: 20px;
--input-padding: 1px 0;
--input-font-size: 12px;
--input-foreground: var(--l1-foreground);
}
.actions {

View File

@@ -5,7 +5,7 @@ import ROUTES from 'constants/routes';
import { getAbsoluteUrl } from 'utils/basePath';
import { Button } from '@signozhq/ui/button';
import { Input } from '@signozhq/ui/input';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { Dropdown, type DropdownItemType } from '@signozhq/ui/dropdown';
import {
@@ -176,16 +176,21 @@ export default function ConversationItem({
<div className={styles.body}>
{isEditing ? (
<Input
ref={inputRef}
className={styles.input}
value={editValue}
onChange={(e): void => setEditValue(e.target.value)}
onKeyDown={handleKeyDown}
onBlur={commitEdit}
onClick={(e): void => e.stopPropagation()}
maxLength={80}
/>
<div className={styles.input}>
<Input
ref={inputRef}
// The wrapper's accent bottom border is the edit/focus
// treatment, so the Input renders chromeless.
variant="unstyled"
noFocusRing
value={editValue}
onChange={(e): void => setEditValue(e.target.value)}
onKeyDown={handleKeyDown}
onBlur={commitEdit}
onClick={(e): void => e.stopPropagation()}
maxLength={80}
/>
</div>
) : (
<>
<span className={styles.title} title={displayTitle}>

View File

@@ -43,11 +43,9 @@
.searchBar {
padding: 0px 8px 12px;
flex-shrink: 0;
}
.search {
width: 100%;
font-size: 12px;
// The search Input fills its parent by default; its text size comes from
// the component's CSS token.
--input-font-size: 12px;
}
.list {

View File

@@ -193,7 +193,6 @@ export default function ConversationsList({
onChange={(e): void => setSearchQuery(e.target.value)}
placeholder="Search conversations…"
prefix={<Search size={12} />}
className={styles.search}
/>
</div>

View File

@@ -46,7 +46,7 @@
height: 80px;
}
.top-contributors-progress {
--progress-background: transparent;
--progress-track-background-color: transparent;
}
&__content {

View File

@@ -1,5 +1,4 @@
import { HTMLAttributes } from 'react';
import { Color } from '@signozhq/design-tokens';
import { Table, TableColumnsType as ColumnsType } from 'antd';
import { Progress } from '@signozhq/ui/progress';
import logEvent from 'api/common/logEvent';
@@ -49,12 +48,13 @@ function TopContributorsRows({
relatedTracesLink={record.relatedTracesLink}
relatedLogsLink={record.relatedLogsLink}
>
<Progress
percent={(count / totalCurrentTriggers) * 100}
showInfo={false}
strokeColor={Color.BG_ROBIN_500}
className="top-contributors-progress"
/>
<div className="top-contributors-progress">
<Progress
percent={(count / totalCurrentTriggers) * 100}
showInfo={false}
color="primary"
/>
</div>
</ConditionalAlertPopover>
),
},

View File

@@ -387,7 +387,7 @@ describe('Create Alert Channel', () => {
it('Should check if API key label, required, info(help_email_to), and textbox are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_email_to',
testId: 'email-to-textbox',
testId: 'email-to-textbox-field',
helpText: 'help_email_to',
required: true,
});
@@ -479,7 +479,7 @@ describe('Create Alert Channel', () => {
it('Should check if saving with a webhook url outside chat.googleapis.com displays error notification', async () => {
const user = userEvent.setup();
await fillField(user, 'channel-name-textbox', 'gchat-channel');
await fillField(user, 'channel-name-textbox-field', 'gchat-channel');
await fillField(user, 'webhook-url-textbox', 'https://example.com/webhook');
await user.click(screen.getByTestId('save-channel-button'));
@@ -506,7 +506,7 @@ describe('Create Alert Channel', () => {
const user = userEvent.setup();
await fillField(user, 'channel-name-textbox', 'gchat-channel');
await fillField(user, 'channel-name-textbox-field', 'gchat-channel');
await fillField(user, 'webhook-url-textbox', validWebhookUrl);
await user.click(screen.getByTestId('save-channel-button'));
@@ -538,7 +538,10 @@ describe('Create Alert Channel', () => {
user: ReturnType<typeof userEvent.setup>,
site: string,
): Promise<void> => {
await user.type(screen.getByTestId('channel-name-textbox'), 'jira-channel');
await user.type(
screen.getByTestId('channel-name-textbox-field'),
'jira-channel',
);
await user.type(screen.getByTestId('jira-site-textbox'), site);
await user.type(screen.getByTestId('jira-email-textbox'), 'me@acme.com');
await user.type(screen.getByTestId('jira-api-token-textbox'), 'tok123');
@@ -687,7 +690,7 @@ describe('Create Alert Channel', () => {
it('Should block save when the API key is missing', async () => {
const user = userEvent.setup();
await user.type(
screen.getByTestId('channel-name-textbox'),
screen.getByTestId('channel-name-textbox-field'),
'jsmops-channel',
);
@@ -715,7 +718,7 @@ describe('Create Alert Channel', () => {
const user = userEvent.setup();
await user.type(
screen.getByTestId('channel-name-textbox'),
screen.getByTestId('channel-name-textbox-field'),
'jsmops-channel',
);
await user.type(screen.getByTestId('jsmops-api-key-textbox'), 'key-abc');
@@ -773,7 +776,7 @@ describe('Create Alert Channel', () => {
it('Should block save when the URL or token is missing', async () => {
const user = userEvent.setup();
await user.type(
screen.getByTestId('channel-name-textbox'),
screen.getByTestId('channel-name-textbox-field'),
'incidentio-channel',
);
@@ -790,7 +793,7 @@ describe('Create Alert Channel', () => {
it('Should display an error when the URL is not an alert events URL', async () => {
const user = userEvent.setup();
await user.type(
screen.getByTestId('channel-name-textbox'),
screen.getByTestId('channel-name-textbox-field'),
'incidentio-channel',
);
await user.type(
@@ -823,7 +826,7 @@ describe('Create Alert Channel', () => {
const user = userEvent.setup();
await user.type(
screen.getByTestId('channel-name-textbox'),
screen.getByTestId('channel-name-textbox-field'),
'incidentio-channel',
);
await user.type(

View File

@@ -297,7 +297,7 @@ describe('Create Alert Channel (Normal User)', () => {
it('Should check if API key label, required, info(help_email_to), and textbox are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_email_to',
testId: 'email-to-textbox',
testId: 'email-to-textbox-field',
helpText: 'help_email_to',
required: true,
});

View File

@@ -43,7 +43,7 @@ describe('Should check if the edit alert channel is properly displayed', () => {
it('Should check if the name label and textbox are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_channel_name',
testId: 'channel-name-textbox',
testId: 'channel-name-textbox-field',
value: 'Dummy-Channel',
});
});

View File

@@ -158,7 +158,7 @@ function DomainDetails({
title={
<div className="domain-details-drawer-header">
<div className="domain-details-drawer-header-title">
<Divider type="vertical" />
<Divider orientation="vertical" />
{domainData?.domainName && (
<Typography.Text className="title">

View File

@@ -1,6 +1,5 @@
import { useMemo } from 'react';
import { useQueries } from 'react-query';
import { Color } from '@signozhq/design-tokens';
import { Skeleton, Tooltip } from 'antd';
import { Progress } from '@signozhq/ui/progress';
import { Typography } from '@signozhq/ui/typography';
@@ -136,26 +135,21 @@ function DomainMetrics({
) : (
<Tooltip title={formattedDomainMetricsData.errorRate}>
{formattedDomainMetricsData.errorRate !== '-' ? (
<Progress
percent={Number(
Number(formattedDomainMetricsData.errorRate).toFixed(2),
)}
strokeLinecap="butt"
showInfo
strokeColor={((): string => {
const errorRatePercent = Number(
<div className="progress-bar">
<Progress
percent={Number(
Number(formattedDomainMetricsData.errorRate).toFixed(2),
);
if (errorRatePercent >= 90) {
return Color.BG_SAKURA_500;
)}
showInfo
color={
Number(formattedDomainMetricsData.errorRate) >= 90
? 'highlight-danger'
: Number(formattedDomainMetricsData.errorRate) >= 60
? 'warning'
: 'success'
}
if (errorRatePercent >= 60) {
return Color.BG_AMBER_500;
}
return Color.BG_FOREST_500;
})()}
className="progress-bar"
/>
/>
</div>
) : (
'-'
)}

View File

@@ -1,6 +1,5 @@
import { useMemo } from 'react';
import { UseQueryResult } from 'react-query';
import { Color } from '@signozhq/design-tokens';
import { Skeleton, Tooltip } from 'antd';
import { Progress } from '@signozhq/ui/progress';
import { Typography } from '@signozhq/ui/typography';
@@ -80,24 +79,19 @@ function EndPointMetrics({
) : (
<Tooltip title={metricsData?.errorRate}>
{metricsData?.errorRate !== '-' ? (
<Progress
percent={Number(Number(metricsData?.errorRate ?? 0).toFixed(2))}
strokeLinecap="butt"
showInfo
strokeColor={((): string => {
const errorRatePercent = Number(
Number(metricsData?.errorRate ?? 0).toFixed(2),
);
if (errorRatePercent >= 90) {
return Color.BG_SAKURA_500;
<div className="progress-bar">
<Progress
percent={Number(Number(metricsData?.errorRate ?? 0).toFixed(2))}
showInfo
color={
Number(metricsData?.errorRate ?? 0) >= 90
? 'highlight-danger'
: Number(metricsData?.errorRate ?? 0) >= 60
? 'warning'
: 'success'
}
if (errorRatePercent >= 60) {
return Color.BG_AMBER_500;
}
return Color.BG_FOREST_500;
})()}
className="progress-bar"
/>
/>
</div>
) : (
'-'
)}

View File

@@ -1,5 +1,4 @@
import { ReactNode } from 'react';
import { Color } from '@signozhq/design-tokens';
import { TableColumnType as ColumnType, Tooltip } from 'antd';
import { Progress } from '@signozhq/ui/progress';
import { Badge } from '@signozhq/ui/badge';
@@ -47,6 +46,18 @@ import {
EndPointsResponseRow,
} from './types';
function metricProgressColor(
percent: number,
): 'success' | 'warning' | 'highlight-danger' {
if (percent >= 90) {
return 'highlight-danger';
}
if (percent >= 60) {
return 'warning';
}
return 'success';
}
export const isEmptyFilterValue = (value: unknown): boolean =>
value === '' || value === null || value === undefined || value === 'n/a';
/**
@@ -258,22 +269,13 @@ export const columnsConfig: ColumnType<APIDomainsRowData>[] = [
const errorRateValue =
errorRate === 'n/a' || errorRate === '-' ? 0 : errorRate;
return (
<Progress
percent={Number((errorRateValue as number).toFixed(2))}
strokeLinecap="butt"
showInfo
strokeColor={((): string => {
const errorRatePercent = Number((errorRateValue as number).toFixed(2));
if (errorRatePercent >= 90) {
return Color.BG_SAKURA_500;
}
if (errorRatePercent >= 60) {
return Color.BG_AMBER_500;
}
return Color.BG_FOREST_500;
})()}
className="progress-bar error-rate"
/>
<div className="progress-bar error-rate">
<Progress
percent={Number((errorRateValue as number).toFixed(2))}
showInfo
color={metricProgressColor(Number((errorRateValue as number).toFixed(2)))}
/>
</div>
);
},
},
@@ -1023,26 +1025,23 @@ export const getEndPointsColumnsConfig = (
align: 'right',
className: `column`,
render: (errorRate: number | string): React.ReactNode => (
<Progress
percent={Number(
(
(errorRate === 'n/a' || errorRate === '-' ? 0 : errorRate) as number
).toFixed(1),
)}
strokeLinecap="butt"
showInfo
strokeColor={((): string => {
const errorRatePercent = Number((errorRate as number).toFixed(1));
if (errorRatePercent >= 90) {
return Color.BG_SAKURA_500;
}
if (errorRatePercent >= 60) {
return Color.BG_AMBER_500;
}
return Color.BG_FOREST_500;
})()}
className="progress-bar error-rate"
/>
<div className="progress-bar error-rate">
<Progress
percent={Number(
(
(errorRate === 'n/a' || errorRate === '-' ? 0 : errorRate) as number
).toFixed(1),
)}
showInfo
color={metricProgressColor(
Number(
(
(errorRate === 'n/a' || errorRate === '-' ? 0 : errorRate) as number
).toFixed(1),
),
)}
/>
</div>
),
},
{
@@ -2514,24 +2513,15 @@ export const dependentServicesColumns: ColumnType<DependentServicesData>[] = [
align: 'center',
render: (errorPercentage: number | string): React.ReactNode =>
errorPercentage !== '-' ? (
<Progress
percent={Number((errorPercentage as number).toFixed(2))}
strokeLinecap="butt"
showInfo
strokeColor={((): string => {
const errorPercentagePercent = Number(
(errorPercentage as number).toFixed(2),
);
if (errorPercentagePercent >= 90) {
return Color.BG_SAKURA_500;
}
if (errorPercentagePercent >= 60) {
return Color.BG_AMBER_500;
}
return Color.BG_FOREST_500;
})()}
className="progress-bar error-rate"
/>
<div className="progress-bar error-rate">
<Progress
percent={Number((errorPercentage as number).toFixed(2))}
showInfo
color={metricProgressColor(
Number((errorPercentage as number).toFixed(2)),
)}
/>
</div>
) : (
'-'
),
@@ -3021,30 +3011,23 @@ export const getAllEndpointsWidgetData = (
</span>
),
F1: (errorRate: any): ReactNode => (
<Progress
percent={Number(
(
(errorRate === 'n/a' || errorRate === '-' ? 0 : errorRate) as number
).toFixed(2),
)}
strokeLinecap="butt"
showInfo
strokeColor={((): string => {
const errorRatePercent = Number(
<div className="progress-bar error-rate">
<Progress
percent={Number(
(
(errorRate === 'n/a' || errorRate === '-' ? 0 : errorRate) as number
).toFixed(2),
);
if (errorRatePercent >= 90) {
return Color.BG_SAKURA_500;
}
if (errorRatePercent >= 60) {
return Color.BG_AMBER_500;
}
return Color.BG_FOREST_500;
})()}
className="progress-bar error-rate"
/>
)}
showInfo
color={metricProgressColor(
Number(
(
(errorRate === 'n/a' || errorRate === '-' ? 0 : errorRate) as number
).toFixed(2),
),
)}
/>
</div>
),
};

View File

@@ -13,7 +13,7 @@ import { useMutation, useQueries } from 'react-query';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import * as Sentry from '@sentry/react';
import { Toaster } from '@signozhq/ui/sonner';
import { Toaster } from '@signozhq/ui/toast';
import { TooltipProvider } from '@signozhq/ui/tooltip';
import { Flex } from 'antd';
import getLocalStorageApi from 'api/browser/localstorage/get';

View File

@@ -4,7 +4,13 @@ import { Typography } from '@signozhq/ui/typography';
import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useMutation } from 'react-query';
import { CircleCheck, Landmark, MonitorDown } from '@signozhq/icons';
import {
CircleCheck,
Landmark,
MonitorDown,
SolidAlertCircle,
SolidInfoCircle,
} from '@signozhq/icons';
import {
Card,
Col,
@@ -480,12 +486,11 @@ export default function BillingContainer(): JSX.Element {
{!isLoading && !isFetchingBillingData && !showGracePeriodMessage
? headerText && (
<Callout
title={headerText}
type="info"
showIcon
style={{ marginTop: 12 }}
/>
<div style={{ marginTop: 12 }}>
<Callout color="info" size="sm" icon={<SolidInfoCircle />}>
{headerText}
</Callout>
</div>
)
: null}
@@ -498,21 +503,22 @@ export default function BillingContainer(): JSX.Element {
billingData &&
trialInfo?.gracePeriodEnd &&
showGracePeriodMessage ? (
<Callout
title={`Your data is safe with us until ${getFormattedDate(
trialInfo?.gracePeriodEnd || Date.now(),
)}. Please upgrade plan now to retain your data.`}
type="info"
showIcon
style={{ marginTop: 12 }}
/>
<div style={{ marginTop: 12 }}>
<Callout color="info" size="sm" icon={<SolidInfoCircle />}>
{`Your data is safe with us until ${getFormattedDate(
trialInfo?.gracePeriodEnd || Date.now(),
)}. Please upgrade plan now to retain your data.`}
</Callout>
</div>
) : null}
{isSubscriptionPastDue &&
(!isLoading && !isFetchingBillingData ? (
<Callout type="error" showIcon style={{ marginTop: 12 }}>
{subscriptionPastDueMessage()}
</Callout>
<div style={{ marginTop: 12 }}>
<Callout color="danger" size="sm" icon={<SolidAlertCircle />}>
{subscriptionPastDueMessage()}
</Callout>
</div>
) : (
<Skeleton.Input active style={{ height: 20, marginTop: 20 }} />
))}
@@ -553,9 +559,11 @@ export default function BillingContainer(): JSX.Element {
)}
</div>
{!isLoading && !isFetchingBillingData && (
<Callout type="info" size="small" className={styles.billingUpdateNote}>
Billing metrics are updated once every 24 hours.
</Callout>
<div className={styles.billingUpdateNote}>
<Callout color="info" size="sm" icon={<SolidInfoCircle />}>
Billing metrics are updated once every 24 hours.
</Callout>
</div>
)}
<div className={styles.billingDetails}>

View File

@@ -92,7 +92,7 @@ describe('CancelSubscriptionBanner', () => {
screen.getByText(/Cancelling your subscription would stop your data/i),
).toBeInTheDocument();
expect(screen.getByText(/Type/i)).toBeInTheDocument();
expect(screen.getByTestId('cancel-confirm-input')).toBeInTheDocument();
expect(screen.getByTestId('cancel-confirm-input-field')).toBeInTheDocument();
expect(screen.getByRole('button', { name: /go back/i })).toBeInTheDocument();
expect(
screen.getByTestId('cancel-subscription-confirm-btn'),
@@ -114,7 +114,7 @@ describe('CancelSubscriptionBanner', () => {
const confirmButton = screen.getByTestId('cancel-subscription-confirm-btn');
expect(confirmButton).toHaveAttribute('aria-disabled', 'true');
const input = screen.getByTestId('cancel-confirm-input');
const input = screen.getByTestId('cancel-confirm-input-field');
await user.type(input, 'canc');
expect(confirmButton).toHaveAttribute('aria-disabled', 'true');
@@ -134,7 +134,7 @@ describe('CancelSubscriptionBanner', () => {
});
await user.click(screen.getByTestId('cancel-subscription-btn'));
const input = screen.getByTestId('cancel-confirm-input');
const input = screen.getByTestId('cancel-confirm-input-field');
await user.type(input, 'cancel');
await user.click(screen.getByRole('button', { name: /go back/i }));
@@ -149,7 +149,7 @@ describe('CancelSubscriptionBanner', () => {
);
});
await user.click(screen.getByTestId('cancel-subscription-btn'));
expect(screen.getByTestId('cancel-confirm-input')).toHaveValue('');
expect(screen.getByTestId('cancel-confirm-input-field')).toHaveValue('');
});
it('fires mailto via DOM-attached anchor and shows fallback view after confirming', async () => {
@@ -165,7 +165,7 @@ describe('CancelSubscriptionBanner', () => {
);
});
await user.click(screen.getByTestId('cancel-subscription-btn'));
await user.type(screen.getByTestId('cancel-confirm-input'), 'cancel');
await user.type(screen.getByTestId('cancel-confirm-input-field'), 'cancel');
await user.click(screen.getByTestId('cancel-subscription-confirm-btn'));
const appendedAnchor = appendSpy.mock.calls
@@ -201,7 +201,7 @@ describe('CancelSubscriptionBanner', () => {
);
});
await user.click(screen.getByTestId('cancel-subscription-btn'));
await user.type(screen.getByTestId('cancel-confirm-input'), 'cancel');
await user.type(screen.getByTestId('cancel-confirm-input-field'), 'cancel');
await user.click(screen.getByTestId('cancel-subscription-confirm-btn'));
await user.click(screen.getByTestId('copy-email-template-btn'));
@@ -227,7 +227,7 @@ describe('CancelSubscriptionBanner', () => {
);
});
await user.click(screen.getByTestId('cancel-subscription-btn'));
await user.type(screen.getByTestId('cancel-confirm-input'), 'cancel');
await user.type(screen.getByTestId('cancel-confirm-input-field'), 'cancel');
await user.click(screen.getByTestId('cancel-subscription-confirm-btn'));
await user.click(screen.getByTestId('retry-mailto-btn'));
@@ -251,7 +251,7 @@ describe('CancelSubscriptionBanner', () => {
);
});
await user.click(screen.getByTestId('cancel-subscription-btn'));
await user.type(screen.getByTestId('cancel-confirm-input'), 'cancel');
await user.type(screen.getByTestId('cancel-confirm-input-field'), 'cancel');
await user.click(screen.getByTestId('cancel-subscription-confirm-btn'));
await user.click(screen.getByRole('button', { name: /close/i }));

View File

@@ -216,7 +216,7 @@ function CancelSubscriptionBanner(): JSX.Element {
placeholder="Enter the word cancel..."
value={confirmText}
onChange={(e): void => setConfirmText(e.target.value)}
data-testid="cancel-confirm-input"
testId="cancel-confirm-input"
/>
</div>
)}

View File

@@ -101,8 +101,8 @@ function ThresholdItem({
onChange={(e): void =>
updateThreshold(threshold.id, 'label', e.target.value)
}
style={{ width: 200 }}
data-testid="threshold-name-input"
width={200}
testId="threshold-name-input"
/>
<Typography.Text className="sentence-text">on value</Typography.Text>
<Typography.Text className="sentence-text highlighted-text">
@@ -114,9 +114,9 @@ function ThresholdItem({
onChange={(e): void =>
updateThreshold(threshold.id, 'thresholdValue', e.target.value)
}
style={{ width: 100 }}
width={100}
type="number"
data-testid="threshold-value-input"
testId="threshold-value-input"
/>
{yAxisUnitSelect}
{!notificationSettings.routingPolicies && (
@@ -165,9 +165,9 @@ function ThresholdItem({
onChange={(e): void =>
updateThreshold(threshold.id, 'recoveryThresholdValue', e.target.value)
}
style={{ width: 100 }}
width={100}
type="number"
data-testid="recovery-threshold-value-input"
testId="recovery-threshold-value-input"
/>
<Tooltip title="Remove recovery threshold">
<Button

View File

@@ -319,15 +319,12 @@ describe('ThresholdItem', () => {
it('renders with correct input widths', () => {
renderThresholdItem();
const labelInput = screen.getByPlaceholderText(
TEST_CONSTANTS.ENTER_THRESHOLD_NAME,
);
const valueInput = screen.getByPlaceholderText(
TEST_CONSTANTS.ENTER_THRESHOLD_VALUE,
);
// The width prop lands on the input frame as a CSS custom property
const labelInputFrame = screen.getByTestId('threshold-name-input');
const valueInputFrame = screen.getByTestId('threshold-value-input');
expect(labelInput).toHaveStyle('width: 200px');
expect(valueInput).toHaveStyle('width: 100px');
expect(labelInputFrame).toHaveStyle('--input-internal-width: 200px');
expect(valueInputFrame).toHaveStyle('--input-internal-width: 100px');
});
it('renders channels selector with correct width', () => {

View File

@@ -73,20 +73,21 @@ function CreateAlertHeader(): JSX.Element {
</div>
)}
<div className="alert-header__content">
<Input
type="text"
value={alertState.name}
onChange={(e): void => {
const newName = e.target.value;
setAlertState({ type: 'SET_ALERT_NAME', payload: newName });
if (isEditMode && alertRuleContext?.setAlertRuleName) {
alertRuleContext.setAlertRuleName(newName);
}
}}
className="alert-header__input title"
placeholder="Enter alert rule name"
data-testid="alert-name-input"
/>
<div className="alert-header__input title">
<Input
type="text"
value={alertState.name}
onChange={(e): void => {
const newName = e.target.value;
setAlertState({ type: 'SET_ALERT_NAME', payload: newName });
if (isEditMode && alertRuleContext?.setAlertRuleName) {
alertRuleContext.setAlertRuleName(newName);
}
}}
placeholder="Enter alert rule name"
testId="alert-name-input"
/>
</div>
<LabelsInput
labels={alertState.labels}
onLabelsChange={(labels: Labels): void =>

View File

@@ -38,8 +38,8 @@
}
&__input.title {
background-color: transparent;
color: var(--l1-foreground);
--input-background: transparent;
--input-foreground: var(--l1-foreground);
width: 100%;
min-width: 300px;
}

View File

@@ -23,7 +23,7 @@ function AdvancedOptions(): JSX.Element {
<Input
placeholder="Enter tolerance limit..."
type="number"
style={{ width: 100 }}
width={100}
onChange={(e): void =>
setAdvancedOptions({
type: 'SET_SEND_NOTIFICATION_IF_DATA_IS_MISSING',
@@ -56,7 +56,7 @@ function AdvancedOptions(): JSX.Element {
<div className="advanced-option-item-input-group">
<Input
placeholder="Enter minimum datapoints..."
style={{ width: 100 }}
width={100}
type="number"
onChange={(e): void =>
setAdvancedOptions({

View File

@@ -217,7 +217,7 @@ function EvaluationWindowDetails({
value={evaluationWindow.startingAt.number}
onChange={(e): void => handleNumberChange(e.target.value)}
placeholder="Enter value"
data-testid="evaluation-window-details-custom-rolling-window-duration-input"
testId="evaluation-window-details-custom-rolling-window-duration-input"
/>
</div>
<div className="select-group time-select-group">

View File

@@ -3,42 +3,20 @@
align-items: center;
gap: 0;
.time-input-field {
width: 40px;
height: 32px;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
font-family: 'Space Mono', monospace;
font-size: 13px;
font-weight: 600;
// The fields are @signozhq/ui Inputs, restyled through the input tokens.
--input-background: var(--l2-background);
--input-border: var(--l1-border);
--input-border-hover: var(--l1-border);
--input-foreground: var(--l1-foreground);
--input-placeholder: var(--l2-foreground);
--input-font-family: 'Space Mono', monospace;
--input-font-size: 13px;
--input-font-weight: 600;
--input-border-radius: 4px;
--input-padding: 0 4px;
input {
text-align: center;
border-radius: 4px;
&::placeholder {
color: var(--l2-foreground);
font-family: 'Space Mono', monospace;
}
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
outline: none;
}
&:disabled {
background-color: var(--l2-background);
color: var(--l2-foreground);
cursor: not-allowed;
&:hover {
border-color: var(--l1-border);
}
}
}
.time-input-separator {

View File

@@ -131,13 +131,13 @@ function TimeInput({
if (e.key === 'ArrowRight' || e.key === 'Tab') {
e.preventDefault();
const nextField = document.querySelector(
`[data-field="${getNextField(currentField)}"]`,
`[data-field="${getNextField(currentField)}"] input`,
) as HTMLInputElement;
nextField?.focus();
} else if (e.key === 'ArrowLeft') {
e.preventDefault();
const prevField = document.querySelector(
`[data-field="${getPrevField(currentField)}"]`,
`[data-field="${getPrevField(currentField)}"] input`,
) as HTMLInputElement;
prevField?.focus();
}
@@ -152,10 +152,11 @@ function TimeInput({
onBlur={handleHoursBlur}
onKeyDown={(e): void => handleKeyDown(e, 'hours')}
disabled={disabled}
disabledTooltip={undefined}
maxLength={2}
className="time-input-field"
width={40}
placeholder="00"
data-testid="time-input-hours"
testId="time-input-hours"
/>
<span className="time-input-separator">:</span>
<Input
@@ -165,10 +166,11 @@ function TimeInput({
onBlur={handleMinutesBlur}
onKeyDown={(e): void => handleKeyDown(e, 'minutes')}
disabled={disabled}
disabledTooltip={undefined}
maxLength={2}
className="time-input-field"
width={40}
placeholder="00"
data-testid="time-input-minutes"
testId="time-input-minutes"
/>
<span className="time-input-separator">:</span>
<Input
@@ -178,10 +180,11 @@ function TimeInput({
onBlur={handleSecondsBlur}
onKeyDown={(e): void => handleKeyDown(e, 'seconds')}
disabled={disabled}
disabledTooltip={undefined}
maxLength={2}
className="time-input-field"
width={40}
placeholder="00"
data-testid="time-input-seconds"
testId="time-input-seconds"
/>
</div>
);

View File

@@ -1,6 +1,6 @@
import { useCallback, useMemo } from 'react';
import { Button } from '@signozhq/ui/button';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import type { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
import { AxiosError } from 'axios';
@@ -98,8 +98,9 @@ function Footer(): JSX.Element {
{
onSuccess: (response) => {
if (response.data?.alertCount === 0) {
toast.error(
toast.danger(
'No alerts found during the evaluation. This happens when rule condition is unsatisfied. You may adjust the rule threshold and retry.',
{ action: { label: 'Dismiss' } },
);
return;
}

View File

@@ -25,6 +25,7 @@ function NotificationSettings(): JSX.Element {
value={notificationSettings.reNotification.value}
placeholder="Enter time interval..."
disabled={!notificationSettings.reNotification.enabled}
disabledTooltip={undefined}
type="number"
onChange={(e): void => {
setNotificationSettings({
@@ -37,7 +38,7 @@ function NotificationSettings(): JSX.Element {
},
});
}}
data-testid="repeat-notifications-time-input"
testId="repeat-notifications-time-input"
/>
<Select
value={notificationSettings.reNotification.unit || null}

View File

@@ -6,13 +6,13 @@ import {
ExternalLink,
FilePenLine,
Link2,
SolidAlertCircle,
SolidInfoCircle,
X,
} from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { Callout } from '@signozhq/ui/callout';
import { Dropdown, type DropdownItemType } from '@signozhq/ui/dropdown';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { Skeleton } from 'antd';
import {
RenderErrorResponseDTO,
@@ -31,7 +31,7 @@ function DomainUpdateToast({
toastId,
url,
}: {
toastId: string | number;
toastId: string;
url: string;
}): JSX.Element {
const displayUrl = url?.split('://')[1] ?? url;
@@ -143,10 +143,12 @@ export default function CustomDomainSettings(): JSX.Element {
setIsEditModalOpen(false);
setCustomDomainSubdomain(subdomain);
const newUrl = `https://${subdomain}.${dnsSuffix}`;
toast.custom(
(toastId) => <DomainUpdateToast toastId={toastId} url={newUrl} />,
{ duration: 5000, position: 'bottom-right' }, // this 5 sec is as per design
);
const toastId = `domain-update:${newUrl}`;
toast.info(<DomainUpdateToast toastId={toastId} url={newUrl} />, {
id: toastId,
timeout: 5000, // this 5 sec is as per design
position: 'bottom-right',
});
},
onError: (error: AxiosError<RenderErrorResponseDTO>) => {
setUpdateDomainError(error as AxiosError<RenderErrorResponseDTO>);
@@ -270,14 +272,11 @@ export default function CustomDomainSettings(): JSX.Element {
</div>
{isPollingEnabled && (
<Callout
type="info"
showIcon
className="custom-domain-callout"
size="small"
icon={<SolidAlertCircle size={13} color="primary" />}
title={`Updating your URL to ⎯ ${customDomainSubdomain}.${dnsSuffix}. This may take a few mins.`}
/>
<div className="custom-domain-callout">
<Callout color="info" size="sm" icon={<SolidInfoCircle />}>
{`Updating your URL to ⎯ ${customDomainSubdomain}.${dnsSuffix}. This may take a few mins.`}
</Callout>
</div>
)}
<CustomDomainEditModal

View File

@@ -12,11 +12,11 @@ jest.mock('components/LaunchChatSupport/LaunchChatSupport', () => ({
),
}));
const mockToastCustom = jest.fn();
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
const mockToastInfo = jest.fn();
jest.mock('@signozhq/ui/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
toast: {
custom: (...args: unknown[]): unknown => mockToastCustom(...args),
info: (...args: unknown[]): unknown => mockToastInfo(...args),
dismiss: jest.fn(),
},
}));
@@ -55,7 +55,7 @@ describe('CustomDomainSettings', () => {
afterEach(() => {
server.resetHandlers();
mockToastCustom.mockClear();
mockToastInfo.mockClear();
});
it('renders active host URL in the trigger button', async () => {
@@ -168,7 +168,7 @@ describe('CustomDomainSettings', () => {
expect(hrefs).toContain('https://custom-host.test.cloud');
});
it('calls toast.custom with new URL after successful domain update', async () => {
it('calls toast.info with new URL after successful domain update', async () => {
server.use(
rest.put(ZEUS_HOSTS_ENDPOINT, (_, res, ctx) =>
res(ctx.status(200), ctx.json({})),
@@ -184,17 +184,22 @@ describe('CustomDomainSettings', () => {
fireEvent.change(input, { target: { value: 'myteam' } });
fireEvent.click(screen.getByRole('button', { name: /apply changes/i }));
// Verify toast.custom was called
// Verify toast.info was called
await waitFor(() => {
expect(mockToastCustom).toHaveBeenCalledTimes(1);
expect(mockToastInfo).toHaveBeenCalledTimes(1);
});
// Render the toast element to verify its content
const toastRenderer = mockToastCustom.mock.calls[0][0] as (
id: string,
) => JSX.Element;
const { container } = render(toastRenderer('test-id'));
const toastElement = mockToastInfo.mock.calls[0][0] as JSX.Element;
const { container } = render(toastElement);
expect(container).toHaveTextContent(/myteam\.test\.cloud/i);
expect(mockToastInfo.mock.calls[0][1]).toStrictEqual(
expect.objectContaining({
id: 'domain-update:https://myteam.test.cloud',
timeout: 5000,
position: 'bottom-right',
}),
);
});
describe('Workspace Name rendering', () => {

View File

@@ -27,7 +27,7 @@
}
&__divider {
--divider-vertical-margin: 0;
--divider-vertical-margin-inline: 0;
}
}

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