How to wrap text in Polaris Index Table cell? - shopify-app

am using Polaris Index Table to display some data in my Shopify app. One of the cells in my table has a long string of text and I want to make it wrap so that it fits the size of the screen. Is there a way to do this in Polaris?
Here is my code:
import {IndexTable, Card, useIndexResourceState, Text} from '#shopify/polaris';
import React from 'react';
function SimpleIndexTableExample() {
const customers = [
{
id: '3411',
url: 'customers/341',
name: 'Mae Jemison long text here, very very long......',
location: 'Decatur, USA',
orders: 20,
amountSpent: '$2,400',
},
{
id: '2561',
url: 'customers/256',
name: 'Ellen Ochoa',
location: 'Los Angeles, USA',
orders: 30,
amountSpent: '$140',
},
];
const resourceName = {
singular: 'customer',
plural: 'customers',
};
const {selectedResources, allResourcesSelected, handleSelectionChange} =
useIndexResourceState(customers);
const rowMarkup = customers.map(
({id, name, location, orders, amountSpent}, index) => (
<IndexTable.Row
id={id}
key={id}
selected={selectedResources.includes(id)}
position={index}
>
<IndexTable.Cell>
<Text variant="bodyMd" fontWeight="bold" as="span">
{name}
</Text>
</IndexTable.Cell>
<IndexTable.Cell>{location}</IndexTable.Cell>
<IndexTable.Cell>{orders}</IndexTable.Cell>
<IndexTable.Cell>{amountSpent}</IndexTable.Cell>
</IndexTable.Row>
),
);
return (
<Card>
<IndexTable
resourceName={resourceName}
itemCount={customers.length}
selectedItemsCount={
allResourcesSelected ? 'All' : selectedResources.length
}
onSelectionChange={handleSelectionChange}
headings={[
{title: 'Name'},
{title: 'Location'},
{title: 'Order count'},
{title: 'Amount spent'},
]}
>
{rowMarkup}
</IndexTable>
</Card>
);
}
I've tried modifying the cells, styles, etc.

To make the text wrap in a cell in a Polaris IndexTable, you can apply the textWrap property to the Text component that you are using within the cell.
You may try this,
import {IndexTable, Card, useIndexResourceState, Text} from '#shopify/polaris';
import React from 'react';
function SimpleIndexTableExample() {
// ...
const rowMarkup = customers.map(
({id, name, location, orders, amountSpent}, index) => (
<IndexTable.Row
id={id}
key={id}
selected={selectedResources.includes(id)}
position={index}
>
<IndexTable.Cell>
<Text variant="bodyMd" fontWeight="bold" as="span" textWrap>
{name}
</Text>
</IndexTable.Cell>
<IndexTable.Cell>{location}</IndexTable.Cell>
<IndexTable.Cell>{orders}</IndexTable.Cell>
<IndexTable.Cell>{amountSpent}</IndexTable.Cell>
</IndexTable.Row>
),
);
// ...
}

I found a workaround for this – in my case there was a HTML representation of a text string available. Then I have used following construction in IndexTable.Row content:
<div dangerouslySetInnerHTML={{__html: myHTMLContent}} />
This allowed using line breaks and other HTML that can be formatted in a way you need. Of course I understand that this adds another problem of creating HTML string, but this is just a workaround.
P.S. that this method adds a risk of XSS attack so can recommend to use this only if you 100% understand the security side.

Related

React-Admin <TranslateInput> how to get the translated label?

following the docs [here][1] I created some en.js and fr.js file to translate the fields of my react-admin app
examples:
const fr = {
resources: {
categories: {
name: 'Catégorie |||| Catégories',
fields: {
code: 'N°',
trigram: 'Trigramme',
description: {
en: 'Description (Anglais)',
fr: 'Description (Français)',
cn: 'Description (Chinois)',
},
},
},
},
};
I have a similar file for English and Chinese with the description as follow
description: {
en: 'Description (English)',
fr: 'Description (French)',
cn: 'Description (Chinese)',
},
and
description: {
en: '类别的描述 (英文)',
fr: '类别的描述 (法文)',
cn: '类别的描述 (中文)',
},
this seems to work fine and I can use the TranslatableInputs as well
<TranslatableInputs locales={['en', 'fr', 'cn']}>
<TextInput source='description' />
</TranslatableInputs>
But, the label in the tab view doesn't seem to be applied (while in the list mode they are shown properly) using description.en for example so the link with the .js files is working fine I guess.
But as shown in the documentation with the Music example the label doesn't change when we switch tab (actually the same problem is show in the documentation's gif
the "name" label doesn't change to "nom"
[![enter image description here][2]][2]
any help to solve that? thanks a lot and thanks a lot to RA team for their wonderful work as well
Nico
[1]: https://marmelab.com/react-admin/TranslatableInputs.html
[2]: https://i.stack.imgur.com/16fWf.gif
That's a great question... And a current limitation of the TranslatableInput component.
The input label translation uses the global user locale, not the one of the selected tab. That would be a great feature to add to react-admin, and I invite you to open a feature request in the react-admin repository.
In the meantime, you should write your own TranslatableInput based on react-admin's, and wrap each tab content with <I18NContext.Provider> in which you force a different locale.
Something like (not tested):
export const TranslatableInputs = (props) => {
const {
className,
defaultLocale,
locales,
groupKey = '',
selector = <TranslatableInputsTabs groupKey={groupKey} />,
children,
variant,
margin,
} = props;
const context = useTranslatable({ defaultLocale, locales });
const i18nProvider = useI18nProvider();
return (
<Root className={className}>
<TranslatableContextProvider value={context}>
{selector}
{locales.map(locale => (
<I18nContext.Provider value={{...i18nProvider, getLocale: () => locale }}>
<TranslatableInputsTabContent
key={locale}
locale={locale}
groupKey={groupKey}
variant={variant}
margin={margin}
>
{children}
</TranslatableInputsTabContent>
</I18nContext.Provider>
))}
</TranslatableContextProvider>
</Root>
);
};

