Add a top level menu in react-admin - admin-on-rest

How can i a a top level menu in the appbar between the title and the usermenu ?
I tried something like this, but it do not work :
const MyAppBar = props => <AppBar {...props} userMenu={<MyUserMenu />} ><MyTopMenu /></AppBar>

Assuming you want to create a sub-menu in react-admin, you'll need to create a custom sub-menu component following the nested list technique from material-ui.
You will also need to create and use a custom menu as explained in react-admin's documentation.

An official way provided from the demo of react-admin: https://github.com/marmelab/react-admin/blob/master/examples/demo/src/layout/Menu.tsx (related answer)
Check this new plugin dedicated for this purpose: ra-tree-menu

As the ra-tree-menu package mentioned above by kxo didn't seem to be working well, I had to resort to develop a new package for this which could solve the use-case and further offer much more ease and flexibility for achieving the purpose.
You can check this out: ra-treemenu. A quick example of using it would be something like:
// In App.js
import * as React from 'react';
import { Admin, Resource, Layout } from 'react-admin';
/* Import TreeMenu from the package */
import TreeMenu from '#bb-tech/ra-treemenu';
const App = () => (
<Admin layout={(props) => <Layout {...props} menu={TreeMenu} />} >
{/* Dummy parent resource with isMenuParent options as true */}
<Resource name="users" options={{ "label": "Users", "isMenuParent": true }} />
{/* Children menu items under parent */}
<Resource name="posts" options={{ "label": "Posts", "menuParent": "users" }} />
<Resource name="comments" options={{ "label": "Comments", "menuParent": "users" }} />
{/* Dummy parent resource with isMenuParent options as true */}
<Resource name="groups" options={{ "label": "Groups", "isMenuParent": true }} />
{/* Children menu items under parent */}
<Resource name="members" options={{ "label": "Members", "menuParent": "groups" }} />
</Admin>
);
export default App;

Related

Connection useFormik and useState is not updating the input field value

Task: I'm trying to change the value of the input field using user-friendly button with. In other words to change the value of the field I don't use the input/select and other tags intended for this.
To be more precise, I'm making an online store with an administrative panel and creating the form to create a category(product categories, such as clothing, electronics and others). And each category has a ParentId(0 - if the first parent or some other value if the child category). I made the catalog button the same is in absolutely every online store and want to link this logic with the creation of a new category.
In the expanded categories menu, select a category, and the parent ID of this category will be shown in the text field. Like, I will select "Clothes and shoes" as the parent category, and a value will be placed in the text field, for example, 8 (the parent ID of the category "Clothes and Shoes")
My approach: I create one state field and change this one when I click on the one of the menu items. Menu items open when I click on the button.
import React, {FC, useState} from "react";
import {Box, Button, Stack, SxProps, TextField} from "#mui/material";
import {useFormik} from "formik";
// own modules
import Catalog from "../catalog/Catalog";
const CreateCategory: FC = (sx?: SxProps) => {
const [parentId, setParentId] = useState<number>(0);
const formik = useFormik({
initialValues: {
name: "",
label: "",
parentId: parentId
},
onSubmit: (values, {setSubmitting}) => {
console.log("values: ", values); // value of the parentId doesn't change on submit
// ...other code...
setSubmitting(false);
}
});
console.log("parentId: ", parentId); // but value of the parentId in the state change properly
return (
<Box
sx={{
display: "grid",
justifyContent: "flex-start",
gridTemplateColumns: "1fr 1fr 1fr 1fr",
gap: "2rem",
"> *": {
gridColumn: "span 2"
},
...sx
}}
component="form"
onSubmit={formik.handleSubmit}
>
<TextField
error={!!(formik.errors.name && formik.touched.name)}
name="name"
onChange={formik.handleChange}
value={formik.values.name}
label="Name (technical - in Latin)"
variant="outlined"
helperText={formik.errors.name}
/>
<TextField
error={!!(formik.errors.label && formik.touched.label)}
name="label"
onChange={formik.handleChange}
value={formik.values.label}
label="label(for users)"
variant="outlined"
helperText={formik.errors.label}
/>
<Stack direction="row" spacing={2}>
<TextField
sx={{flexGrow: 1}}
error={!!(formik.errors.parentId && formik.touched.parentId)}
name="parentId"
value={parentId}
label="parentId"
variant="outlined"
helperText={formik.errors.parentId}
/>
<Catalog onClickOverload={setParentId} /> {/*custom catalog button*/}
<Button
sx={{height: "100%", width: "max-content"}}
onClick={() => setParentId(0)} // you can change this value(0 to 10, example) to check, that initial value(0) doesn't change in the formik values
>
Set parent (0)
</Button>
</Stack>
<Button
sx={{gridColumnStart: 2, gridColumnEnd: 4}}
type="submit"
disabled={formik.isSubmitting}
variant="outlined">
Create
</Button>
</Box>
)
}
export default CreateCategory;
I can't figure out what to try, because the value of the state change but not the parentId value of the formik. I'm at a dead end. Where is my mistake?
There was no need to use useState in this case. It turned out that it was possible to use the following construction:
formik.setFieldValue(nameField, value);
And when you need to change the value in the some custom way - you can use this one.

