Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0fef0be08 | ||
|
|
270069c3e3 | ||
|
|
2413af0ba9 | ||
|
|
2b5b8a8b13 | ||
|
|
7af82fb6a4 | ||
|
|
9beb1ca326 | ||
|
|
d54092b0e2 | ||
|
|
60701c2cf9 | ||
|
|
381f3ba7d9 | ||
|
|
64470baa1e | ||
|
|
f5e8c05267 | ||
|
|
48c540eb6f | ||
|
|
5583254f58 | ||
|
|
97832af778 | ||
|
|
7ce3e2b065 | ||
|
|
a49d8386aa | ||
|
|
e420ee8746 | ||
|
|
7adab8884b | ||
|
|
6101a4f113 | ||
|
|
da8fe7f6f7 | ||
|
|
cddc47b61f | ||
|
|
446c39d1c0 | ||
|
|
5eda10760c | ||
|
|
b9605a3003 | ||
|
|
56d6e13b5d | ||
|
|
725171bfd3 | ||
|
|
3b85747d3f | ||
|
|
a4cc7fb9c5 | ||
|
|
0f8152f536 | ||
|
|
a328c127f3 | ||
|
|
8cb803cedf | ||
|
|
5e83ad43de | ||
|
|
7ba5fe32f8 | ||
|
|
7601dbc218 | ||
|
|
8a968d1e31 | ||
|
|
0c4a194c7a | ||
|
|
94b8e34362 | ||
|
|
2fccd194f3 | ||
|
|
762fb6fd64 | ||
|
|
7956ecc7b2 | ||
|
|
e12b55a951 | ||
|
|
2df32f7003 | ||
|
|
46ee72160d | ||
|
|
f953aea3c5 | ||
|
|
c805a351ab |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-twenty-app",
|
||||
"version": "1.22.0-canary.6",
|
||||
"version": "1.22.0",
|
||||
"description": "Command-line interface to create Twenty application",
|
||||
"main": "dist/cli.cjs",
|
||||
"bin": "dist/cli.cjs",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "twenty-client-sdk",
|
||||
"version": "1.22.0-canary.6",
|
||||
"version": "1.22.0",
|
||||
"sideEffects": false,
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
|
||||
@@ -735,6 +735,7 @@ enum ViewType {
|
||||
KANBAN
|
||||
CALENDAR
|
||||
FIELDS_WIDGET
|
||||
TABLE_WIDGET
|
||||
}
|
||||
|
||||
enum ViewKey {
|
||||
@@ -1731,6 +1732,12 @@ type PublicWorkspaceData {
|
||||
workspaceUrls: WorkspaceUrls!
|
||||
}
|
||||
|
||||
type PublicWorkspaceDataSummary {
|
||||
id: UUID!
|
||||
logo: String
|
||||
displayName: String
|
||||
}
|
||||
|
||||
type NativeModelCapabilities {
|
||||
webSearch: Boolean
|
||||
twitterSearch: Boolean
|
||||
@@ -2644,6 +2651,7 @@ type CommandMenuItem {
|
||||
hotKeys: [String!]
|
||||
conditionalAvailabilityExpression: String
|
||||
availabilityObjectMetadataId: UUID
|
||||
pageLayoutId: UUID
|
||||
applicationId: UUID
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
@@ -3343,6 +3351,7 @@ type Query {
|
||||
currentUser: User!
|
||||
currentWorkspace: Workspace!
|
||||
getPublicWorkspaceDataByDomain(origin: String): PublicWorkspaceData!
|
||||
getPublicWorkspaceDataById(id: UUID!): PublicWorkspaceDataSummary!
|
||||
getSSOIdentityProviders: [FindAvailableSSOIDP!]!
|
||||
getConnectedImapSmtpCaldavAccount(id: UUID!): ConnectedImapSmtpCaldavAccount!
|
||||
getAutoCompleteAddress(address: String!, token: String!, country: String, isFieldCity: Boolean): [AutocompleteResult!]!
|
||||
@@ -4175,6 +4184,7 @@ input CreateCommandMenuItemInput {
|
||||
conditionalAvailabilityExpression: String
|
||||
availabilityObjectMetadataId: UUID
|
||||
payload: JSON
|
||||
pageLayoutId: UUID
|
||||
}
|
||||
|
||||
input UpdateCommandMenuItemInput {
|
||||
@@ -4188,6 +4198,7 @@ input UpdateCommandMenuItemInput {
|
||||
availabilityObjectMetadataId: UUID
|
||||
engineComponentKey: EngineComponentKey
|
||||
hotKeys: [String!]
|
||||
pageLayoutId: UUID
|
||||
}
|
||||
|
||||
input CreateFrontComponentInput {
|
||||
@@ -4598,6 +4609,7 @@ input UpdateApplicationRegistrationVariableInput {
|
||||
|
||||
input UpdateApplicationRegistrationVariablePayload {
|
||||
value: String
|
||||
resetValue: Boolean
|
||||
description: String
|
||||
}
|
||||
|
||||
|
||||
@@ -534,7 +534,7 @@ export interface View {
|
||||
__typename: 'View'
|
||||
}
|
||||
|
||||
export type ViewType = 'TABLE' | 'KANBAN' | 'CALENDAR' | 'FIELDS_WIDGET'
|
||||
export type ViewType = 'TABLE' | 'KANBAN' | 'CALENDAR' | 'FIELDS_WIDGET' | 'TABLE_WIDGET'
|
||||
|
||||
export type ViewKey = 'INDEX'
|
||||
|
||||
@@ -1430,6 +1430,13 @@ export interface PublicWorkspaceData {
|
||||
__typename: 'PublicWorkspaceData'
|
||||
}
|
||||
|
||||
export interface PublicWorkspaceDataSummary {
|
||||
id: Scalars['UUID']
|
||||
logo?: Scalars['String']
|
||||
displayName?: Scalars['String']
|
||||
__typename: 'PublicWorkspaceDataSummary'
|
||||
}
|
||||
|
||||
export interface NativeModelCapabilities {
|
||||
webSearch?: Scalars['Boolean']
|
||||
twitterSearch?: Scalars['Boolean']
|
||||
@@ -2361,6 +2368,7 @@ export interface CommandMenuItem {
|
||||
hotKeys?: Scalars['String'][]
|
||||
conditionalAvailabilityExpression?: Scalars['String']
|
||||
availabilityObjectMetadataId?: Scalars['UUID']
|
||||
pageLayoutId?: Scalars['UUID']
|
||||
applicationId?: Scalars['UUID']
|
||||
createdAt: Scalars['DateTime']
|
||||
updatedAt: Scalars['DateTime']
|
||||
@@ -2905,6 +2913,7 @@ export interface Query {
|
||||
currentUser: User
|
||||
currentWorkspace: Workspace
|
||||
getPublicWorkspaceDataByDomain: PublicWorkspaceData
|
||||
getPublicWorkspaceDataById: PublicWorkspaceDataSummary
|
||||
getSSOIdentityProviders: FindAvailableSSOIDP[]
|
||||
getConnectedImapSmtpCaldavAccount: ConnectedImapSmtpCaldavAccount
|
||||
getAutoCompleteAddress: AutocompleteResult[]
|
||||
@@ -4680,6 +4689,14 @@ export interface PublicWorkspaceDataGenqlSelection{
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface PublicWorkspaceDataSummaryGenqlSelection{
|
||||
id?: boolean | number
|
||||
logo?: boolean | number
|
||||
displayName?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface NativeModelCapabilitiesGenqlSelection{
|
||||
webSearch?: boolean | number
|
||||
twitterSearch?: boolean | number
|
||||
@@ -5690,6 +5707,7 @@ export interface CommandMenuItemGenqlSelection{
|
||||
hotKeys?: boolean | number
|
||||
conditionalAvailabilityExpression?: boolean | number
|
||||
availabilityObjectMetadataId?: boolean | number
|
||||
pageLayoutId?: boolean | number
|
||||
applicationId?: boolean | number
|
||||
createdAt?: boolean | number
|
||||
updatedAt?: boolean | number
|
||||
@@ -6274,6 +6292,7 @@ export interface QueryGenqlSelection{
|
||||
currentUser?: UserGenqlSelection
|
||||
currentWorkspace?: WorkspaceGenqlSelection
|
||||
getPublicWorkspaceDataByDomain?: (PublicWorkspaceDataGenqlSelection & { __args?: {origin?: (Scalars['String'] | null)} })
|
||||
getPublicWorkspaceDataById?: (PublicWorkspaceDataSummaryGenqlSelection & { __args: {id: Scalars['UUID']} })
|
||||
getSSOIdentityProviders?: FindAvailableSSOIDPGenqlSelection
|
||||
getConnectedImapSmtpCaldavAccount?: (ConnectedImapSmtpCaldavAccountGenqlSelection & { __args: {id: Scalars['UUID']} })
|
||||
getAutoCompleteAddress?: (AutocompleteResultGenqlSelection & { __args: {address: Scalars['String'], token: Scalars['String'], country?: (Scalars['String'] | null), isFieldCity?: (Scalars['Boolean'] | null)} })
|
||||
@@ -6725,9 +6744,9 @@ update: UpdateLogicFunctionFromSourceInputUpdates}
|
||||
|
||||
export interface UpdateLogicFunctionFromSourceInputUpdates {name?: (Scalars['String'] | null),description?: (Scalars['String'] | null),timeoutSeconds?: (Scalars['Float'] | null),sourceHandlerCode?: (Scalars['String'] | null),toolInputSchema?: (Scalars['JSON'] | null),handlerName?: (Scalars['String'] | null),sourceHandlerPath?: (Scalars['String'] | null),isTool?: (Scalars['Boolean'] | null),cronTriggerSettings?: (Scalars['JSON'] | null),databaseEventTriggerSettings?: (Scalars['JSON'] | null),httpRouteTriggerSettings?: (Scalars['JSON'] | null)}
|
||||
|
||||
export interface CreateCommandMenuItemInput {workflowVersionId?: (Scalars['UUID'] | null),frontComponentId?: (Scalars['UUID'] | null),engineComponentKey: EngineComponentKey,label: Scalars['String'],icon?: (Scalars['String'] | null),shortLabel?: (Scalars['String'] | null),position?: (Scalars['Float'] | null),isPinned?: (Scalars['Boolean'] | null),availabilityType?: (CommandMenuItemAvailabilityType | null),hotKeys?: (Scalars['String'][] | null),conditionalAvailabilityExpression?: (Scalars['String'] | null),availabilityObjectMetadataId?: (Scalars['UUID'] | null),payload?: (Scalars['JSON'] | null)}
|
||||
export interface CreateCommandMenuItemInput {workflowVersionId?: (Scalars['UUID'] | null),frontComponentId?: (Scalars['UUID'] | null),engineComponentKey: EngineComponentKey,label: Scalars['String'],icon?: (Scalars['String'] | null),shortLabel?: (Scalars['String'] | null),position?: (Scalars['Float'] | null),isPinned?: (Scalars['Boolean'] | null),availabilityType?: (CommandMenuItemAvailabilityType | null),hotKeys?: (Scalars['String'][] | null),conditionalAvailabilityExpression?: (Scalars['String'] | null),availabilityObjectMetadataId?: (Scalars['UUID'] | null),payload?: (Scalars['JSON'] | null),pageLayoutId?: (Scalars['UUID'] | null)}
|
||||
|
||||
export interface UpdateCommandMenuItemInput {id: Scalars['UUID'],label?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),shortLabel?: (Scalars['String'] | null),position?: (Scalars['Float'] | null),isPinned?: (Scalars['Boolean'] | null),availabilityType?: (CommandMenuItemAvailabilityType | null),availabilityObjectMetadataId?: (Scalars['UUID'] | null),engineComponentKey?: (EngineComponentKey | null),hotKeys?: (Scalars['String'][] | null)}
|
||||
export interface UpdateCommandMenuItemInput {id: Scalars['UUID'],label?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),shortLabel?: (Scalars['String'] | null),position?: (Scalars['Float'] | null),isPinned?: (Scalars['Boolean'] | null),availabilityType?: (CommandMenuItemAvailabilityType | null),availabilityObjectMetadataId?: (Scalars['UUID'] | null),engineComponentKey?: (EngineComponentKey | null),hotKeys?: (Scalars['String'][] | null),pageLayoutId?: (Scalars['UUID'] | null)}
|
||||
|
||||
export interface CreateFrontComponentInput {id?: (Scalars['UUID'] | null),name: Scalars['String'],description?: (Scalars['String'] | null),sourceComponentPath: Scalars['String'],builtComponentPath: Scalars['String'],componentName: Scalars['String'],builtComponentChecksum: Scalars['String']}
|
||||
|
||||
@@ -6859,7 +6878,7 @@ export interface CreateApplicationRegistrationVariableInput {applicationRegistra
|
||||
|
||||
export interface UpdateApplicationRegistrationVariableInput {id: Scalars['String'],update: UpdateApplicationRegistrationVariablePayload}
|
||||
|
||||
export interface UpdateApplicationRegistrationVariablePayload {value?: (Scalars['String'] | null),description?: (Scalars['String'] | null)}
|
||||
export interface UpdateApplicationRegistrationVariablePayload {value?: (Scalars['String'] | null),resetValue?: (Scalars['Boolean'] | null),description?: (Scalars['String'] | null)}
|
||||
|
||||
export interface UpdateWorkspaceMemberSettingsInput {workspaceMemberId: Scalars['UUID'],update: Scalars['JSON']}
|
||||
|
||||
@@ -7850,6 +7869,14 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
|
||||
|
||||
|
||||
|
||||
const PublicWorkspaceDataSummary_possibleTypes: string[] = ['PublicWorkspaceDataSummary']
|
||||
export const isPublicWorkspaceDataSummary = (obj?: { __typename?: any } | null): obj is PublicWorkspaceDataSummary => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isPublicWorkspaceDataSummary"')
|
||||
return PublicWorkspaceDataSummary_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const NativeModelCapabilities_possibleTypes: string[] = ['NativeModelCapabilities']
|
||||
export const isNativeModelCapabilities = (obj?: { __typename?: any } | null): obj is NativeModelCapabilities => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isNativeModelCapabilities"')
|
||||
@@ -9267,7 +9294,8 @@ export const enumViewType = {
|
||||
TABLE: 'TABLE' as const,
|
||||
KANBAN: 'KANBAN' as const,
|
||||
CALENDAR: 'CALENDAR' as const,
|
||||
FIELDS_WIDGET: 'FIELDS_WIDGET' as const
|
||||
FIELDS_WIDGET: 'FIELDS_WIDGET' as const,
|
||||
TABLE_WIDGET: 'TABLE_WIDGET' as const
|
||||
}
|
||||
|
||||
export const enumViewKey = {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
node_modules
|
||||
storybook-static
|
||||
src/__stories__/example-sources-built
|
||||
src/__stories__/example-sources-built/*
|
||||
!src/__stories__/example-sources-built/bundle-sizes.json.d.ts
|
||||
src/__stories__/example-sources-built-preact
|
||||
|
||||
+4
-1
@@ -60,7 +60,7 @@ const twentySharedAliases = Object.fromEntries(
|
||||
const storyAlias = {
|
||||
react: path.join(rootNodeModules, 'react'),
|
||||
'react-dom': path.join(rootNodeModules, 'react-dom'),
|
||||
'@/sdk': sdkIndividualIndex,
|
||||
'twenty-sdk': sdkIndividualIndex,
|
||||
'twenty-sdk/ui': twentyUiIndividualIndex,
|
||||
...twentySharedAliases,
|
||||
};
|
||||
@@ -77,6 +77,9 @@ const STORY_COMPONENTS = [
|
||||
'mui-example.front-component',
|
||||
'twenty-ui-example.front-component',
|
||||
'sdk-context-example.front-component',
|
||||
'form-events.front-component',
|
||||
'keyboard-events.front-component',
|
||||
'host-api-calls.front-component',
|
||||
];
|
||||
|
||||
const resolveEntryPoints = (): Record<string, string> => {
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ const getHtmlElementSchemas = (): ComponentSchema[] => {
|
||||
events: element.events
|
||||
? [...COMMON_HTML_EVENTS, ...element.events]
|
||||
: COMMON_HTML_EVENTS,
|
||||
htmlTag: extractHtmlTag(element.tag),
|
||||
htmlTag: element.htmlTag ?? extractHtmlTag(element.tag),
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ export const HtmlElementConfigZ = z.object({
|
||||
.regex(/^Html[A-Z]/, 'Name must be PascalCase starting with Html'),
|
||||
properties: z.record(z.string(), PropertySchemaZ),
|
||||
events: z.array(z.string()).optional(),
|
||||
htmlTag: z.string().optional(),
|
||||
});
|
||||
|
||||
export const HtmlElementConfigArrayZ = z.array(HtmlElementConfigZ);
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
||||
import { expect, fn, userEvent, waitFor, within } from 'storybook/test';
|
||||
|
||||
import { FrontComponentRenderer } from '../host/components/FrontComponentRenderer';
|
||||
|
||||
import { getBuiltStoryComponentPathForRender } from './utils/getBuiltStoryComponentPathForRender';
|
||||
|
||||
const errorHandler = fn();
|
||||
|
||||
const createHostApiMocks = () => ({
|
||||
navigate: fn().mockResolvedValue(undefined),
|
||||
enqueueSnackbar: fn().mockResolvedValue(undefined),
|
||||
openSidePanelPage: fn().mockResolvedValue(undefined),
|
||||
closeSidePanel: fn().mockResolvedValue(undefined),
|
||||
unmountFrontComponent: fn().mockResolvedValue(undefined),
|
||||
updateProgress: fn().mockResolvedValue(undefined),
|
||||
requestAccessTokenRefresh: fn().mockResolvedValue('refreshed-token'),
|
||||
openCommandConfirmationModal: fn().mockResolvedValue(undefined),
|
||||
});
|
||||
|
||||
const meta: Meta<typeof FrontComponentRenderer> = {
|
||||
title: 'FrontComponent/EventForwarding',
|
||||
component: FrontComponentRenderer,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
args: {
|
||||
onError: errorHandler,
|
||||
applicationAccessToken: 'fake-token',
|
||||
executionContext: {
|
||||
frontComponentId: 'storybook-test',
|
||||
userId: null,
|
||||
recordId: null,
|
||||
},
|
||||
colorScheme: 'light',
|
||||
frontComponentHostCommunicationApi: createHostApiMocks(),
|
||||
},
|
||||
beforeEach: () => {
|
||||
errorHandler.mockClear();
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof FrontComponentRenderer>;
|
||||
|
||||
const MOUNT_TIMEOUT = 30000;
|
||||
const INTERACTION_TIMEOUT = 5000;
|
||||
const HOST_API_TIMEOUT = 10000;
|
||||
|
||||
const createComponentStory = (
|
||||
name: string,
|
||||
options?: { play?: Story['play'] },
|
||||
): Story => ({
|
||||
args: {
|
||||
componentUrl: getBuiltStoryComponentPathForRender(
|
||||
`${name}.front-component`,
|
||||
),
|
||||
},
|
||||
...(options?.play ? { play: options.play } : {}),
|
||||
});
|
||||
|
||||
const createHostApiStory = (play: Story['play']): Story => ({
|
||||
...createComponentStory('host-api-calls'),
|
||||
args: {
|
||||
...createComponentStory('host-api-calls').args,
|
||||
frontComponentHostCommunicationApi: createHostApiMocks(),
|
||||
},
|
||||
play,
|
||||
});
|
||||
|
||||
export const FormTextInput: Story = createComponentStory('form-events', {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findByTestId(
|
||||
'form-events-component',
|
||||
{},
|
||||
{ timeout: MOUNT_TIMEOUT },
|
||||
);
|
||||
|
||||
const textInput = await canvas.findByTestId('text-input');
|
||||
await userEvent.type(textInput, 'hello');
|
||||
|
||||
expect(
|
||||
await canvas.findByText('hello', {}, { timeout: INTERACTION_TIMEOUT }),
|
||||
).toBeVisible();
|
||||
},
|
||||
});
|
||||
|
||||
export const FormCheckbox: Story = createComponentStory('form-events', {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findByTestId(
|
||||
'form-events-component',
|
||||
{},
|
||||
{ timeout: MOUNT_TIMEOUT },
|
||||
);
|
||||
|
||||
const checkbox = await canvas.findByTestId('checkbox-input');
|
||||
await userEvent.click(checkbox);
|
||||
|
||||
expect(
|
||||
await canvas.findByText('true', {}, { timeout: INTERACTION_TIMEOUT }),
|
||||
).toBeVisible();
|
||||
},
|
||||
});
|
||||
|
||||
export const FormFocusAndBlur: Story = createComponentStory('form-events', {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findByTestId(
|
||||
'form-events-component',
|
||||
{},
|
||||
{ timeout: MOUNT_TIMEOUT },
|
||||
);
|
||||
|
||||
const textInput = await canvas.findByTestId('text-input');
|
||||
await userEvent.click(textInput);
|
||||
|
||||
expect(
|
||||
await canvas.findByText('focused', {}, { timeout: INTERACTION_TIMEOUT }),
|
||||
).toBeVisible();
|
||||
|
||||
await userEvent.click(await canvas.findByTestId('form-events-component'));
|
||||
|
||||
expect(
|
||||
await canvas.findByText('blurred', {}, { timeout: INTERACTION_TIMEOUT }),
|
||||
).toBeVisible();
|
||||
},
|
||||
});
|
||||
|
||||
export const FormSubmission: Story = createComponentStory('form-events', {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findByTestId(
|
||||
'form-events-component',
|
||||
{},
|
||||
{ timeout: MOUNT_TIMEOUT },
|
||||
);
|
||||
|
||||
const textInput = await canvas.findByTestId('text-input');
|
||||
await userEvent.type(textInput, 'hello');
|
||||
|
||||
const checkbox = await canvas.findByTestId('checkbox-input');
|
||||
await userEvent.click(checkbox);
|
||||
|
||||
const submitButton = await canvas.findByTestId('submit-button');
|
||||
await userEvent.click(submitButton);
|
||||
|
||||
expect(
|
||||
await canvas.findByText(
|
||||
'{"text":"hello","checkbox":true}',
|
||||
{},
|
||||
{ timeout: INTERACTION_TIMEOUT },
|
||||
),
|
||||
).toBeVisible();
|
||||
},
|
||||
});
|
||||
|
||||
export const KeyboardBasicInput: Story = createComponentStory(
|
||||
'keyboard-events',
|
||||
{
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findByTestId(
|
||||
'keyboard-events-component',
|
||||
{},
|
||||
{ timeout: MOUNT_TIMEOUT },
|
||||
);
|
||||
|
||||
const input = await canvas.findByTestId('keyboard-input');
|
||||
await userEvent.click(input);
|
||||
|
||||
await userEvent.keyboard('a');
|
||||
|
||||
expect(
|
||||
await canvas.findByText('a', {}, { timeout: INTERACTION_TIMEOUT }),
|
||||
).toBeVisible();
|
||||
|
||||
expect(
|
||||
await canvas.findByText('KeyA', {}, { timeout: INTERACTION_TIMEOUT }),
|
||||
).toBeVisible();
|
||||
|
||||
expect(
|
||||
await canvas.findByText(
|
||||
/^[1-9]\d*$/,
|
||||
{},
|
||||
{ timeout: INTERACTION_TIMEOUT },
|
||||
),
|
||||
).toBeVisible();
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export const KeyboardModifiers: Story = createComponentStory(
|
||||
'keyboard-events',
|
||||
{
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findByTestId(
|
||||
'keyboard-events-component',
|
||||
{},
|
||||
{ timeout: MOUNT_TIMEOUT },
|
||||
);
|
||||
|
||||
const input = await canvas.findByTestId('keyboard-input');
|
||||
await userEvent.click(input);
|
||||
|
||||
await userEvent.keyboard('{Shift>}b{/Shift}');
|
||||
|
||||
expect(
|
||||
await canvas.findByText('shift', {}, { timeout: INTERACTION_TIMEOUT }),
|
||||
).toBeVisible();
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export const HostApiNavigate: Story = createHostApiStory(
|
||||
async ({ canvasElement, args }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const api = args.frontComponentHostCommunicationApi!;
|
||||
|
||||
await canvas.findByTestId(
|
||||
'host-api-calls-component',
|
||||
{},
|
||||
{ timeout: MOUNT_TIMEOUT },
|
||||
);
|
||||
|
||||
const navigateBtn = await canvas.findByTestId('btn-navigate');
|
||||
await userEvent.click(navigateBtn);
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(api.navigate).toHaveBeenCalled();
|
||||
},
|
||||
{ timeout: HOST_API_TIMEOUT },
|
||||
);
|
||||
|
||||
expect(
|
||||
await canvas.findByText(
|
||||
'navigate:success',
|
||||
{},
|
||||
{ timeout: INTERACTION_TIMEOUT },
|
||||
),
|
||||
).toBeVisible();
|
||||
},
|
||||
);
|
||||
|
||||
export const HostApiSnackbar: Story = createHostApiStory(
|
||||
async ({ canvasElement, args }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const api = args.frontComponentHostCommunicationApi!;
|
||||
|
||||
await canvas.findByTestId(
|
||||
'host-api-calls-component',
|
||||
{},
|
||||
{ timeout: MOUNT_TIMEOUT },
|
||||
);
|
||||
|
||||
const snackbarBtn = await canvas.findByTestId('btn-snackbar');
|
||||
await userEvent.click(snackbarBtn);
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(api.enqueueSnackbar).toHaveBeenCalledWith({
|
||||
message: 'Test notification',
|
||||
variant: 'success',
|
||||
});
|
||||
},
|
||||
{ timeout: HOST_API_TIMEOUT },
|
||||
);
|
||||
|
||||
expect(
|
||||
await canvas.findByText(
|
||||
'snackbar:success',
|
||||
{},
|
||||
{ timeout: INTERACTION_TIMEOUT },
|
||||
),
|
||||
).toBeVisible();
|
||||
},
|
||||
);
|
||||
|
||||
export const HostApiProgress: Story = createHostApiStory(
|
||||
async ({ canvasElement, args }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const api = args.frontComponentHostCommunicationApi!;
|
||||
|
||||
await canvas.findByTestId(
|
||||
'host-api-calls-component',
|
||||
{},
|
||||
{ timeout: MOUNT_TIMEOUT },
|
||||
);
|
||||
|
||||
const progressBtn = await canvas.findByTestId('btn-progress');
|
||||
await userEvent.click(progressBtn);
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(api.updateProgress).toHaveBeenCalledWith(50);
|
||||
},
|
||||
{ timeout: HOST_API_TIMEOUT },
|
||||
);
|
||||
|
||||
expect(
|
||||
await canvas.findByText(
|
||||
'progress:success',
|
||||
{},
|
||||
{ timeout: INTERACTION_TIMEOUT },
|
||||
),
|
||||
).toBeVisible();
|
||||
},
|
||||
);
|
||||
|
||||
export const HostApiClosePanel: Story = createHostApiStory(
|
||||
async ({ canvasElement, args }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const api = args.frontComponentHostCommunicationApi!;
|
||||
|
||||
await canvas.findByTestId(
|
||||
'host-api-calls-component',
|
||||
{},
|
||||
{ timeout: MOUNT_TIMEOUT },
|
||||
);
|
||||
|
||||
const closePanelBtn = await canvas.findByTestId('btn-close-panel');
|
||||
await userEvent.click(closePanelBtn);
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(api.closeSidePanel).toHaveBeenCalled();
|
||||
},
|
||||
{ timeout: HOST_API_TIMEOUT },
|
||||
);
|
||||
|
||||
expect(
|
||||
await canvas.findByText(
|
||||
'closePanel:success',
|
||||
{},
|
||||
{ timeout: INTERACTION_TIMEOUT },
|
||||
),
|
||||
).toBeVisible();
|
||||
},
|
||||
);
|
||||
+9
-8
@@ -16,7 +16,11 @@ const meta: Meta<typeof FrontComponentRenderer> = {
|
||||
args: {
|
||||
onError: errorHandler,
|
||||
applicationAccessToken: 'fake-token',
|
||||
executionContext: { frontComponentId: 'storybook-test', userId: null },
|
||||
executionContext: {
|
||||
frontComponentId: 'storybook-test',
|
||||
userId: null,
|
||||
recordId: null,
|
||||
},
|
||||
},
|
||||
beforeEach: () => {
|
||||
errorHandler.mockClear();
|
||||
@@ -92,13 +96,9 @@ export const Lifecycle: Story = {
|
||||
|
||||
expect(await canvas.findByText('Mounted')).toBeVisible();
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
const tickElement = canvas.getByTestId('tick-count');
|
||||
expect(tickElement.textContent).toMatch(/Ticks: [1-9]\d*/);
|
||||
},
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
expect(
|
||||
await canvas.findByText(/Ticks: [1-9]\d*/, {}, { timeout: 10000 }),
|
||||
).toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -121,6 +121,7 @@ export const SdkContext: Story = {
|
||||
executionContext: {
|
||||
frontComponentId: 'sdk-context-test',
|
||||
userId: 'test-user-abc-123',
|
||||
recordId: null,
|
||||
},
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
|
||||
@@ -16,7 +16,11 @@ const meta: Meta<typeof FrontComponentRenderer> = {
|
||||
args: {
|
||||
onError: errorHandler,
|
||||
applicationAccessToken: 'fake-token',
|
||||
executionContext: { frontComponentId: 'storybook-test', userId: null },
|
||||
executionContext: {
|
||||
frontComponentId: 'storybook-test',
|
||||
userId: null,
|
||||
recordId: null,
|
||||
},
|
||||
},
|
||||
beforeEach: () => {
|
||||
errorHandler.mockClear();
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
declare const bundleSizes: {
|
||||
name: string;
|
||||
reactBytes: number;
|
||||
preactBytes: number;
|
||||
}[];
|
||||
|
||||
export default bundleSizes;
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
import styled from '@emotion/styled';
|
||||
import { useState } from 'react';
|
||||
import { defineFrontComponent } from '@/sdk';
|
||||
|
||||
const Card = styled.div`
|
||||
padding: 24px;
|
||||
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
import { type ChangeEvent, useState } from 'react';
|
||||
|
||||
const CARD_STYLE = {
|
||||
padding: 24,
|
||||
backgroundColor: '#f0fdf4',
|
||||
border: '2px solid #22c55e',
|
||||
borderRadius: 12,
|
||||
fontFamily: 'system-ui, sans-serif',
|
||||
display: 'flex',
|
||||
flexDirection: 'column' as const,
|
||||
gap: 16,
|
||||
maxWidth: 400,
|
||||
};
|
||||
|
||||
const HEADING_STYLE = {
|
||||
color: '#166534',
|
||||
fontWeight: 700,
|
||||
fontSize: 18,
|
||||
margin: 0,
|
||||
};
|
||||
|
||||
const LABEL_STYLE = {
|
||||
fontSize: 13,
|
||||
fontWeight: 600,
|
||||
color: '#374151',
|
||||
};
|
||||
|
||||
const HINT_STYLE = {
|
||||
fontSize: 13,
|
||||
color: '#6b7280',
|
||||
};
|
||||
|
||||
const INPUT_STYLE = {
|
||||
padding: '8px 12px',
|
||||
border: '1px solid #d1d5db',
|
||||
borderRadius: 6,
|
||||
fontSize: 14,
|
||||
};
|
||||
|
||||
const SUBMIT_BUTTON_STYLE = {
|
||||
padding: '10px 20px',
|
||||
backgroundColor: '#16a34a',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: 6,
|
||||
fontWeight: 600,
|
||||
cursor: 'pointer',
|
||||
};
|
||||
|
||||
const FormEventsComponent = () => {
|
||||
const [textValue, setTextValue] = useState('');
|
||||
const [checkboxValue, setCheckboxValue] = useState(false);
|
||||
const [focusState, setFocusState] = useState('none');
|
||||
const [submittedData, setSubmittedData] = useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<div data-testid="form-events-component" style={CARD_STYLE}>
|
||||
<h2 style={HEADING_STYLE}>Form Events</h2>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
<label style={LABEL_STYLE}>Text Input</label>
|
||||
<input
|
||||
data-testid="text-input"
|
||||
type="text"
|
||||
placeholder="Type here..."
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
const detail = (event as unknown as { detail: { value?: string } })
|
||||
.detail;
|
||||
setTextValue(detail?.value ?? '');
|
||||
}}
|
||||
onFocus={() => setFocusState('focused')}
|
||||
onBlur={() => setFocusState('blurred')}
|
||||
style={INPUT_STYLE}
|
||||
/>
|
||||
<span data-testid="text-value" style={HINT_STYLE}>
|
||||
{textValue}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<input
|
||||
data-testid="checkbox-input"
|
||||
type="checkbox"
|
||||
checked={checkboxValue}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
const detail = (
|
||||
event as unknown as { detail: { checked?: boolean } }
|
||||
).detail;
|
||||
setCheckboxValue(detail?.checked ?? false);
|
||||
}}
|
||||
/>
|
||||
<label style={LABEL_STYLE}>Check me</label>
|
||||
<span data-testid="checkbox-value" style={HINT_STYLE}>
|
||||
{String(checkboxValue)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span data-testid="focus-state" style={HINT_STYLE}>
|
||||
{focusState}
|
||||
</span>
|
||||
|
||||
<button
|
||||
data-testid="submit-button"
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setSubmittedData(
|
||||
JSON.stringify({ text: textValue, checkbox: checkboxValue }),
|
||||
)
|
||||
}
|
||||
style={SUBMIT_BUTTON_STYLE}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
|
||||
{submittedData !== null && (
|
||||
<pre
|
||||
data-testid="submitted-data"
|
||||
style={{
|
||||
fontSize: 13,
|
||||
background: '#dcfce7',
|
||||
padding: 12,
|
||||
borderRadius: 8,
|
||||
margin: 0,
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
{submittedData}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'test-form-00000000-0000-0000-0000-000000000020',
|
||||
name: 'form-events-component',
|
||||
description:
|
||||
'Component testing form input events (onChange, onFocus, onBlur, submit)',
|
||||
component: FormEventsComponent,
|
||||
});
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
import {
|
||||
AppPath,
|
||||
closeSidePanel,
|
||||
defineFrontComponent,
|
||||
enqueueSnackbar,
|
||||
navigate,
|
||||
openSidePanelPage,
|
||||
SidePanelPages,
|
||||
unmountFrontComponent,
|
||||
updateProgress,
|
||||
} from 'twenty-sdk';
|
||||
import { useState } from 'react';
|
||||
|
||||
const CARD_STYLE = {
|
||||
padding: 24,
|
||||
backgroundColor: '#faf5ff',
|
||||
border: '2px solid #a78bfa',
|
||||
borderRadius: 12,
|
||||
fontFamily: 'system-ui, sans-serif',
|
||||
display: 'flex',
|
||||
flexDirection: 'column' as const,
|
||||
gap: 10,
|
||||
maxWidth: 400,
|
||||
};
|
||||
|
||||
const HEADING_STYLE = {
|
||||
color: '#5b21b6',
|
||||
fontWeight: 700,
|
||||
fontSize: 18,
|
||||
margin: 0,
|
||||
};
|
||||
|
||||
const BUTTON_STYLE = {
|
||||
padding: '8px 16px',
|
||||
backgroundColor: '#7c3aed',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: 6,
|
||||
fontWeight: 600,
|
||||
cursor: 'pointer',
|
||||
fontSize: 13,
|
||||
};
|
||||
|
||||
const STATUS_STYLE = {
|
||||
fontSize: 13,
|
||||
color: '#6b7280',
|
||||
fontFamily: 'monospace',
|
||||
};
|
||||
|
||||
const HostApiCallsComponent = () => {
|
||||
const [apiStatus, setApiStatus] = useState('idle');
|
||||
|
||||
const callApi = async (name: string, apiFunction: () => Promise<void>) => {
|
||||
try {
|
||||
await apiFunction();
|
||||
setApiStatus(`${name}:success`);
|
||||
} catch (error) {
|
||||
setApiStatus(
|
||||
`${name}:error:${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div data-testid="host-api-calls-component" style={CARD_STYLE}>
|
||||
<h2 style={HEADING_STYLE}>Host API Calls</h2>
|
||||
|
||||
<button
|
||||
data-testid="btn-navigate"
|
||||
type="button"
|
||||
onClick={() =>
|
||||
callApi('navigate', () =>
|
||||
navigate(AppPath.RecordIndexPage, {
|
||||
objectNamePlural: 'companies',
|
||||
}),
|
||||
)
|
||||
}
|
||||
style={BUTTON_STYLE}
|
||||
>
|
||||
Navigate
|
||||
</button>
|
||||
|
||||
<button
|
||||
data-testid="btn-snackbar"
|
||||
type="button"
|
||||
onClick={() =>
|
||||
callApi('snackbar', () =>
|
||||
enqueueSnackbar({
|
||||
message: 'Test notification',
|
||||
variant: 'success',
|
||||
}),
|
||||
)
|
||||
}
|
||||
style={BUTTON_STYLE}
|
||||
>
|
||||
Snackbar
|
||||
</button>
|
||||
|
||||
<button
|
||||
data-testid="btn-side-panel"
|
||||
type="button"
|
||||
onClick={() =>
|
||||
callApi('sidePanel', () =>
|
||||
openSidePanelPage({
|
||||
page: SidePanelPages.ViewRecord,
|
||||
pageTitle: 'Test Record',
|
||||
}),
|
||||
)
|
||||
}
|
||||
style={BUTTON_STYLE}
|
||||
>
|
||||
Open Side Panel
|
||||
</button>
|
||||
|
||||
<button
|
||||
data-testid="btn-close-panel"
|
||||
type="button"
|
||||
onClick={() => callApi('closePanel', () => closeSidePanel())}
|
||||
style={BUTTON_STYLE}
|
||||
>
|
||||
Close Side Panel
|
||||
</button>
|
||||
|
||||
<button
|
||||
data-testid="btn-unmount"
|
||||
type="button"
|
||||
onClick={() => callApi('unmount', () => unmountFrontComponent())}
|
||||
style={BUTTON_STYLE}
|
||||
>
|
||||
Unmount
|
||||
</button>
|
||||
|
||||
<button
|
||||
data-testid="btn-progress"
|
||||
type="button"
|
||||
onClick={() => callApi('progress', () => updateProgress(50))}
|
||||
style={BUTTON_STYLE}
|
||||
>
|
||||
Update Progress (50)
|
||||
</button>
|
||||
|
||||
<span data-testid="api-status" style={STATUS_STYLE}>
|
||||
{apiStatus}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'test-hapi-00000000-0000-0000-0000-000000000022',
|
||||
name: 'host-api-calls-component',
|
||||
description:
|
||||
'Component testing host communication API calls (navigate, snackbar, side panel, etc.)',
|
||||
component: HostApiCallsComponent,
|
||||
});
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineFrontComponent } from '@/sdk';
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
import { useState } from 'react';
|
||||
|
||||
const InteractiveComponent = () => {
|
||||
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
import { type KeyboardEvent, useState } from 'react';
|
||||
|
||||
type RemoteKeyboardEventDetail = {
|
||||
key?: string;
|
||||
code?: string;
|
||||
shiftKey?: boolean;
|
||||
ctrlKey?: boolean;
|
||||
metaKey?: boolean;
|
||||
altKey?: boolean;
|
||||
};
|
||||
|
||||
const KeyboardEventsComponent = () => {
|
||||
const [lastKey, setLastKey] = useState('');
|
||||
const [lastCode, setLastCode] = useState('');
|
||||
const [modifiers, setModifiers] = useState('');
|
||||
const [keyCount, setKeyCount] = useState(0);
|
||||
|
||||
// remote-dom serializes keyboard events into CustomEvent.detail
|
||||
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
||||
const data = (event as unknown as { detail: RemoteKeyboardEventDetail })
|
||||
.detail;
|
||||
|
||||
setLastKey(data.key ?? '');
|
||||
setLastCode(data.code ?? '');
|
||||
setKeyCount((previousCount) => previousCount + 1);
|
||||
|
||||
const activeModifiers: string[] = [];
|
||||
if (data.shiftKey) activeModifiers.push('shift');
|
||||
if (data.ctrlKey) activeModifiers.push('ctrl');
|
||||
if (data.metaKey) activeModifiers.push('meta');
|
||||
if (data.altKey) activeModifiers.push('alt');
|
||||
setModifiers(activeModifiers.join(','));
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="keyboard-events-component"
|
||||
style={{
|
||||
padding: 24,
|
||||
backgroundColor: '#fefce8',
|
||||
border: '2px solid #eab308',
|
||||
borderRadius: 12,
|
||||
fontFamily: 'system-ui, sans-serif',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 12,
|
||||
maxWidth: 400,
|
||||
}}
|
||||
>
|
||||
<h2
|
||||
style={{
|
||||
color: '#854d0e',
|
||||
fontWeight: 700,
|
||||
fontSize: 18,
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
Keyboard Events
|
||||
</h2>
|
||||
|
||||
<input
|
||||
data-testid="keyboard-input"
|
||||
type="text"
|
||||
placeholder="Press keys here..."
|
||||
onKeyDown={handleKeyDown}
|
||||
style={{
|
||||
padding: '8px 12px',
|
||||
border: '1px solid #d1d5db',
|
||||
borderRadius: 6,
|
||||
fontSize: 14,
|
||||
}}
|
||||
/>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<span style={{ fontSize: 13, color: '#6b7280' }}>
|
||||
Key:{' '}
|
||||
<span
|
||||
data-testid="last-key"
|
||||
style={{ fontWeight: 700, color: '#854d0e' }}
|
||||
>
|
||||
{lastKey}
|
||||
</span>
|
||||
</span>
|
||||
<span style={{ fontSize: 13, color: '#6b7280' }}>
|
||||
Code:{' '}
|
||||
<span
|
||||
data-testid="last-code"
|
||||
style={{ fontWeight: 700, color: '#854d0e' }}
|
||||
>
|
||||
{lastCode}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<span style={{ fontSize: 13, color: '#6b7280' }}>
|
||||
Modifiers:{' '}
|
||||
<span
|
||||
data-testid="modifiers"
|
||||
style={{ fontWeight: 700, color: '#854d0e' }}
|
||||
>
|
||||
{modifiers}
|
||||
</span>
|
||||
</span>
|
||||
<span style={{ fontSize: 13, color: '#6b7280' }}>
|
||||
Key count:{' '}
|
||||
<span
|
||||
data-testid="key-count"
|
||||
style={{ fontWeight: 700, color: '#854d0e' }}
|
||||
>
|
||||
{keyCount}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'test-kbd0-00000000-0000-0000-0000-000000000021',
|
||||
name: 'keyboard-events-component',
|
||||
description:
|
||||
'Component testing keyboard event serialization (key, code, modifiers)',
|
||||
component: KeyboardEventsComponent,
|
||||
});
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineFrontComponent } from '@/sdk';
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const LifecycleComponent = () => {
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
import Button from '@mui/material/Button';
|
||||
import MuiChip from '@mui/material/Chip';
|
||||
import { useState } from 'react';
|
||||
import { defineFrontComponent } from '@/sdk';
|
||||
|
||||
const MuiComponent = () => {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
defineFrontComponent,
|
||||
useFrontComponentExecutionContext,
|
||||
useUserId,
|
||||
} from '@/sdk';
|
||||
} from 'twenty-sdk';
|
||||
import { useState } from 'react';
|
||||
|
||||
const CARD_STYLE = {
|
||||
padding: 24,
|
||||
@@ -72,7 +72,7 @@ const SdkContextComponent = () => {
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
{JSON.stringify(fullContext, null, 2) ?? 'undefined'}
|
||||
{JSON.stringify(fullContext, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
import { useState } from 'react';
|
||||
import { defineFrontComponent } from '@/sdk';
|
||||
|
||||
const SHADCN_CSS = `
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineFrontComponent } from '@/sdk';
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
|
||||
const StaticComponent = () => (
|
||||
<div
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
import { useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { defineFrontComponent } from '@/sdk';
|
||||
|
||||
const Card = styled.div`
|
||||
padding: 24px;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineFrontComponent } from '@/sdk';
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
import { useState } from 'react';
|
||||
|
||||
const TAILWIND_CSS = `
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
import { useState } from 'react';
|
||||
import { defineFrontComponent } from '@/sdk';
|
||||
import {
|
||||
Button,
|
||||
Chip,
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
type PropertySchema = {
|
||||
type: 'string' | 'number' | 'boolean';
|
||||
optional: boolean;
|
||||
};
|
||||
import { type PropertySchema } from './PropertySchema';
|
||||
import { SVG_PRESENTATION_PROPERTIES } from './SvgPresentationProperties';
|
||||
|
||||
export type AllowedHtmlElement = {
|
||||
tag: string;
|
||||
name: string;
|
||||
properties: Record<string, PropertySchema>;
|
||||
events?: string[];
|
||||
htmlTag?: string;
|
||||
};
|
||||
|
||||
export const ALLOWED_HTML_ELEMENTS: AllowedHtmlElement[] = [
|
||||
@@ -249,4 +248,421 @@ export const ALLOWED_HTML_ELEMENTS: AllowedHtmlElement[] = [
|
||||
height: { type: 'number', optional: true },
|
||||
},
|
||||
},
|
||||
|
||||
// Semantic inline text
|
||||
{ tag: 'html-b', name: 'HtmlB', properties: {} },
|
||||
{ tag: 'html-i', name: 'HtmlI', properties: {} },
|
||||
{ tag: 'html-u', name: 'HtmlU', properties: {} },
|
||||
{ tag: 'html-s', name: 'HtmlS', properties: {} },
|
||||
{ tag: 'html-mark', name: 'HtmlMark', properties: {} },
|
||||
{ tag: 'html-sub', name: 'HtmlSub', properties: {} },
|
||||
{ tag: 'html-sup', name: 'HtmlSup', properties: {} },
|
||||
{ tag: 'html-abbr', name: 'HtmlAbbr', properties: {} },
|
||||
{ tag: 'html-cite', name: 'HtmlCite', properties: {} },
|
||||
{ tag: 'html-kbd', name: 'HtmlKbd', properties: {} },
|
||||
{ tag: 'html-samp', name: 'HtmlSamp', properties: {} },
|
||||
{ tag: 'html-var', name: 'HtmlVar', properties: {} },
|
||||
{ tag: 'html-dfn', name: 'HtmlDfn', properties: {} },
|
||||
{ tag: 'html-bdi', name: 'HtmlBdi', properties: {} },
|
||||
{
|
||||
tag: 'html-bdo',
|
||||
name: 'HtmlBdo',
|
||||
properties: {
|
||||
dir: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-data',
|
||||
name: 'HtmlData',
|
||||
properties: {
|
||||
value: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
|
||||
// Edited/annotated text
|
||||
{
|
||||
tag: 'html-del',
|
||||
name: 'HtmlDel',
|
||||
properties: {
|
||||
cite: { type: 'string', optional: true },
|
||||
dateTime: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-ins',
|
||||
name: 'HtmlIns',
|
||||
properties: {
|
||||
cite: { type: 'string', optional: true },
|
||||
dateTime: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-q',
|
||||
name: 'HtmlQ',
|
||||
properties: {
|
||||
cite: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-time',
|
||||
name: 'HtmlTime',
|
||||
properties: {
|
||||
dateTime: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
|
||||
// Ruby annotations
|
||||
{ tag: 'html-ruby', name: 'HtmlRuby', properties: {} },
|
||||
{ tag: 'html-rt', name: 'HtmlRt', properties: {} },
|
||||
{ tag: 'html-rp', name: 'HtmlRp', properties: {} },
|
||||
|
||||
// Description lists
|
||||
{ tag: 'html-dl', name: 'HtmlDl', properties: {} },
|
||||
{ tag: 'html-dt', name: 'HtmlDt', properties: {} },
|
||||
{ tag: 'html-dd', name: 'HtmlDd', properties: {} },
|
||||
|
||||
// Structural/semantic
|
||||
{ tag: 'html-figure', name: 'HtmlFigure', properties: {} },
|
||||
{ tag: 'html-figcaption', name: 'HtmlFigcaption', properties: {} },
|
||||
{
|
||||
tag: 'html-details',
|
||||
name: 'HtmlDetails',
|
||||
properties: {
|
||||
open: { type: 'boolean', optional: true },
|
||||
},
|
||||
},
|
||||
{ tag: 'html-summary', name: 'HtmlSummary', properties: {} },
|
||||
{ tag: 'html-address', name: 'HtmlAddress', properties: {} },
|
||||
{
|
||||
tag: 'html-dialog',
|
||||
name: 'HtmlDialog',
|
||||
properties: {
|
||||
open: { type: 'boolean', optional: true },
|
||||
},
|
||||
},
|
||||
{ tag: 'html-hgroup', name: 'HtmlHgroup', properties: {} },
|
||||
{ tag: 'html-search', name: 'HtmlSearch', properties: {} },
|
||||
|
||||
// Table additions
|
||||
{ tag: 'html-caption', name: 'HtmlCaption', properties: {} },
|
||||
{
|
||||
tag: 'html-colgroup',
|
||||
name: 'HtmlColgroup',
|
||||
properties: {
|
||||
span: { type: 'number', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-col',
|
||||
name: 'HtmlCol',
|
||||
properties: {
|
||||
span: { type: 'number', optional: true },
|
||||
},
|
||||
},
|
||||
|
||||
// Form additions
|
||||
{
|
||||
tag: 'html-fieldset',
|
||||
name: 'HtmlFieldset',
|
||||
properties: {
|
||||
disabled: { type: 'boolean', optional: true },
|
||||
name: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{ tag: 'html-legend', name: 'HtmlLegend', properties: {} },
|
||||
{
|
||||
tag: 'html-output',
|
||||
name: 'HtmlOutput',
|
||||
properties: {
|
||||
name: { type: 'string', optional: true },
|
||||
htmlFor: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-progress',
|
||||
name: 'HtmlProgress',
|
||||
properties: {
|
||||
value: { type: 'number', optional: true },
|
||||
max: { type: 'number', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-meter',
|
||||
name: 'HtmlMeter',
|
||||
properties: {
|
||||
value: { type: 'number', optional: true },
|
||||
min: { type: 'number', optional: true },
|
||||
max: { type: 'number', optional: true },
|
||||
low: { type: 'number', optional: true },
|
||||
high: { type: 'number', optional: true },
|
||||
optimum: { type: 'number', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-optgroup',
|
||||
name: 'HtmlOptgroup',
|
||||
properties: {
|
||||
label: { type: 'string', optional: true },
|
||||
disabled: { type: 'boolean', optional: true },
|
||||
},
|
||||
},
|
||||
{ tag: 'html-datalist', name: 'HtmlDatalist', properties: {} },
|
||||
|
||||
// Media additions
|
||||
{ tag: 'html-picture', name: 'HtmlPicture', properties: {} },
|
||||
{
|
||||
tag: 'html-track',
|
||||
name: 'HtmlTrack',
|
||||
properties: {
|
||||
src: { type: 'string', optional: true },
|
||||
kind: { type: 'string', optional: true },
|
||||
srclang: { type: 'string', optional: true },
|
||||
label: { type: 'string', optional: true },
|
||||
default: { type: 'boolean', optional: true },
|
||||
},
|
||||
},
|
||||
|
||||
// Miscellaneous
|
||||
{ tag: 'html-wbr', name: 'HtmlWbr', properties: {} },
|
||||
{ tag: 'html-menu', name: 'HtmlMenu', properties: {} },
|
||||
|
||||
// SVG container/structural
|
||||
{
|
||||
tag: 'html-svg',
|
||||
name: 'HtmlSvg',
|
||||
properties: {
|
||||
...SVG_PRESENTATION_PROPERTIES,
|
||||
viewBox: { type: 'string', optional: true },
|
||||
xmlns: { type: 'string', optional: true },
|
||||
width: { type: 'string', optional: true },
|
||||
height: { type: 'string', optional: true },
|
||||
preserveAspectRatio: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-g',
|
||||
name: 'HtmlG',
|
||||
properties: {
|
||||
...SVG_PRESENTATION_PROPERTIES,
|
||||
},
|
||||
},
|
||||
{ tag: 'html-defs', name: 'HtmlDefs', properties: {} },
|
||||
{
|
||||
tag: 'html-symbol',
|
||||
name: 'HtmlSymbol',
|
||||
properties: {
|
||||
viewBox: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-use',
|
||||
name: 'HtmlUse',
|
||||
properties: {
|
||||
href: { type: 'string', optional: true },
|
||||
x: { type: 'string', optional: true },
|
||||
y: { type: 'string', optional: true },
|
||||
width: { type: 'string', optional: true },
|
||||
height: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-clippath',
|
||||
name: 'HtmlClipPath',
|
||||
htmlTag: 'clipPath',
|
||||
properties: {
|
||||
clipPathUnits: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-mask',
|
||||
name: 'HtmlMask',
|
||||
properties: {
|
||||
maskUnits: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
|
||||
// SVG shapes
|
||||
{
|
||||
tag: 'html-circle',
|
||||
name: 'HtmlCircle',
|
||||
properties: {
|
||||
...SVG_PRESENTATION_PROPERTIES,
|
||||
cx: { type: 'string', optional: true },
|
||||
cy: { type: 'string', optional: true },
|
||||
r: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-ellipse',
|
||||
name: 'HtmlEllipse',
|
||||
properties: {
|
||||
...SVG_PRESENTATION_PROPERTIES,
|
||||
cx: { type: 'string', optional: true },
|
||||
cy: { type: 'string', optional: true },
|
||||
rx: { type: 'string', optional: true },
|
||||
ry: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-rect',
|
||||
name: 'HtmlRect',
|
||||
properties: {
|
||||
...SVG_PRESENTATION_PROPERTIES,
|
||||
x: { type: 'string', optional: true },
|
||||
y: { type: 'string', optional: true },
|
||||
width: { type: 'string', optional: true },
|
||||
height: { type: 'string', optional: true },
|
||||
rx: { type: 'string', optional: true },
|
||||
ry: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-line',
|
||||
name: 'HtmlLine',
|
||||
properties: {
|
||||
...SVG_PRESENTATION_PROPERTIES,
|
||||
x1: { type: 'string', optional: true },
|
||||
y1: { type: 'string', optional: true },
|
||||
x2: { type: 'string', optional: true },
|
||||
y2: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-path',
|
||||
name: 'HtmlPath',
|
||||
properties: {
|
||||
...SVG_PRESENTATION_PROPERTIES,
|
||||
d: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-polygon',
|
||||
name: 'HtmlPolygon',
|
||||
properties: {
|
||||
...SVG_PRESENTATION_PROPERTIES,
|
||||
points: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-polyline',
|
||||
name: 'HtmlPolyline',
|
||||
properties: {
|
||||
...SVG_PRESENTATION_PROPERTIES,
|
||||
points: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
|
||||
// SVG text
|
||||
{
|
||||
tag: 'html-text',
|
||||
name: 'HtmlText',
|
||||
properties: {
|
||||
...SVG_PRESENTATION_PROPERTIES,
|
||||
x: { type: 'string', optional: true },
|
||||
y: { type: 'string', optional: true },
|
||||
dx: { type: 'string', optional: true },
|
||||
dy: { type: 'string', optional: true },
|
||||
textAnchor: { type: 'string', optional: true },
|
||||
dominantBaseline: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-tspan',
|
||||
name: 'HtmlTspan',
|
||||
properties: {
|
||||
...SVG_PRESENTATION_PROPERTIES,
|
||||
x: { type: 'string', optional: true },
|
||||
y: { type: 'string', optional: true },
|
||||
dx: { type: 'string', optional: true },
|
||||
dy: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
|
||||
// SVG gradients/patterns
|
||||
{
|
||||
tag: 'html-lineargradient',
|
||||
name: 'HtmlLinearGradient',
|
||||
htmlTag: 'linearGradient',
|
||||
properties: {
|
||||
x1: { type: 'string', optional: true },
|
||||
y1: { type: 'string', optional: true },
|
||||
x2: { type: 'string', optional: true },
|
||||
y2: { type: 'string', optional: true },
|
||||
gradientUnits: { type: 'string', optional: true },
|
||||
gradientTransform: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-radialgradient',
|
||||
name: 'HtmlRadialGradient',
|
||||
htmlTag: 'radialGradient',
|
||||
properties: {
|
||||
cx: { type: 'string', optional: true },
|
||||
cy: { type: 'string', optional: true },
|
||||
r: { type: 'string', optional: true },
|
||||
fx: { type: 'string', optional: true },
|
||||
fy: { type: 'string', optional: true },
|
||||
gradientUnits: { type: 'string', optional: true },
|
||||
gradientTransform: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-stop',
|
||||
name: 'HtmlStop',
|
||||
properties: {
|
||||
offset: { type: 'string', optional: true },
|
||||
stopColor: { type: 'string', optional: true },
|
||||
stopOpacity: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-pattern',
|
||||
name: 'HtmlPattern',
|
||||
properties: {
|
||||
x: { type: 'string', optional: true },
|
||||
y: { type: 'string', optional: true },
|
||||
width: { type: 'string', optional: true },
|
||||
height: { type: 'string', optional: true },
|
||||
patternUnits: { type: 'string', optional: true },
|
||||
patternTransform: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
|
||||
// SVG other
|
||||
{
|
||||
tag: 'html-image',
|
||||
name: 'HtmlImage',
|
||||
properties: {
|
||||
href: { type: 'string', optional: true },
|
||||
x: { type: 'string', optional: true },
|
||||
y: { type: 'string', optional: true },
|
||||
width: { type: 'string', optional: true },
|
||||
height: { type: 'string', optional: true },
|
||||
preserveAspectRatio: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-foreignobject',
|
||||
name: 'HtmlForeignObject',
|
||||
htmlTag: 'foreignObject',
|
||||
properties: {
|
||||
x: { type: 'string', optional: true },
|
||||
y: { type: 'string', optional: true },
|
||||
width: { type: 'string', optional: true },
|
||||
height: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: 'html-marker',
|
||||
name: 'HtmlMarker',
|
||||
properties: {
|
||||
markerWidth: { type: 'string', optional: true },
|
||||
markerHeight: { type: 'string', optional: true },
|
||||
refX: { type: 'string', optional: true },
|
||||
refY: { type: 'string', optional: true },
|
||||
orient: { type: 'string', optional: true },
|
||||
markerUnits: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{ tag: 'html-title', name: 'HtmlTitle', properties: {} },
|
||||
];
|
||||
|
||||
@@ -4,24 +4,29 @@ const UTILITY_TAG_MAPPINGS: Record<string, string> = {
|
||||
'remote-style': 'RemoteStyle',
|
||||
};
|
||||
|
||||
const getHostTagName = (element: (typeof ALLOWED_HTML_ELEMENTS)[number]) =>
|
||||
element.htmlTag ??
|
||||
(element.tag.startsWith('html-') ? element.tag.slice(5) : element.tag);
|
||||
|
||||
export const HTML_TAG_TO_REMOTE_COMPONENT: Record<string, string> = {
|
||||
...Object.fromEntries(
|
||||
ALLOWED_HTML_ELEMENTS.map((element) => [
|
||||
element.tag.startsWith('html-') ? element.tag.slice(5) : element.tag,
|
||||
getHostTagName(element),
|
||||
element.name,
|
||||
]),
|
||||
),
|
||||
...UTILITY_TAG_MAPPINGS,
|
||||
};
|
||||
|
||||
// Maps standard HTML tag names to their custom element equivalents
|
||||
// used by the remote DOM polyfill (e.g. "div" → "html-div").
|
||||
// Maps standard HTML/SVG tag names to their custom element equivalents
|
||||
// used by the remote DOM polyfill (e.g. "div" → "html-div",
|
||||
// "clipPath" → "html-clippath").
|
||||
// Consumed by the jsx-runtime wrapper so React creates the correct
|
||||
// custom elements instead of standard HTML tags.
|
||||
// custom elements instead of standard HTML/SVG tags.
|
||||
export const HTML_TAG_TO_CUSTOM_ELEMENT_TAG: Record<string, string> = {
|
||||
...Object.fromEntries(
|
||||
ALLOWED_HTML_ELEMENTS.map((element) => [
|
||||
element.tag.startsWith('html-') ? element.tag.slice(5) : element.tag,
|
||||
getHostTagName(element),
|
||||
element.tag,
|
||||
]),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { type PropertySchema } from './PropertySchema';
|
||||
|
||||
export const SVG_PRESENTATION_PROPERTIES: Record<string, PropertySchema> = {
|
||||
fill: { type: 'string', optional: true },
|
||||
fillOpacity: { type: 'string', optional: true },
|
||||
fillRule: { type: 'string', optional: true },
|
||||
stroke: { type: 'string', optional: true },
|
||||
strokeWidth: { type: 'string', optional: true },
|
||||
strokeOpacity: { type: 'string', optional: true },
|
||||
strokeLinecap: { type: 'string', optional: true },
|
||||
strokeLinejoin: { type: 'string', optional: true },
|
||||
strokeDasharray: { type: 'string', optional: true },
|
||||
strokeDashoffset: { type: 'string', optional: true },
|
||||
strokeMiterlimit: { type: 'string', optional: true },
|
||||
opacity: { type: 'string', optional: true },
|
||||
transform: { type: 'string', optional: true },
|
||||
clipPath: { type: 'string', optional: true },
|
||||
clipRule: { type: 'string', optional: true },
|
||||
mask: { type: 'string', optional: true },
|
||||
filter: { type: 'string', optional: true },
|
||||
pointerEvents: { type: 'string', optional: true },
|
||||
};
|
||||
+156
@@ -92,6 +92,162 @@ export const componentRegistry: Map<string, ComponentRegistryValue> = new Map([
|
||||
'html-source',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('source')),
|
||||
],
|
||||
['html-b', createRemoteComponentRenderer(createHtmlHostWrapper('b'))],
|
||||
['html-i', createRemoteComponentRenderer(createHtmlHostWrapper('i'))],
|
||||
['html-u', createRemoteComponentRenderer(createHtmlHostWrapper('u'))],
|
||||
['html-s', createRemoteComponentRenderer(createHtmlHostWrapper('s'))],
|
||||
['html-mark', createRemoteComponentRenderer(createHtmlHostWrapper('mark'))],
|
||||
['html-sub', createRemoteComponentRenderer(createHtmlHostWrapper('sub'))],
|
||||
['html-sup', createRemoteComponentRenderer(createHtmlHostWrapper('sup'))],
|
||||
['html-abbr', createRemoteComponentRenderer(createHtmlHostWrapper('abbr'))],
|
||||
['html-cite', createRemoteComponentRenderer(createHtmlHostWrapper('cite'))],
|
||||
['html-kbd', createRemoteComponentRenderer(createHtmlHostWrapper('kbd'))],
|
||||
['html-samp', createRemoteComponentRenderer(createHtmlHostWrapper('samp'))],
|
||||
['html-var', createRemoteComponentRenderer(createHtmlHostWrapper('var'))],
|
||||
['html-dfn', createRemoteComponentRenderer(createHtmlHostWrapper('dfn'))],
|
||||
['html-bdi', createRemoteComponentRenderer(createHtmlHostWrapper('bdi'))],
|
||||
['html-bdo', createRemoteComponentRenderer(createHtmlHostWrapper('bdo'))],
|
||||
['html-data', createRemoteComponentRenderer(createHtmlHostWrapper('data'))],
|
||||
['html-del', createRemoteComponentRenderer(createHtmlHostWrapper('del'))],
|
||||
['html-ins', createRemoteComponentRenderer(createHtmlHostWrapper('ins'))],
|
||||
['html-q', createRemoteComponentRenderer(createHtmlHostWrapper('q'))],
|
||||
['html-time', createRemoteComponentRenderer(createHtmlHostWrapper('time'))],
|
||||
['html-ruby', createRemoteComponentRenderer(createHtmlHostWrapper('ruby'))],
|
||||
['html-rt', createRemoteComponentRenderer(createHtmlHostWrapper('rt'))],
|
||||
['html-rp', createRemoteComponentRenderer(createHtmlHostWrapper('rp'))],
|
||||
['html-dl', createRemoteComponentRenderer(createHtmlHostWrapper('dl'))],
|
||||
['html-dt', createRemoteComponentRenderer(createHtmlHostWrapper('dt'))],
|
||||
['html-dd', createRemoteComponentRenderer(createHtmlHostWrapper('dd'))],
|
||||
[
|
||||
'html-figure',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('figure')),
|
||||
],
|
||||
[
|
||||
'html-figcaption',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('figcaption')),
|
||||
],
|
||||
[
|
||||
'html-details',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('details')),
|
||||
],
|
||||
[
|
||||
'html-summary',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('summary')),
|
||||
],
|
||||
[
|
||||
'html-address',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('address')),
|
||||
],
|
||||
[
|
||||
'html-dialog',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('dialog')),
|
||||
],
|
||||
[
|
||||
'html-hgroup',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('hgroup')),
|
||||
],
|
||||
[
|
||||
'html-search',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('search')),
|
||||
],
|
||||
[
|
||||
'html-caption',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('caption')),
|
||||
],
|
||||
[
|
||||
'html-colgroup',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('colgroup')),
|
||||
],
|
||||
['html-col', createRemoteComponentRenderer(createHtmlHostWrapper('col'))],
|
||||
[
|
||||
'html-fieldset',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('fieldset')),
|
||||
],
|
||||
[
|
||||
'html-legend',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('legend')),
|
||||
],
|
||||
[
|
||||
'html-output',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('output')),
|
||||
],
|
||||
[
|
||||
'html-progress',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('progress')),
|
||||
],
|
||||
['html-meter', createRemoteComponentRenderer(createHtmlHostWrapper('meter'))],
|
||||
[
|
||||
'html-optgroup',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('optgroup')),
|
||||
],
|
||||
[
|
||||
'html-datalist',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('datalist')),
|
||||
],
|
||||
[
|
||||
'html-picture',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('picture')),
|
||||
],
|
||||
['html-track', createRemoteComponentRenderer(createHtmlHostWrapper('track'))],
|
||||
['html-wbr', createRemoteComponentRenderer(createHtmlHostWrapper('wbr'))],
|
||||
['html-menu', createRemoteComponentRenderer(createHtmlHostWrapper('menu'))],
|
||||
['html-svg', createRemoteComponentRenderer(createHtmlHostWrapper('svg'))],
|
||||
['html-g', createRemoteComponentRenderer(createHtmlHostWrapper('g'))],
|
||||
['html-defs', createRemoteComponentRenderer(createHtmlHostWrapper('defs'))],
|
||||
[
|
||||
'html-symbol',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('symbol')),
|
||||
],
|
||||
['html-use', createRemoteComponentRenderer(createHtmlHostWrapper('use'))],
|
||||
[
|
||||
'html-clippath',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('clipPath')),
|
||||
],
|
||||
['html-mask', createRemoteComponentRenderer(createHtmlHostWrapper('mask'))],
|
||||
[
|
||||
'html-circle',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('circle')),
|
||||
],
|
||||
[
|
||||
'html-ellipse',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('ellipse')),
|
||||
],
|
||||
['html-rect', createRemoteComponentRenderer(createHtmlHostWrapper('rect'))],
|
||||
['html-line', createRemoteComponentRenderer(createHtmlHostWrapper('line'))],
|
||||
['html-path', createRemoteComponentRenderer(createHtmlHostWrapper('path'))],
|
||||
[
|
||||
'html-polygon',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('polygon')),
|
||||
],
|
||||
[
|
||||
'html-polyline',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('polyline')),
|
||||
],
|
||||
['html-text', createRemoteComponentRenderer(createHtmlHostWrapper('text'))],
|
||||
['html-tspan', createRemoteComponentRenderer(createHtmlHostWrapper('tspan'))],
|
||||
[
|
||||
'html-lineargradient',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('linearGradient')),
|
||||
],
|
||||
[
|
||||
'html-radialgradient',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('radialGradient')),
|
||||
],
|
||||
['html-stop', createRemoteComponentRenderer(createHtmlHostWrapper('stop'))],
|
||||
[
|
||||
'html-pattern',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('pattern')),
|
||||
],
|
||||
['html-image', createRemoteComponentRenderer(createHtmlHostWrapper('image'))],
|
||||
[
|
||||
'html-foreignobject',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('foreignObject')),
|
||||
],
|
||||
[
|
||||
'html-marker',
|
||||
createRemoteComponentRenderer(createHtmlHostWrapper('marker')),
|
||||
],
|
||||
['html-title', createRemoteComponentRenderer(createHtmlHostWrapper('title'))],
|
||||
['remote-style', createRemoteComponentRenderer(RemoteStyleRenderer)],
|
||||
['remote-fragment', RemoteFragmentRenderer],
|
||||
]);
|
||||
|
||||
@@ -13,14 +13,14 @@ const EVENT_NAME_MAP: Record<string, string> = Object.fromEntries(
|
||||
);
|
||||
|
||||
const VOID_ELEMENTS = new Set([
|
||||
'input',
|
||||
'br',
|
||||
'hr',
|
||||
'img',
|
||||
'area',
|
||||
'base',
|
||||
'br',
|
||||
'col',
|
||||
'embed',
|
||||
'hr',
|
||||
'img',
|
||||
'input',
|
||||
'link',
|
||||
'meta',
|
||||
'source',
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -31,7 +31,6 @@
|
||||
"src/remote/mock/**/*",
|
||||
"src/host/generated/host-component-registry.ts",
|
||||
"src/remote/generated/remote-components.ts",
|
||||
"src/remote/generated/remote-elements.ts",
|
||||
"src/__stories__/**/*"
|
||||
"src/remote/generated/remote-elements.ts"
|
||||
]
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+10
-24
@@ -2,45 +2,31 @@ import { styled } from '@linaria/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useContext } from 'react';
|
||||
import { IconLock } from 'twenty-ui/display';
|
||||
import { Card, CardContent } from 'twenty-ui/layout';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledVisibilityCardContainer = styled.div`
|
||||
color: ${themeCssVariables.font.color.light};
|
||||
flex: 1;
|
||||
transition: color ${themeCssVariables.animation.duration.normal} ease;
|
||||
width: 100%;
|
||||
|
||||
> * {
|
||||
border-color: ${themeCssVariables.border.color.light};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledVisibilityCardContentContainer = styled.div`
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
background-color: ${themeCssVariables.background.transparent.lighter};
|
||||
background: ${themeCssVariables.background.transparent.lighter};
|
||||
border: 1px solid ${themeCssVariables.border.color.light};
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
box-sizing: border-box;
|
||||
color: ${themeCssVariables.font.color.light};
|
||||
display: flex;
|
||||
font-size: ${themeCssVariables.font.size.sm};
|
||||
font-weight: ${themeCssVariables.font.weight.medium};
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
height: ${themeCssVariables.spacing[6]};
|
||||
padding: ${themeCssVariables.spacing[0]} ${themeCssVariables.spacing[1]};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const CalendarEventNotSharedContent = () => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<StyledVisibilityCardContainer>
|
||||
<Card>
|
||||
<StyledVisibilityCardContentContainer>
|
||||
<CardContent>
|
||||
<IconLock size={theme.icon.size.sm} />
|
||||
<Trans>Not shared</Trans>
|
||||
</CardContent>
|
||||
</StyledVisibilityCardContentContainer>
|
||||
</Card>
|
||||
</StyledVisibilityCardContainer>
|
||||
<StyledContainer>
|
||||
<IconLock size={theme.icon.size.sm} />
|
||||
<Trans>Not shared</Trans>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -204,6 +204,14 @@ const SettingsApplicationRegistrationDetails = lazy(() =>
|
||||
})),
|
||||
);
|
||||
|
||||
const SettingsApplicationRegistrationConfigVariableDetail = lazy(() =>
|
||||
import(
|
||||
'~/pages/settings/applications/components/SettingsApplicationRegistrationConfigVariableDetail'
|
||||
).then((module) => ({
|
||||
default: module.SettingsApplicationRegistrationConfigVariableDetail,
|
||||
})),
|
||||
);
|
||||
|
||||
const SettingsAgentForm = lazy(() =>
|
||||
import('~/pages/settings/ai/SettingsAgentForm').then((module) => ({
|
||||
default: module.SettingsAgentForm,
|
||||
@@ -744,6 +752,10 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => (
|
||||
path={SettingsPath.ApplicationLogicFunctionDetail}
|
||||
element={<SettingsLogicFunctionDetail />}
|
||||
/>
|
||||
<Route
|
||||
path={SettingsPath.ApplicationRegistrationConfigVariableDetails}
|
||||
element={<SettingsApplicationRegistrationConfigVariableDetail />}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route
|
||||
|
||||
@@ -32,6 +32,7 @@ import { useResetFocusStackToFocusItem } from '@/ui/utilities/focus/hooks/useRes
|
||||
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { currentPageLayoutIdState } from '@/page-layout/states/currentPageLayoutIdState';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import {
|
||||
@@ -44,6 +45,7 @@ import { AppBasePath, AppPath, SidePanelPages } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { AnalyticsType } from '~/generated-metadata/graphql';
|
||||
import { usePageChangeEffectNavigateLocation } from '~/hooks/usePageChangeEffectNavigateLocation';
|
||||
import { getPageLayoutIdForLocation } from '~/modules/app/utils/getPageLayoutIdForLocation';
|
||||
import { useInitializeQueryParamState } from '~/modules/app/hooks/useInitializeQueryParamState';
|
||||
import { isMatchingLocation } from '~/utils/isMatchingLocation';
|
||||
import { getPageTitleFromPath } from '~/utils/title-utils';
|
||||
@@ -146,10 +148,15 @@ export const PageChangeEffect = () => {
|
||||
if (!previousLocation || previousLocation !== location.pathname) {
|
||||
setPreviousLocation(location.pathname);
|
||||
executeTasksOnAnyLocationChange();
|
||||
} else {
|
||||
return;
|
||||
|
||||
const newPageLayoutId = getPageLayoutIdForLocation({
|
||||
location,
|
||||
store,
|
||||
});
|
||||
|
||||
store.set(currentPageLayoutIdState.atom, newPageLayoutId);
|
||||
}
|
||||
}, [location, previousLocation, executeTasksOnAnyLocationChange]);
|
||||
}, [location, previousLocation, executeTasksOnAnyLocationChange, store]);
|
||||
|
||||
useEffect(() => {
|
||||
initializeQueryParamState();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user