react-admin filter with i18n fields

I have followed the docs of marmelab to add i18n to my react-admin, so far so good. One of my ressources has only a field that is translated in 3 languages (en.js fr.js etc.)
name: {
en: 'name',
fr: 'name (French)',
cn: 'name (Chinese)',
},
this is the components I have tried to add a filter to
import { Datagrid, List, TextInput, TextField } from 'react-admin';
function ManufacturerList() {
const manufacturerFilter = [
<TextInput
source='name.en'
alwaysOn
resettable
/>,
];
return (
<List
sort={{ field: 'name', order: 'ASC' }}
filters={manufacturerFilter}
>
<Datagrid
rowClick='show'
bulkActionButtons={false}
>
<TextField source={`name.en`} />
<TextField source={`name.fr`} />
<TextField source={`name.cn`} />
</Datagrid>
</List>
);
}
export default ManufacturerList;
so I have 3 problems:
the filter with name.en gives me an error "something went wrong", the url return after typing "Example" in the textinput filter is
http://localhost:3001/#/manufacturers?displayedFilters=%7B%7D&filter=%7B%22name%22%3A%7B%22en%22%3A%22example%22%7D%7D&order=ASC&page=1&perPage=10&sort=name
once i figure how to use the name.en, can i replace .en by .${locale} that would match the language chose by the user?
my search filter before i18n where source='name.like' to have partial match, is it possible to combine this with the i18n ?
and here is the query that is shown in react query devtool
["manufacturers","getList",{"filter":{"name":{"en":"example"}},"pagination":{"page":1,"perPage":10},"sort":{"field":"name","order":"ASC"}}]
--> if i edit the url and i get to this query below, then the filtering works
["manufacturers","getList",{"filter":{"name.en":"example"},"pagination":{"page":1,"perPage":10},"sort":{"field":"name","order":"ASC"}}]
React-admin sees compound names as paths, so it's normal that a filter on names.en translates to the following filter value in dataProvider.getList(): {"name":{"en":"example"}}.
If that's not what your API expects, then you must do the translation in your dataProvider, by flattening the filter value, e.g. using the flat npm module.
Something like:
const dataProvider = {
...baseDataProvider,
getList: async (resource, params) => {
let newParams = params;
if (resource === "manufacturers") {
newParams.filter = flat(params.filter);
}
return baseDataProvider.getList(resource, newParams);
},
};

Trouble configuring CKEditor downcast for custom element

I have a custom Media Library which I need to integrate with CKEditor 5. I've managed to write a plugin for adding images and another for adding download links for documents, but I can't get the downcast conversion for the download links to work properly.
I am currently able to insert the following into the content of the CKEditor window:
<a class="downloadLink" data-file-id="3" download="pretty-file-name-for-download.pdf" href="actual_file_name.pdf">Download link text</a>
and when I save my content this gets written to the database - so far so good.
When I reload the content into the editor, it comes back formatted differently:
<a href="actual_file_name.pdf">
<a class="downloadLink" data-file-id="3" download="pretty-file-name-for-download.pdf" href="actual_file_name.pdf">Download link text</a>
</a>
There is now an additional anchor tag wrapping the original anchor tag.
My downcast and upcast converters look like this:
conversion.for('downcast').elementToElement( {
model: {
name: 'downloadLink',
attributes: ['fileId', 'fileUrl', 'fileDownloadName', 'title']
},
view: (modelElement, conversionApi) => {
const viewWriter = conversionApi.writer;
return viewWriter.createContainerElement(
'a',
{
'data-file-id': modelElement.getAttribute('fileId'),
'download': modelElement.getAttribute('fileDownloadName'),
'href': modelElement.getAttribute('fileUrl'),
'class': 'downloadLink'
},
viewWriter.createText(modelElement.getAttribute('title'))
);
}
} );
conversion.for('upcast').elementToElement( {
view: {
name: 'a',
classes: 'downloadLink'
},
model: (viewElement, conversionApi) => {
const modelWriter = conversionApi.writer;
return modelWriter.createElement(
'downloadLink',
{
fileId: viewElement.getAttribute('data-file-id'),
fileUrl: viewElement.getAttribute('href'),
fileDownloadName: viewElement.getAttribute('download'),
title: viewElement.getChild(0).data,
}
);
}
} );
If anyone has any suggestions about what I'm doing wrong I'd appreciate the help!