JSX how to return an image correctly? Üebersicht widget

I would like to return an image as a widget.
I put my photo.png in a folder images next to myFile.jsx.
I have always the same error :
Can't walk dependency graph: Cannot find module './images/photo.png' form /Users/macbookpro/Library/Application Support/Übersicht/widgets/my file/myFile.jsx required by /Users/macbookpro/Library/Application Support/Übersicht/widgets/my file/myFile.jsx
I have also tried to: move the photo to my Desktop, use require, use import .. from ...
Here there is the code:
import { css } from "uebersicht";
// import photo from "./images/photo.jpg";
// import photo from "./Users/macbookpro/Desktop/photo.png";
export const render = () => {
return (
<div>
<img src="./images/photo.png" />
{/* <img src="./Users/macbookpro/Desktop/photo.png" /> */}
{/* <img src={require("./images/photo.png")} /> */}
{/* <img src={require("./Users/macbookpro/Desktop/photo.png")} /> */}
</div>
);
};
Since myFile.jsx is actually act as a component for uebersicht you have to include your widget folder name in you src.
For example if your photo.png is in example.widget/images folder, your should be:
<img src="example.widget/images/photo.png/>

gatsby-sharp images loaded via graphql in react-carousel not activated

I have created a react-bootstrap carousel with some images I load via graphql and gatsby-image-sharp (the source images are in directus).
I see the images are created in the code, but they never get assigned the "active" class. When I put the images outside of the carousel, they are shown correctly one below the other, so the retrieval seems to work fine.
If I manually add an item before and/or after the graphql images, those slides get shown correctly.
I also see that in case I put a slide before and after the graphql ones, the first "normal" slide gets shown and when the transaction is started, I get a delay corresponding to the number of generated slides before the next statically created slide is shown again.
When inspecting the site with the react development tools, I see the working slides have a transaction key of ".0" and ".2", while the non-working slides between them have transaction keys of ".1:0:0", ".1:0:1", etc...:
React devtools tree
The code I use to generate this is included below:
import React from "react"
import { Row, Col, Container, Carousel } from "react-bootstrap"
import { graphql } from "gatsby"
import Layout from "../components/layout"
import SEO from "../components/seo"
import Img from "gatsby-image"
const IndexPage = ({ data }) => (
<Layout pageInfo={{ pageName: `index` }}>
<SEO title="Home" keywords={[`gatsby`, `react`, `bootstrap`]} />
<Container className="text-center">
<Row>
<Col>
<Carousel>
<Carousel.Item>
<div className="d-block w-100 gatsby-image-wrapper">
<h1>test</h1>
</div>
</Carousel.Item>
{data.allDirectusSlideshow.edges.map((slideShow) => {
return slideShow.node.images.map((image) => {
const sharp = data.allImageSharp.edges.find((imageSharp) => (
imageSharp.node.fluid.originalName === image.filename_disk))
return <>
<Carousel.Item key={sharp.node.id}>
<h1>Image</h1>
<Img fluid={sharp.node.fluid} className="d-block w-100" />
</Carousel.Item>
</>
})
})}
<Carousel.Item>
<h1>Test 2</h1>
</Carousel.Item>
</Carousel>
</Col>
</Row>
</Container>
</Layout>
)
export const query = graphql`
query {
allDirectusSlideshow(filter: {title: {eq: "Voorpagina"}}) {
edges {
node {
title
images {
filename_disk
}
}
}
}
allImageSharp {
edges {
node {
id
fluid {
originalName
...GatsbyImageSharpFluid
}
}
}
}
}
`
export default IndexPage
As the directus api for this site is not public, I have put up a generated code example here (note: only the homepage works)
Apparently, bootstrap carousel expects its items to be a direct child of the carousel. This means that react fragments don't work over here.
Changing this:
return <>
<Carousel.Item key={sharp.node.id}>
<h1>Image</h1>
<Img fluid={sharp.node.fluid} className="d-block w-100" />
</Carousel.Item>
</>
To this solves the issue:
return (
<Carousel.Item key={sharp.node.id}>
<h1>Image</h1>
<Img fluid={sharp.node.fluid} className="d-block w-100" />
</Carousel.Item>
)

