mirror of
https://github.com/mattermost/mattermost.git
synced 2026-02-03 20:40:00 -05:00
Merge branch 'master' into mattermost-pages-channel
This commit is contained in:
commit
0426e32e52
11 changed files with 376 additions and 13 deletions
|
|
@ -1,4 +1,4 @@
|
|||
.PHONY: build package run stop run-client run-server run-node run-haserver stop-haserver stop-client stop-server restart restart-server restart-client restart-haserver start-docker update-docker clean-dist clean nuke check-style check-client-style check-server-style check-unit-tests test dist run-client-tests setup-run-client-tests cleanup-run-client-tests test-client build-linux build-osx build-windows package-prep package-linux package-osx package-windows internal-test-web-client vet run-server-for-web-client-tests diff-config prepackaged-plugins prepackaged-binaries test-server test-server-ee test-server-quick test-server-race test-mmctl-unit test-mmctl-e2e test-mmctl test-mmctl-coverage mmctl-build mmctl-docs new-migration migrations-extract test-public mocks-public
|
||||
.PHONY: build package run stop run-client run-server run-node run-haserver stop-haserver stop-client stop-server restart restart-server restart-client restart-haserver start-docker update-docker clean-dist clean nuke check-style check-client-style check-server-style check-unit-tests test dist run-client-tests setup-run-client-tests cleanup-run-client-tests test-client build-linux build-osx build-freebsd build-windows package-prep package-linux package-osx package-windows internal-test-web-client vet run-server-for-web-client-tests diff-config prepackaged-plugins prepackaged-binaries test-server test-server-ee test-server-quick test-server-race test-mmctl-unit test-mmctl-e2e test-mmctl test-mmctl-coverage mmctl-build mmctl-docs new-migration migrations-extract test-public mocks-public
|
||||
|
||||
ROOT := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||
|
||||
|
|
@ -142,6 +142,8 @@ DIST_PATH_LIN_AMD64=$(DIST_ROOT)/linux_amd64/mattermost
|
|||
DIST_PATH_LIN_ARM64=$(DIST_ROOT)/linux_arm64/mattermost
|
||||
DIST_PATH_OSX_AMD64=$(DIST_ROOT)/darwin_amd64/mattermost
|
||||
DIST_PATH_OSX_ARM64=$(DIST_ROOT)/darwin_arm64/mattermost
|
||||
DIST_PATH_FREEBSD_AMD64=$(DIST_ROOT)/freebsd_amd64/mattermost
|
||||
DIST_PATH_FREEBSD_ARM64=$(DIST_ROOT)/freebsd_arm64/mattermost
|
||||
DIST_PATH_WIN=$(DIST_ROOT)/windows/mattermost
|
||||
|
||||
# Packages lists
|
||||
|
|
|
|||
|
|
@ -49,6 +49,26 @@ else
|
|||
env GOOS=darwin GOARCH=arm64 $(GO) build -o $(GOBIN)/darwin_arm64 $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./...
|
||||
endif
|
||||
|
||||
build-freebsd: build-freebsd-amd64 build-freebsd-arm64
|
||||
|
||||
build-freebsd-amd64:
|
||||
@echo Build FreeBSD amd64
|
||||
ifeq ($(BUILDER_GOOS_GOARCH),"freebsd_amd64")
|
||||
env GOOS=freebsd GOARCH=amd64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./...
|
||||
else
|
||||
mkdir -p $(GOBIN)/freebsd_amd64
|
||||
env GOOS=freebsd GOARCH=amd64 $(GO) build -o $(GOBIN)/freebsd_amd64 $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./...
|
||||
endif
|
||||
|
||||
build-freebsd-arm64:
|
||||
@echo Build FreeBSD arm64
|
||||
ifeq ($(BUILDER_GOOS_GOARCH),"freebsd_arm64")
|
||||
env GOOS=freebsd GOARCH=arm64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./...
|
||||
else
|
||||
mkdir -p $(GOBIN)/freebsd_arm64
|
||||
env GOOS=freebsd GOARCH=arm64 $(GO) build -o $(GOBIN)/freebsd_arm64 $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./...
|
||||
endif
|
||||
|
||||
build-windows:
|
||||
@echo Build Windows amd64
|
||||
ifeq ($(BUILDER_GOOS_GOARCH),"windows_amd64")
|
||||
|
|
@ -103,6 +123,22 @@ else
|
|||
env GOOS=darwin GOARCH=arm64 $(GO) build -o $(GOBIN)/darwin_arm64 $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./cmd/...
|
||||
endif
|
||||
|
||||
build-cmd-freebsd:
|
||||
@echo Build CMD FreeBSD amd64
|
||||
ifeq ($(BUILDER_GOOS_GOARCH),"freebsd_amd64")
|
||||
env GOOS=freebsd GOARCH=amd64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./cmd/...
|
||||
else
|
||||
mkdir -p $(GOBIN)/freebsd_amd64
|
||||
env GOOS=freebsd GOARCH=amd64 $(GO) build -o $(GOBIN)/freebsd_amd64 $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./cmd/...
|
||||
endif
|
||||
@echo Build CMD FreeBSD arm64
|
||||
ifeq ($(BUILDER_GOOS_GOARCH),"freebsd_arm64")
|
||||
env GOOS=freebsd GOARCH=arm64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./cmd/...
|
||||
else
|
||||
mkdir -p $(GOBIN)/freebsd_arm64
|
||||
env GOOS=freebsd GOARCH=arm64 $(GO) build -o $(GOBIN)/freebsd_arm64 $(GOFLAGS) -trimpath -tags production -ldflags '$(LDFLAGS)' ./cmd/...
|
||||
endif
|
||||
|
||||
build-cmd-windows:
|
||||
@echo Build CMD Windows amd64
|
||||
ifeq ($(BUILDER_GOOS_GOARCH),"windows_amd64")
|
||||
|
|
@ -223,6 +259,22 @@ package-osx-arm64: package-prep
|
|||
|
||||
package-osx: package-osx-amd64 package-osx-arm64
|
||||
|
||||
package-freebsd-amd64: package-prep
|
||||
DIST_PATH_GENERIC=$(DIST_PATH_FREEBSD_AMD64) CURRENT_PACKAGE_ARCH=freebsd_amd64 MM_BIN_NAME=mattermost MMCTL_BIN_NAME=mmctl $(MAKE) package-general
|
||||
@# Package
|
||||
tar -C $(DIST_PATH_FREEBSD_AMD64)/.. -czf $(DIST_PATH)-$(BUILD_TYPE_NAME)-freebsd-amd64.tar.gz mattermost ../mattermost
|
||||
@# Cleanup
|
||||
rm -rf $(DIST_ROOT)/freebsd_amd64
|
||||
|
||||
package-freebsd-arm64: package-prep
|
||||
DIST_PATH_GENERIC=$(DIST_PATH_FREEBSD_ARM64) CURRENT_PACKAGE_ARCH=freebsd_arm64 MM_BIN_NAME=mattermost MMCTL_BIN_NAME=mmctl $(MAKE) package-general
|
||||
@# Package
|
||||
tar -C $(DIST_PATH_FREEBSD_ARM64)/.. -czf $(DIST_PATH)-$(BUILD_TYPE_NAME)-freebsd-arm64.tar.gz mattermost ../mattermost
|
||||
@# Cleanup
|
||||
rm -rf $(DIST_ROOT)/freebsd_arm64
|
||||
|
||||
package-freebsd: package-freebsd-amd64 package-freebsd-arm64
|
||||
|
||||
package-linux-amd64: package-prep
|
||||
DIST_PATH_GENERIC=$(DIST_PATH_LIN_AMD64) PLUGIN_ARCH=linux-amd64 $(MAKE) package-plugins
|
||||
DIST_PATH_GENERIC=$(DIST_PATH_LIN_AMD64) CURRENT_PACKAGE_ARCH=linux_amd64 MM_BIN_NAME=mattermost MMCTL_BIN_NAME=mmctl $(MAKE) package-general
|
||||
|
|
|
|||
|
|
@ -3177,11 +3177,23 @@ func (a *App) RewriteMessage(
|
|||
return nil, model.NewAppError("RewriteMessage", "app.post.rewrite.invalid_action", nil, fmt.Sprintf("invalid action: %s", action), 400)
|
||||
}
|
||||
|
||||
userLocale := ""
|
||||
if session := rctx.Session(); session != nil && session.UserId != "" {
|
||||
user, appErr := a.GetUser(session.UserId)
|
||||
if appErr == nil {
|
||||
userLocale = user.Locale
|
||||
} else {
|
||||
rctx.Logger().Warn("Failed to get user for rewrite locale", mlog.Err(appErr), mlog.String("user_id", session.UserId))
|
||||
}
|
||||
}
|
||||
|
||||
systemPrompt := buildRewriteSystemPrompt(userLocale)
|
||||
|
||||
// Prepare completion request in the format expected by the client
|
||||
client := a.GetBridgeClient(rctx.Session().UserId)
|
||||
completionRequest := agentclient.CompletionRequest{
|
||||
Posts: []agentclient.Post{
|
||||
{Role: "system", Message: model.RewriteSystemPrompt},
|
||||
{Role: "system", Message: systemPrompt},
|
||||
{Role: "user", Message: userPrompt},
|
||||
},
|
||||
}
|
||||
|
|
@ -3379,6 +3391,17 @@ func getRewritePromptForAction(action model.RewriteAction, message string, custo
|
|||
return actionPrompt
|
||||
}
|
||||
|
||||
func buildRewriteSystemPrompt(userLocale string) string {
|
||||
locale := strings.TrimSpace(userLocale)
|
||||
if locale == "" {
|
||||
return model.RewriteSystemPrompt
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`%s
|
||||
|
||||
User locale: %s. Preserve locale-specific spelling, grammar, and formatting. Keep locale identifiers (like %s) unchanged. Do not translate between locales.`, model.RewriteSystemPrompt, locale, locale)
|
||||
}
|
||||
|
||||
// RevealPost reveals a burn-on-read post for a specific user, creating a read receipt
|
||||
// if this is the first time the user is revealing it. Returns the revealed post content
|
||||
// with expiration metadata.
|
||||
|
|
|
|||
28
server/channels/app/post_rewrite_test.go
Normal file
28
server/channels/app/post_rewrite_test.go
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
func TestBuildRewriteSystemPrompt(t *testing.T) {
|
||||
basePrompt := model.RewriteSystemPrompt
|
||||
|
||||
t.Run("uses_user_locale", func(t *testing.T) {
|
||||
prompt := buildRewriteSystemPrompt("en_CA")
|
||||
require.True(t, strings.HasPrefix(prompt, basePrompt))
|
||||
require.Contains(t, prompt, "User locale: en_CA.")
|
||||
})
|
||||
|
||||
t.Run("returns_base_prompt_when_no_locale", func(t *testing.T) {
|
||||
prompt := buildRewriteSystemPrompt("")
|
||||
require.Equal(t, basePrompt, prompt)
|
||||
})
|
||||
}
|
||||
|
|
@ -1,5 +1,47 @@
|
|||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`components/ProductBranding should fallback to ProductChannelsIcon when string icon name is not found in glyphMap 1`] = `
|
||||
<ProductBrandingContainer
|
||||
tabIndex={-1}
|
||||
>
|
||||
<ProductChannelsIcon
|
||||
size={24}
|
||||
/>
|
||||
<h1
|
||||
className="sr-only"
|
||||
>
|
||||
InvalidProduct
|
||||
</h1>
|
||||
<ProductBrandingHeading>
|
||||
InvalidProduct
|
||||
</ProductBrandingHeading>
|
||||
</ProductBrandingContainer>
|
||||
`;
|
||||
|
||||
exports[`components/ProductBranding should render a React element icon when switcherIcon is a React node 1`] = `
|
||||
<ProductBrandingContainer
|
||||
tabIndex={-1}
|
||||
>
|
||||
<svg
|
||||
data-testid="custom-icon"
|
||||
>
|
||||
<circle
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
/>
|
||||
</svg>
|
||||
<h1
|
||||
className="sr-only"
|
||||
>
|
||||
CustomProduct
|
||||
</h1>
|
||||
<ProductBrandingHeading>
|
||||
CustomProduct
|
||||
</ProductBrandingHeading>
|
||||
</ProductBrandingContainer>
|
||||
`;
|
||||
|
||||
exports[`components/ProductBranding should show correct icon glyph when we are on Boards 1`] = `
|
||||
<ProductBrandingContainer
|
||||
tabIndex={-1}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import {TopLevelProducts} from 'utils/constants';
|
|||
import * as productUtils from 'utils/products';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import type {ProductComponent} from 'types/store/plugins';
|
||||
|
||||
import ProductBranding from './product_branding';
|
||||
|
||||
describe('components/ProductBranding', () => {
|
||||
|
|
@ -42,4 +44,45 @@ describe('components/ProductBranding', () => {
|
|||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render a React element icon when switcherIcon is a React node', () => {
|
||||
const currentProductSpy = jest.spyOn(productUtils, 'useCurrentProduct');
|
||||
const CustomIcon = (
|
||||
<svg data-testid='custom-icon'>
|
||||
<circle
|
||||
cx='12'
|
||||
cy='12'
|
||||
r='10'
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
const productWithReactIcon: ProductComponent = {
|
||||
...TestHelper.makeProduct('CustomProduct'),
|
||||
switcherIcon: CustomIcon,
|
||||
};
|
||||
currentProductSpy.mockReturnValue(productWithReactIcon);
|
||||
|
||||
const wrapper = shallow(
|
||||
<ProductBranding/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.find('[data-testid="custom-icon"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('should fallback to ProductChannelsIcon when string icon name is not found in glyphMap', () => {
|
||||
const currentProductSpy = jest.spyOn(productUtils, 'useCurrentProduct');
|
||||
const productWithInvalidIcon: ProductComponent = {
|
||||
...TestHelper.makeProduct('InvalidProduct'),
|
||||
switcherIcon: 'non-existent-icon-name' as ProductComponent['switcherIcon'],
|
||||
};
|
||||
currentProductSpy.mockReturnValue(productWithInvalidIcon);
|
||||
|
||||
const wrapper = shallow(
|
||||
<ProductBranding/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.find('ProductChannelsIcon').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import React from 'react';
|
|||
import styled from 'styled-components';
|
||||
|
||||
import glyphMap, {ProductChannelsIcon} from '@mattermost/compass-icons/components';
|
||||
import type {IconGlyphTypes} from '@mattermost/compass-icons/IconGlyphs';
|
||||
|
||||
import {useCurrentProduct} from 'utils/products';
|
||||
|
||||
|
|
@ -27,11 +28,29 @@ const ProductBrandingHeading = styled.span`
|
|||
const ProductBranding = (): JSX.Element => {
|
||||
const currentProduct = useCurrentProduct();
|
||||
|
||||
const Icon = currentProduct?.switcherIcon ? glyphMap[currentProduct.switcherIcon] : ProductChannelsIcon;
|
||||
// Handle both string icon names and React elements
|
||||
const renderIcon = () => {
|
||||
if (!currentProduct?.switcherIcon) {
|
||||
return <ProductChannelsIcon size={24}/>;
|
||||
}
|
||||
|
||||
if (typeof currentProduct.switcherIcon === 'string') {
|
||||
const Icon = glyphMap[currentProduct.switcherIcon as IconGlyphTypes];
|
||||
if (Icon) {
|
||||
return <Icon size={24}/>;
|
||||
}
|
||||
|
||||
// Fallback if icon name not found in glyphMap
|
||||
return <ProductChannelsIcon size={24}/>;
|
||||
}
|
||||
|
||||
// React element - render directly
|
||||
return <>{currentProduct.switcherIcon}</>;
|
||||
};
|
||||
|
||||
return (
|
||||
<ProductBrandingContainer tabIndex={-1}>
|
||||
<Icon size={24}/>
|
||||
{renderIcon()}
|
||||
<h1 className='sr-only'>
|
||||
{currentProduct ? currentProduct.switcherText : 'Channels'}
|
||||
</h1>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,147 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {renderWithContext, screen, userEvent} from 'tests/react_testing_utils';
|
||||
|
||||
import ProductMenuItem from './product_menu_item';
|
||||
import type {ProductMenuItemProps} from './product_menu_item';
|
||||
|
||||
describe('components/ProductMenuItem', () => {
|
||||
const defaultProps: ProductMenuItemProps = {
|
||||
destination: '/test-destination',
|
||||
icon: 'product-channels',
|
||||
text: 'Test Product',
|
||||
active: false,
|
||||
onClick: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('should render menu item with correct text', () => {
|
||||
renderWithContext(<ProductMenuItem {...defaultProps}/>);
|
||||
|
||||
expect(screen.getByRole('menuitem')).toBeInTheDocument();
|
||||
expect(screen.getByText('Test Product')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render with string icon from glyphMap', () => {
|
||||
renderWithContext(<ProductMenuItem {...defaultProps}/>);
|
||||
|
||||
// When icon is a string, the component looks up the glyph from glyphMap
|
||||
// The icon should be rendered with proper styling
|
||||
const menuItem = screen.getByRole('menuitem');
|
||||
expect(menuItem).toBeInTheDocument();
|
||||
|
||||
// The ProductChannelsIcon should be rendered (via glyphMap lookup)
|
||||
// We can verify the menu item contains an svg element
|
||||
expect(menuItem.querySelector('svg')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render with React element icon', () => {
|
||||
const CustomIcon = (
|
||||
<svg data-testid='custom-svg-icon'>
|
||||
<rect
|
||||
width='24'
|
||||
height='24'
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
const props: ProductMenuItemProps = {
|
||||
...defaultProps,
|
||||
icon: CustomIcon,
|
||||
};
|
||||
|
||||
renderWithContext(<ProductMenuItem {...props}/>);
|
||||
|
||||
expect(screen.getByTestId('custom-svg-icon')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should show check icon when active is true', () => {
|
||||
const props: ProductMenuItemProps = {
|
||||
...defaultProps,
|
||||
active: true,
|
||||
};
|
||||
|
||||
renderWithContext(<ProductMenuItem {...props}/>);
|
||||
|
||||
const menuItem = screen.getByRole('menuitem');
|
||||
|
||||
// When active, there should be two SVG elements: the product icon and the check icon
|
||||
const svgElements = menuItem.querySelectorAll('svg');
|
||||
expect(svgElements.length).toBe(2);
|
||||
});
|
||||
|
||||
test('should not show check icon when active is false', () => {
|
||||
renderWithContext(<ProductMenuItem {...defaultProps}/>);
|
||||
|
||||
const menuItem = screen.getByRole('menuitem');
|
||||
|
||||
// When not active, there should only be one SVG element: the product icon
|
||||
const svgElements = menuItem.querySelectorAll('svg');
|
||||
expect(svgElements.length).toBe(1);
|
||||
});
|
||||
|
||||
test('should call onClick when clicked', async () => {
|
||||
const onClick = jest.fn();
|
||||
const props: ProductMenuItemProps = {
|
||||
...defaultProps,
|
||||
onClick,
|
||||
};
|
||||
|
||||
renderWithContext(<ProductMenuItem {...props}/>);
|
||||
|
||||
await userEvent.click(screen.getByRole('menuitem'));
|
||||
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should render tour tip when provided', () => {
|
||||
const tourTipContent = 'Tour tip content';
|
||||
const TourTip = <div data-testid='tour-tip'>{tourTipContent}</div>;
|
||||
const props: ProductMenuItemProps = {
|
||||
...defaultProps,
|
||||
tourTip: TourTip,
|
||||
};
|
||||
|
||||
renderWithContext(<ProductMenuItem {...props}/>);
|
||||
|
||||
expect(screen.getByTestId('tour-tip')).toBeInTheDocument();
|
||||
expect(screen.getByText(tourTipContent)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should pass correct id to menu item', () => {
|
||||
const props: ProductMenuItemProps = {
|
||||
...defaultProps,
|
||||
id: 'test-menu-item-id',
|
||||
};
|
||||
|
||||
renderWithContext(<ProductMenuItem {...props}/>);
|
||||
|
||||
expect(screen.getByRole('menuitem')).toHaveAttribute('id', 'test-menu-item-id');
|
||||
});
|
||||
|
||||
test('should render with correct destination link', () => {
|
||||
renderWithContext(<ProductMenuItem {...defaultProps}/>);
|
||||
|
||||
const menuItem = screen.getByRole('menuitem');
|
||||
expect(menuItem).toHaveAttribute('href', '/test-destination');
|
||||
});
|
||||
|
||||
test('should render custom React component as icon', () => {
|
||||
const customIconText = 'Custom Icon';
|
||||
const CustomIconComponent = () => <span data-testid='custom-component-icon'>{customIconText}</span>;
|
||||
const props: ProductMenuItemProps = {
|
||||
...defaultProps,
|
||||
icon: <CustomIconComponent/>,
|
||||
};
|
||||
|
||||
renderWithContext(<ProductMenuItem {...props}/>);
|
||||
|
||||
expect(screen.getByTestId('custom-component-icon')).toBeInTheDocument();
|
||||
expect(screen.getByText(customIconText)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
@ -10,7 +10,7 @@ import type {IconGlyphTypes} from '@mattermost/compass-icons/IconGlyphs';
|
|||
|
||||
export interface ProductMenuItemProps {
|
||||
destination: string;
|
||||
icon: IconGlyphTypes;
|
||||
icon: IconGlyphTypes | React.ReactNode;
|
||||
text: React.ReactNode;
|
||||
active: boolean;
|
||||
onClick: () => void;
|
||||
|
|
@ -54,7 +54,7 @@ const MenuItemTextContainer = styled.div`
|
|||
`;
|
||||
|
||||
const ProductMenuItem = ({icon, destination, text, active, onClick, tourTip, id}: ProductMenuItemProps): JSX.Element => {
|
||||
const ProductIcon = glyphMap[icon];
|
||||
const ProductIcon = typeof icon === 'string' ? glyphMap[icon as IconGlyphTypes] : null;
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
|
|
@ -63,10 +63,14 @@ const ProductMenuItem = ({icon, destination, text, active, onClick, tourTip, id}
|
|||
id={id}
|
||||
role='menuitem'
|
||||
>
|
||||
<ProductIcon
|
||||
size={24}
|
||||
color={'var(--button-bg)'}
|
||||
/>
|
||||
{ProductIcon ? (
|
||||
<ProductIcon
|
||||
size={24}
|
||||
color={'var(--button-bg)'}
|
||||
/>
|
||||
) : (
|
||||
icon
|
||||
)}
|
||||
<MenuItemTextContainer>
|
||||
{text}
|
||||
</MenuItemTextContainer>
|
||||
|
|
|
|||
|
|
@ -1183,7 +1183,7 @@ export default class PluginRegistry {
|
|||
dispatchPluginComponentWithData('Product', {
|
||||
id,
|
||||
pluginId: this.id,
|
||||
switcherIcon,
|
||||
switcherIcon: resolveReactElement(switcherIcon),
|
||||
switcherText: resolveReactElement(switcherText),
|
||||
baseURL: '/' + standardizeRoute(baseURL),
|
||||
switcherLinkURL: '/' + standardizeRoute(switcherLinkURL),
|
||||
|
|
|
|||
|
|
@ -244,9 +244,12 @@ export type ProductSubComponentNames = 'mainComponent' | 'publicComponent' | 'he
|
|||
export type ProductComponent = PluginComponent & {
|
||||
|
||||
/**
|
||||
* A compass-icon glyph to display as the icon in the product switcher
|
||||
* A compass-icon glyph name or React element to display as the icon in the product switcher.
|
||||
* Accepts either:
|
||||
* - IconGlyphTypes: A string name from the Compass Icons library (e.g., 'product-channels')
|
||||
* - React.ReactNode: A custom React element to render as the icon
|
||||
*/
|
||||
switcherIcon: IconGlyphTypes;
|
||||
switcherIcon: IconGlyphTypes | React.ReactNode;
|
||||
|
||||
/**
|
||||
* A string or React element to display in the product switcher
|
||||
|
|
|
|||
Loading…
Reference in a new issue