Make react-select 2.0.0 <Async> work with redux-form <Field>

react-select just upgraded to 2.0.0 so google results on the first three pages are all about older versions, even the official document, and none of them helped.
My select box can show all options correctly, but redux form won't pick up the value, with the warning: Warning: A component is changing a controlled input of type hidden to be uncontrolled.
I wonder what have I missed here...
Form component:
<Field
name="residentialAddress"
label = "Residential Address"
type="select"
component={AddressField}
validate={required}
/>
Component
export class AddressField extends Component {
searchAddress = input => {
let options = []
return myPromise(input)
.then(suggestions => {
options = suggestions.map(suggestion =>
({
label: suggestion.label,
data: suggestion.value
})
)
return options;
}
).catch(
error => {
return options = [{ label: "Auto fetching failed, please enter your address manually", value: "", isDisabled: true }];
}
);
};
render() {
const {
input,
label,
meta: { touched, error },
type
} = this.props;
return(
<FormGroup>
<ControlLabel>{label}</ControlLabel>
<Async
{...input}
placeholder={label}
isClearable={true}
getOptionValue={(option) => option.residentialAddress}
onChange = { value => input.onChange(value.data) }
loadOptions={this.searchAddress}
/>
{ touched && error && <span>{error}</span> }
</FormGroup>
)
}
Solution: Simply remove the {...input} in <Async>.
Unlike regular custom Field component where we need to pass in {input}, the react-select Async component seems to take care of itself very well and doesn't require any intervene. Someone may explain it in a more professional way perhaps...
Also worth mention for those who come across this question:
loadOptions with promise used to require object {options: options} as return type. Now it changes to just array (options as in my code). But I didn't find any document that mentions this one.
Hope this could help.

Split string from Redux-Form Field into multiple data points

I'm currently working on a project that uses redux-form fields. We make use of the react-google-autocomplete component to allow users to enter an address in a similar fashion to if they were typing it in Google Maps.
Currently, we strip out the name of the location (if there is one) and just store the address. (So if I typed in "The White House", and selected the suggestion of "The White House, Pennsylvania Avenue Northwest, Washington, DC", we actually end up storing "1600 Pennsylvania Avenue Northwest, Washington, DC".
import React, { PropTypes } from 'react';
import { Field } from 'redux-form';
import Autocomplete from 'react-google-autocomplete';
function emulateTabPress(currentEl) {
const formEls = Array.from(currentEl.form.elements);
const currentIdx = formEls.findIndex(el => el === currentEl);
formEls[currentIdx + 1].focus();
}
function getFormattedGoogleAddress(googleParams) {
return googleParams.formatted_address || googleParams.name;
}
function renderGoogleAutoComplete(props) {
return (
<Autocomplete
type="text"
name="location"
onPlaceSelected={
param => props.input.onChange(getFormattedGoogleAddress(param))
}
types={[]}
value={props.input.value}
onChange={newValue => props.input.onChange(newValue)}
onKeyPress={event => {
if (event.key === 'Enter') {
event.preventDefault();
emulateTabPress(event.target);
}
}}
/>
);
}
renderGoogleAutoComplete.propTypes = {
input: PropTypes.shape({
value: PropTypes.string,
onChange: PropTypes.func
})
};
function AutocompleteLocation({ name, required }) {
return (
<Field
name={name}
required={required}
component={renderGoogleAutoComplete}
/>
);
}
AutocompleteLocation.propTypes = {
name: PropTypes.string.isRequired,
required: PropTypes.bool
};
export default AutocompleteLocation;
What I WANT to do, is store three separate pieces of information
The address (googleParam.formatted_address)
The name of the location (googleParam.name)
The Google ID for the location (googleParam.id)
I've written code that stores this as an object and used that as the value in my component, but then when I try to use any of the values from the store later on, it just shows as "object object"
Any suggestions on how to get these values into discrete data elements?

Resources