set header for drawer navigation

I tried to set header in react navigation v5 by setting options without any change
<Drawer.Navigator
initialRouteName='...'
>
<Drawer.Screen
name='...'
component={Component}
options={{ title: 'My home' }}
/>
</Drawer.Navigator>
is there a way I could have my react navigation header in drawer?
Update: The latest version of drawer navigator now has a header (can be shown with headerShown: true)
Drawer Navigator doesn't provide a header.
If you want to show headers in drawer screens, you have 2 options:
Provide your own header component. You can use header from libraries such as react-native-paper
Nest a Stack Navigator in each drawer screen where you want to show a header.
I was looking for the solution, however did not find any nice way to wrap up every component automatically with custom header. So I ended up creating HOC component and wrapping each component for every screen.
import React, {Fragment} from 'react';
const NavHeader = props => {
// ... NavHeader code goes here
};
export const withHeader = Component => {
return props => {
return (
<Fragment>
<NavHeader {...props} />
<Component {...props} />
</Fragment>
);
};
};
Then in your Drawer you do:
<Drawer.Navigator>
<Drawer.Screen
name={ROUTES.DASHBOARD}
component={withHeader(DashboardContainer)} // <--- Wrap it around component here.
/>
</Drawer.Navigator>
This is a simple example with react navigation 5:
function Root() {
return (
<Stack.Navigator>
<Stack.Screen name="Profile" component={Profile} />
<Stack.Screen name="Settings" component={Settings} />
</Stack.Navigator>
);
}
function App() {
return (
<NavigationContainer>
<Drawer.Navigator>
<Drawer.Screen name="Home" component={Home} />
<Drawer.Screen name="Root" component={Root} />
</Drawer.Navigator>
</NavigationContainer>
);
}
You can find about the navigation to a screen in a nested navigator in docs and you can try this example on Snack

radautocomplete menu pops open when navigating with bottom navigation

I have a radautocomplete in one of my pages and I'm using bottom-navigation in my app.
The first time I navigate to that page is fine, but after that, when I navigate to that page, the suggestions menu automatically pops open as if I had typed something in the autocomplete but I have not. I even put a textfields above that in my form to steal the focus but that didn't make things any better.
Here is a playground sample
In case playground breaks in the future:
App.vue
<template>
<Page actionBarHidden="true">
<BottomNavigation :selectedIndex="activePage">
<TabStrip>
<TabStripItem>
<label text="0" />
</TabStripItem>
<TabStripItem>
<label text="1" />
</TabStripItem>
</TabStrip>
<TabContentItem>
<button text="go to 1" #tap="activePage=1" />
</TabContentItem>
<TabContentItem>
<StackLayout>
<TextField v-model="textFieldValue" hint="Enter text..."
backgroundColor="lightgray" />
<RadAutoCompleteTextView ref="autocomplete"
:items="choices" backgroundColor="lightgray"
completionMode="Contains" returnKeyType="done"
width="100%" borderRadius="5" />
</StackLayout>
</TabContentItem>
</BottomNavigation>
</Page>
</template>
<script>
import {
ObservableArray
} from "tns-core-modules/data/observable-array";
import {
TokenModel
} from "nativescript-ui-autocomplete";
export default {
data() {
return {
textFieldValue: "",
choices: new ObservableArray(
["one", "two", "three"].map(r => new TokenModel(r))
),
activePage: 0
};
}
};
</script>
<style scoped>
TabContentItem>* {
font-size: 30;
text-align: center;
vertical-align: center;
}
</style>
app.js
import Vue from 'nativescript-vue';
import App from './components/App';
import RadAutoComplete from 'nativescript-ui-autocomplete/vue';
Vue.use(RadAutoComplete);
new Vue({ render: h => h('frame', [h(App)]) }).$start();
I guess the issue is specific to Android, iOS seem to work fine. You may raise an issue at Github, meanwhile a possible workaround is to set visibility on suggestion view on unloaded event, toggle it back on textChanged event.
Updated Playground Sample 1
Update
Changing visibility seems to hide the suggestion view but still occupy the same so components below auto complete field becomes inaccessible. I believe setSuggestionViewHeight(...) may solve this.
Updated Playground Sample 2

Resources