Im making a react page with fluent ui and i wanted to open a certain component (documentpane.tsx) when i click a certain button from my command bar.
this is my commandbar code:
const theme = getTheme();
// Styles for both command bar and overflow/menu items
const itemStyles: Partial<IContextualMenuItemStyles> = {
label: { fontSize: 18 },
icon: { color: uPrinceTheme.palette.themePrimary },
iconHovered: { color: uPrinceTheme.palette.themeSecondary },
};
// For passing the styles through to the context menus
const menuStyles: Partial<IContextualMenuStyles> = {
subComponentStyles: { menuItem: itemStyles, callout: {} },
};
const getCommandBarButtonStyles = memoizeFunction(
(originalStyles: IButtonStyles | undefined): Partial<IContextualMenuItemStyles> => {
if (!originalStyles) {
return itemStyles;
}
return concatStyleSets(originalStyles, itemStyles);
},
);
// Custom renderer for main command bar items
const CustomButton: React.FunctionComponent<IButtonProps> = props => {
const buttonOnMouseClick = () => alert(`${props.text} clicked`);
// eslint-disable-next-line react/jsx-no-bind
return <CommandBarButton {...props} onClick={buttonOnMouseClick} styles={getCommandBarButtonStyles(props.styles)} />;
};
// Custom renderer for menu items (these must have a separate custom renderer because it's unlikely
// that the same component could be rendered properly as both a command bar item and menu item).
// It's also okay to custom render only the command bar items without changing the menu items.
const CustomMenuItem: React.FunctionComponent<IContextualMenuItemProps> = props => {
// Due to ContextualMenu implementation quirks, passing styles or onClick here doesn't work.
// The onClick handler must be on the ICommandBarItemProps item instead (_overflowItems in this example).
return <ContextualMenuItem {...props} />;
};
const overflowProps: IButtonProps = {
ariaLabel: 'More commands',
menuProps: {
contextualMenuItemAs: CustomMenuItem,
// Styles are passed through to menu items here
styles: menuStyles,
items: [], // CommandBar will determine items rendered in overflow
isBeakVisible: true,
beakWidth: 20,
gapSpace: 10,
directionalHint: DirectionalHint.topCenter,
},
};
export const CommandBarButtonAsExample: React.FunctionComponent = () => {
return (
<CommandBar
overflowButtonProps={overflowProps}
// Custom render all buttons
buttonAs={CustomButton}
items={_items}
ariaLabel="Use left and right arrow keys to navigate between commands"
/>
);
};
const _items: ICommandBarItemProps[] = [
{
key: 'newItem',
text: 'Create',
iconProps: { iconName: 'Add' },
},
{
key: 'upload',
text: 'Read',
iconProps: { iconName: 'Read' },
href: 'https://developer.microsoft.com/en-us/fluentui',
},
{ key: 'share', text: 'Update', iconProps: { iconName: 'Share' } },
{ key: 'download', text: 'Delete', iconProps: { iconName: 'Delete' } },
];
export default CommandBarButtonAsExample;
and this is my index.tsx now:
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { initializeIcons } from "#fluentui/react/lib/Icons";
import App from "./App";
const rootElement = document.getElementById("root");
initializeIcons();
ReactDOM.render(
<StrictMode>
<App />
</StrictMode>,
rootElement
);
i will also add a git repo with my code in it so you guys can see it better.
https://github.com/robbe-delsoir/app2
thank you very much for the help!
Robbe
Add an onClick method in the commandbar item which sets the show component state for documentpane.tsx.
const _items: ICommandBarItemProps[] = [
{
key: 'newItem',
text: 'Create',
iconProps: { iconName: 'Add' },
},
{
key: 'upload',
text: 'Read',
iconProps: { iconName: 'Read' },
onClick:{ () => { setShowDocumentPane(true) } }
href: 'https://developer.microsoft.com/en-us/fluentui',
},
{ key: 'share', text: 'Update', iconProps: { iconName: 'Share' } },
{ key: 'download', text: 'Delete', iconProps: { iconName: 'Delete' } },
];
Related
I am trying to create Tabs and have JSX Components dynamically placed into each Tab as content. I am using React and Polaris as I am creating a new Shopify App.
I cannot seem to work out how to do this - I am very new to Javascript/Typescript and even React.
I have all the Tabs working showing the correct details in each, but I cannot pull the child JSX 'DesignForm' and make it show as within the First Tab.
import React, { Children } from "react";
import { Card, Page, Layout, TextContainer, Image, Stack, Link, Heading, Tabs} from "#shopify/polaris";
import {ReactNode, useState, useCallback} from 'react';
import { DesignForm } from "../designform/DesignForm";
export function NavTabs() {
const [selected, setSelected] = useState(0);
interface childrenProps {
children: JSX.Element;
}
const index = ({ children }: childrenProps) => {
return (
<>
<DesignForm />
{children}
</>
);
};
const handleTabChange = useCallback(
(selectedTabIndex) => setSelected(selectedTabIndex),
[],
);
const tabs = [
{
id: 'all-customers-4',
content: 'All',
accessibilityLabel: 'All customers',
panelID: 'all-customers-content-4',
children: DesignForm,
},
{
id: 'accepts-marketing-4',
content: 'Accepts marketing',
panelID: 'accepts-marketing-content-4',
},
{
id: 'repeat-customers-4',
content: 'Repeat customers',
panelID: 'repeat-customers-content-4',
},
{
id: 'prospects-4',
content: 'Prospects',
panelID: 'prospects-content-4',
},
];
return (
<Card>
<Tabs
tabs={tabs}
selected={selected}
onSelect={handleTabChange}
disclosureText="More views"
>
<Card.Section title={tabs[selected].content}>
<p>Tab {selected} selected</p>
</Card.Section>
<Card.Section children={tabs[selected].children}></Card.Section>
</Tabs>
</Card>
);
}
in
{
id: 'all-customers-4',
content: 'All',
accessibilityLabel: 'All customers',
panelID: 'all-customers-content-4',
children: DesignForm,
}
Your children (i.e DesignForm) is a function here you should set the children to your component instead
{
id: 'all-customers-4',
content: 'All',
accessibilityLabel: 'All customers',
panelID: 'all-customers-content-4',
children: <DesignForm/>,
}
You also could replace
<Card.Section children={tabs[selected].children}></Card.Section>
by
<Card.Section>
{tabs[selected].children}
</Card.Section>
Few pointers
Move this outside of the component for performance reasons, and React components should start with Capital letter
interface childrenProps {
children: JSX.Element;
}
// change from index to Index
const Index = ({ children }: childrenProps) => {
return (
<>
<DesignForm />
{children}
</>
);
};
If this is static move it outside the component, as it will be recreated in every render
const tabs = [
{
id: 'all-customers-4',
content: 'All',
accessibilityLabel: 'All customers',
panelID: 'all-customers-content-4',
},
{
id: 'accepts-marketing-4',
content: 'Accepts marketing',
panelID: 'accepts-marketing-content-4',
},
{
id: 'repeat-customers-4',
content: 'Repeat customers',
panelID: 'repeat-customers-content-4',
},
{
id: 'prospects-4',
content: 'Prospects',
panelID: 'prospects-content-4',
},
];
Create a component map, or something similar to this
const componentMap = {
['all-customers-4']: DesignForm,
// ... more components can be added in the future
}
const EmptyComponent = () => null;
export function NavTabs() {
const [selected, setSelected] = useState(0);
const handleTabChange = useCallback(
(selectedTabIndex) => setSelected(selectedTabIndex),
[],
);
const ChildComponent = useMemo(() => {
return componentMap[selected] ?? EmptyComponent
}, [selected])
return (
<Card>
<Tabs
tabs={tabs}
selected={selected}
onSelect={handleTabChange}
disclosureText="More views"
>
<Card.Section title={tabs[selected].content}>
<p>Tab {selected} selected</p>
<ChildComponent />
</Card.Section>
<Card.Section children={tabs[selected].children}></Card.Section>
</Tabs>
</Card>
);
}
Hope this helps you in some way to find a good solution
Cheers
i am working on react and i want to add diffrent routes like admin and driver. I have two route object for each claims.I am getting user's role from api and i want to showing route by role(claim)
what i have tried,
i added that condition line -> Role==="Admin"? but i got error that Cannot find menuItem
what should i do? thanks in advance
const getRole="Admin" //for example
const admin= {
id: 'dashboard',
title: '',
type: 'group',
children: [
{
id: 'default',
title: 'addShipment',
type: 'item',
url: '/dashboard/addShipment',
icon: icons.IconDashboard,
breadcrumbs: false
},
{
id: 'default',
title: 'showShipment',
type: 'item',
url: '/dashboard/showShipment',
icon: icons.IconDashboard,
breadcrumbs: false
}
]
};
const driver= {
id: 'dashboard',
title: '',
type: 'group',
children: [
{
id: 'default',
title: 'showShipment',
type: 'item',
url: '/dashboard/showShipment',
icon: icons.IconDashboard,
breadcrumbs: false
}
]
};
{Role==="Admin"?
(
const menuItem = {
items: [admin]
};
)
:(
const menuItem = {
items: [driver]
};
)
const MenuList = () => {
const navItems = menuItem?.items.map((item) => {
switch (item.type) {
case 'group':
return <NavGroup key={item.id} item={item} />;
default:
return (
<Typography key={item.id} variant="h6" color="error" align="center">
Menu Items Error
</Typography>
);
}
});
return <>{navItems}</>;};
export default MenuList;
This is not the right way to initialize a variable:
{Role==="Admin"?
(
const menuItem = {
items: [admin]
};
)
:(
const menuItem = {
items: [driver]
};
)
}
The variable menuItem is only present within the scope of that condition.
If you want to use a ternary operator try this:
const menuItem = Role === "Admin" ? { items: [admin] } : { items: [driver] }
OR
const menuItem = { items: [Role === "Admin" ? admin: driver] }
The addProject function of my code is updating state correctly but the new project is not added to the DOM afterwards. WHY?
What I tried so far:
forceUpdate()
Made a deep copy of the array
Changed the key for the map to be the project title
import React from 'react'
const productBacklog = [
{ id: 1, text: 'FrontEnd'},
{ id: 2, text: 'Finished page - for Cluster'}
];
const parkingLot = [
{ id: 1, text: 'Home page: Google Log In/Github API ---Update: Have Google Cloud account for this'},
{ id: 2, text: 'Screenshots of steps needed to setup test class w/annotation & imports' },
];
const projects = [
{ id: 1, title: 'Parking Lot', children: parkingLot },
{ id: 2, title: 'Product Backlog', children: productBacklog }
];
export default class App extends React.Component {
constructor() {
super();
this.state = {
projects: projects,
}
}
addProject = () => {
const newProject = {
id: Math.round(Math.random() * 1000000),
title: 'newProject',
children: [],
}
const projects = [...this.state.projects];
projects.push(newProject);
this.setState({projects: [...projects]}, () => {
console.log(this.state.projects) // SHOWS THAT STATE IS UPDATED CORRECTLY
})
}
render () {
return (
<div className='App'>
<input type='button' value='Add project' onClick={this.addProject}/>
<div className='projects'>
{projects.map((project, projectId) =>
<div className='Project' key={projectId}>
Project-Title: { project.title}
</div>
)}
</div>
</div>
)
}
}
As I said in the comments, you need to map over this.state.projects. You should carefully name things.. this happened only because you had a global variable called projects (which is synced with the component's state, bad idea in my opinion), otherwise it would've been an obvious error. Also I think functional components are a little bit nicer:
import React, { useState } from 'react';
const productBacklog = [
{ id: 1, text: 'FrontEnd'},
{ id: 2, text: 'Finished page - for Cluster'}
];
const parkingLot = [
{ id: 1, text: 'Home page: Google Log In/Github API ---Update: Have Google Cloud account for this'},
{ id: 2, text: 'Screenshots of steps needed to setup test class w/annotation & imports' },
];
const projects = [
{ id: 1, title: 'Parking Lot', children: parkingLot },
{ id: 2, title: 'Product Backlog', children: productBacklog }
];
export default function App() {
const [state, setState] = useState({projects: projects})
const addProject = () => {
const newProject = {
id: Math.round(Math.random() * 1000000),
title: 'newProject',
children: [],
}
setState({projects: [...state.projects, newProject]})
}
return (
<div className='App'>
<input type='button' value='Add project' onClick={addProject}/>
<div className='projects'>
{state.projects.map((project, projectId) =>
<div className='Project' key={projectId}>
Project-Title: { project.title}
</div>
)}
</div>
</div>
)
}
Comment from Péter Leéh solved the problem: Map over this.state.projects instead of just projects. Thx!
I'm testing a state machine using model-based testing using #xstate/test and #testing-library/react.
Basically, I'm testing this machine:
const itemDamagedMachine = createMachine({
initial: 'newModal',
context: {
productScan: '',
binScan: '',
},
states: {
newModal: {
initial: 'scanDamagedItem',
states: {
scanDamagedItem: {},
scanDamagedBin: {},
declareItemDamaged: {},
},
},
closed: {},
},
on: {
UPDATE_PRODUCT_SCAN: {
actions: assign({
productScan: 123456,
}),
},
VALIDATE: {
target: 'newModal.scanDamagedBin',
},
UNREADABLE: {
target: 'newModal.scanDamagedBin',
},
CANCEL: {
target: 'closed',
},
UPDATE_DAMAGED_BIN_SCAN: {
actions: assign({
binScan: 'PB_DAMAGED',
}),
},
},
});
I'm then configuring the model, and testing it using const testPlans = itemDamagedModel.getSimplePathPlans();.
Everything seems to run smoothly with about 200 passing tests, but I'm having a few issues:
For each of my test and each of my event, I'm getting a warning Missing config for event "VALIDATE". I don't understand what it's supposed to mean.
All of my tests are validated even if I make typos on purpose in my model event. Sometimes the number of tests is reduced, but I would have hoped to see a few warnings when the model doesn't find a particular input or button.
The tests are all passing, even if I'm passing an empty div as my xstate/test rendered component.
I do not get the idea, but I have tested a component as follow:
First I have my machine:
import { createMachine, sendParent } from 'xstate';
export const machineDefinition = {
id: 'checkbox',
initial: 'unchecked',
states: {
unchecked: {
on: {
TOGGLE: [
{
actions: [ 'sendParent' ],
target: 'checked',
},
],
},
},
checked: {
on: {
TOGGLE: [
{
actions: [ 'sendParent' ],
target: 'unchecked',
},
],
},
},
},
};
const machineOptions = {
actions: {
sendParent: sendParent((context, event) => event.data),
},
};
export default createMachine(machineDefinition, machineOptions);
Second, I have extended the render method of testing-library
import React from 'react'
import HelmetProvider from 'react-navi-helmet-async'
import SpinnerProvider from '#atoms/GlobalSpinner'
import AlertProvider from '#molecules/GlobalAlert'
import InternationalizationProvider from '#internationalization/InternationalizationProvider'
import { render as originalRender } from '#testing-library/react'
const render = (ui, { locale = 'es', ...renderOptions } = {}) => {
const Wrapper = ({ children }) => {
return (
<InternationalizationProvider>
<AlertProvider>
<SpinnerProvider>
<HelmetProvider>
{children}
</HelmetProvider>
</SpinnerProvider>
</AlertProvider>
</InternationalizationProvider>
)
}
return originalRender(ui, { wrapper: Wrapper, ...renderOptions })
}
export * from '#testing-library/react'
export { render }
Finally, I have created the test
import React from 'react';
import { produce } from 'immer';
import { machineDefinition } from '#stateMachines/atoms/checkbox';
import { createMachine } from 'xstate';
import { createModel } from '#xstate/test';
import { render, cleanup, fireEvent } from '#root/jest.utils';
import Checkbox from '#atoms/Checkbox';
const getMachineDefinitionWithTests = () => produce(machineDefinition, (draft) => {
draft.states.unchecked.meta = {
test: ({ getByTestId }) => {
expect(getByTestId('checkbox-child-3')).toHaveClass('w-8 h-4 rounded-md duration-500 bg-dark-300 dark:bg-accent-100');
},
};
draft.states.checked.meta = {
test: ({ getByTestId }) => {
expect(getByTestId('checkbox-child-3')).toHaveClass('w-8 h-4 rounded-md duration-500 bg-dark-300 dark:bg-accent-100');
expect(getByTestId('checkbox-child-3.1')).toHaveClass('bg-light-100 w-4 h-4 rounded-full duration-500 dark:transform dark:translate-x-full');
},
};
});
const getEvents = () => ({
TOGGLE: {
exec: ({ getByTestId }) => {
fireEvent.click(getByTestId('checkbox-container'));
},
cases: [ {} ],
},
});
describe('checkbox', () => {
const machine = createMachine(getMachineDefinitionWithTests(), {
actions: {
sendParent: () => {},
},
});
const machineModel = createModel(machine)
.withEvents(getEvents());
const testPlans = machineModel.getSimplePathPlans();
testPlans.forEach((plan) => {
describe(plan.description, () => {
afterEach(cleanup);
plan.paths.forEach((path) => {
it(path.description, () => {
const rendered = render(
<Checkbox
test
label='main.txt1'
data={{}}
machine={machine}
/>,
{ locale: 'en' },
);
return path.test(rendered);
});
});
});
});
describe('coverage', () => {
it('should have full coverage', () => {
machineModel.testCoverage();
});
});
});
I have created a react boilerplate which contains XState, there you can find the previous test
Let's see we have the simple component ToggleButton:
const ButtonComponent = Vue.component('ButtonComponent', {
props: {
value: Boolean
},
methods: {
handleClick() {
this.$emit('toggle');
}
},
template: `
<button
:class="value ? 'on' : 'off'"
#click="handleClick"
>
Toggle
</button>`
});
And the story for that component:
import ToggleButton from './ToggleButton.vue';
export default {
title: 'ToggleButton',
component: ToggleButton,
argTypes: {
onToggle: {
action: 'toggle' // <-- instead of logging "toggle" I'd like to mutate `args.value` here
}
}
};
export const Default = (_args, { argTypes }) => ({
components: { ToggleButton },
props: Object.keys(argTypes),
template: `
<ToggleButton
:value="value"
:toggle="onToggle"
/>
`
});
Default.args = {
value: false
}
What I want to achieve is to handle toggle action inside the story and change value that I've used in Default.args object to change the button style by changing the class name from .off to .on.
I had the same exact issue, and kept looking for days, till I stumbled upon this github post:
https://github.com/storybookjs/storybook/issues/12006
Currently in my React (am sure vue approach will be similar), I do following:
import React from 'react';
import CheckboxGroupElement from '../CheckboxGroup';
import { STORYBOOK_CATEGORIES } from 'elements/storybook.categories';
import { useArgs } from '#storybook/client-api';
export default {
component: CheckboxGroupElement,
title: 'Components/CheckboxGroup',
argTypes: {
onChange: {
control: 'func',
table: {
category: STORYBOOK_CATEGORIES.EVENTS,
},
},
},
parameters: { actions: { argTypesRegex: '^on.*' } },
};
const Template = (args) => {
const [_, updateArgs] = useArgs();
const handle = (e, f) => {
// inside this function I am updating arguments, but you can call it anywhere according to your demand, the key solution here is using `useArgs()`
// As you see I am updating list of options with new state here
console.log(e, f);
updateArgs({ ...args, options: e });
};
return <CheckboxGroupElement {...args} onChange={handle} />;
};
export const CheckboxGroup = Template.bind({});
CheckboxGroup.storyName = 'CheckboxGroup';
CheckboxGroup.args = {
//Here you define default args for your story (initial ones)
controller: { label: 'Group controller' },
options: [
{ label: 'option 1', checked: true },
{ label: 'option 2', checked: false },
{ label: 'option 3', checked: false },
],
mode: 'nested',
};