Browse Source

better ux linting

pull/183/head
Dash8 6 months ago
parent
commit
ff7a5eaa85
  1. 182
      src/components/CommunityCombobox.tsx
  2. 2
      src/components/main.tsx
  3. 2
      src/components/navbar.tsx
  4. 2
      src/components/post-form.tsx
  5. 113
      src/components/site-form.tsx
  6. 4
      src/index.tsx

182
src/components/CommunityCombobox.tsx

@ -1,98 +1,98 @@
import { useState } from "react";
import { Community } from "../interfaces";
import { useState } from 'react';
import { Community } from '../interfaces';
import matchSorter from 'match-sorter';
import {
Combobox,
ComboboxInput,
ComboboxPopover,
ComboboxList,
ComboboxOption,
} from '@reach/combobox';
import '@reach/combobox/styles.css';
import React from "react";
import { Alert, Input } from "theme-ui";
import { i18n } from "../i18next";
Combobox,
ComboboxInput,
ComboboxPopover,
ComboboxList,
ComboboxOption,
} from '@reach/combobox';
import '@reach/combobox/styles.css';
import React from 'react';
import { Alert, Input } from 'theme-ui';
import { i18n } from '../i18next';
export function CommunityCombobox({
communities,
onSelect,
initialValue,
validation,
}: {
communities: Array<Community>;
onSelect: (community_id: number) => void;
initialValue?: string;
validation?: boolean;
}) {
const [value, setValue] = useState(initialValue ?? '');
const [visited, setVisited] = useState(false);
const results = matchSorter(communities, value, {
keys: [(item: Community) => `${item.name}`],
});
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
setValue(e.target.value);
}
function validateInput() {
// if the current value does not equal a valid community, clear it
if (
!communities.some(
community => community.name.toLowerCase() === value.toLowerCase()
)
) {
setValue('');
}
communities,
onSelect,
initialValue,
validation,
}: {
communities: Array<Community>;
onSelect: (community_id: number) => void;
initialValue?: string;
validation?: boolean;
}) {
const [value, setValue] = useState(initialValue ?? '');
const [visited, setVisited] = useState(false);
const results = matchSorter(communities, value, {
keys: [(item: Community) => `${item.name}`],
});
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
setValue(e.target.value);
}
function validateInput() {
// if the current value does not equal a valid community, clear it
if (
!communities.some(
community => community.name.toLowerCase() === value.toLowerCase()
)
) {
setValue('');
}
const invalidCommunity = !communities.some(
community => community.name.toLowerCase() === value.toLowerCase()
);
return (
<>
<Combobox
aria-label="Communities"
openOnFocus
onSelect={name => {
const community = communities.find(comm => comm.name === name);
setValue(community.name);
onSelect(community.id);
}}
onBlur={() => setVisited(true)}
}
const invalidCommunity = !communities.some(
community => community.name.toLowerCase() === value.toLowerCase()
);
return (
<>
<Combobox
aria-label="Communities"
openOnFocus
onSelect={name => {
const community = communities.find(comm => comm.name === name);
setValue(community.name);
onSelect(community.id);
}}
onBlur={() => setVisited(true)}
>
<ComboboxInput
as={Input}
className="community-search-input"
onChange={handleChange}
value={value}
placeholder={i18n.t('select_a_community')}
/>
<ComboboxPopover
className="shadow-popup"
style={{ maxHeight: '33vh', overflowY: 'scroll' }}
>
<ComboboxInput
as={Input}
className="community-search-input"
onChange={handleChange}
value={value}
placeholder={i18n.t('select_a_community')}
/>
<ComboboxPopover
className="shadow-popup"
style={{ maxHeight: '33vh', overflowY: 'scroll' }}
>
{results.length > 0 ? (
<ComboboxList persistSelection>
{results.map((result, index) => (
<ComboboxOption key={index} value={result.name} />
))}
</ComboboxList>
) : (
<div
data-reach-combobox-popover
style={{ fontSize: '16px', padding: 8, paddingTop: 0 }}
>
No results found
</div>
)}
</ComboboxPopover>
</Combobox>
{visited && invalidCommunity && validation && (
<Alert mt={2}>Please enter a valid community</Alert>
)}
</>
);
}
{results.length > 0 ? (
<ComboboxList persistSelection>
{results.map((result, index) => (
<ComboboxOption key={index} value={result.name} />
))}
</ComboboxList>
) : (
<div
data-reach-combobox-popover
style={{ fontSize: '16px', padding: 8, paddingTop: 0 }}
>
No results found
</div>
)}
</ComboboxPopover>
</Combobox>
{visited && invalidCommunity && validation && (
<Alert mt={2}>Please enter a valid community</Alert>
)}
</>
);
}

2
src/components/main.tsx

@ -115,7 +115,7 @@ function Main(props) {
enable_create_communities: null,
open_registration: null,
enable_nsfw: null,
autosubscribe_comms: []
autosubscribe_comms: [],
},
admins: [],
sitemods: [],

2
src/components/navbar.tsx

@ -144,7 +144,7 @@ class UnwrappedNavbar extends Component<any, NavbarState> {
let query = new URLSearchParams(this.props.location.search);
this.setState({
communityDropdownShown: query.get('communities') === 'true',
})
});
}
}

2
src/components/post-form.tsx

@ -405,7 +405,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
initialValue={this.props.params?.community}
onSelect={this.handlePostCommunityChange}
communities={communities}
validation={true}
validation
/>
</div>
</div>

113
src/components/site-form.tsx

@ -2,7 +2,13 @@ import React, { Component } from 'react';
import { Prompt } from 'react-router-dom';
import isEqual from 'lodash.isequal';
import { MarkdownTextArea } from './markdown-textarea';
import { Community, ListCommunitiesResponse, Site, SiteForm as SiteFormI, SortType } from '../interfaces';
import {
Community,
ListCommunitiesResponse,
Site,
SiteForm as SiteFormI,
SortType,
} from '../interfaces';
import { WebSocketService } from '../services';
import { api, capitalizeFirstLetter, randomStr } from '../utils';
import { i18n } from '../i18next';
@ -18,7 +24,7 @@ interface SiteFormProps {
interface SiteFormState {
siteForm: SiteFormI;
communities: Array<Community>
communities: Array<Community>;
loading: boolean;
}
@ -41,34 +47,36 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
componentDidMount() {
if (this.props.site) {
this.setState({
siteForm: {
name: this.props.site.name,
description: this.props.site.description,
enable_downvotes: this.props.site.enable_downvotes,
enable_create_communities: this.props.site.enable_create_communities,
open_registration: this.props.site.open_registration,
enable_nsfw: this.props.site.enable_nsfw,
autosubscribe_comms: this.props.site.autosubscribe_comms,
this.setState(
{
siteForm: {
name: this.props.site.name,
description: this.props.site.description,
enable_downvotes: this.props.site.enable_downvotes,
enable_create_communities: this.props.site
.enable_create_communities,
open_registration: this.props.site.open_registration,
enable_nsfw: this.props.site.enable_nsfw,
autosubscribe_comms: this.props.site.autosubscribe_comms,
},
loading: false,
},
loading: false,
},
() => {
const params = new URLSearchParams({
sort: SortType[SortType.Hot],
limit: 9999,
} as any);
api
.get<ListCommunitiesResponse>(`community/list?${params.toString()}`)
.then(res => {
const data = res.data;
this.setState(
{
() => {
const params = new URLSearchParams({
sort: SortType[SortType.Hot],
limit: 9999,
} as any);
api
.get<ListCommunitiesResponse>(`community/list?${params.toString()}`)
.then(res => {
const data = res.data;
this.setState({
communities: data.communities,
},
)});
});
});
});
}
);
}
}
@ -225,23 +233,30 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
<div className="form-group row">
<div className="col-12">
<Card>
<Heading as='h5' className="mb-2">
<Heading as="h5" className="mb-2">
Default Communities
</Heading>
<CommunityCombobox
communities={this.state.communities}
onSelect={this.handleSiteCommunityComboboxChange}
/>
{this.state.communities.length > 0 && this.state.siteForm.autosubscribe_comms.map(comm => (
<div className="mt-2">
{this.state.communities.find(c => c.id == comm).name}
<button className = "btn" style={{marginLeft: -7, marginTop: -7}} onClick={() => this.handleSiteDefaultCommunityRemove(comm)}>
<svg className="icon icon-inline ml-1">
<use xlinkHref="#icon-cancel" />
</svg>
</button>
</div>
))}
{this.state.communities.length > 0 &&
this.state.siteForm.autosubscribe_comms.map(comm => (
<div className="mt-2" key={comm}>
{this.state.communities.find(c => c.id == comm).name}
<button
className="btn"
style={{ marginLeft: -7, marginTop: -7 }}
onClick={() =>
this.handleSiteDefaultCommunityRemove(comm)
}
>
<svg className="icon icon-inline ml-1">
<use xlinkHref="#icon-cancel" />
</svg>
</button>
</div>
))}
</Card>
</div>
</div>
@ -342,21 +357,23 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
...this.state.siteForm,
autosubscribe_comms: [
...this.state.siteForm.autosubscribe_comms,
community
]
}
})
}
community,
],
},
});
};
handleSiteDefaultCommunityRemove = (community: number) => {
this.setState({
siteForm: {
...this.state.siteForm,
//we don't use splice() because it doesn't return the array
autosubscribe_comms: this.state.siteForm.autosubscribe_comms.filter(comm => comm != community),
}
})
}
autosubscribe_comms: this.state.siteForm.autosubscribe_comms.filter(
comm => comm != community
),
},
});
};
handleCancel = () => {
this.props.onCancel();

4
src/index.tsx

@ -74,7 +74,9 @@ class Index extends Component<any, any> {
component={CreatePrivateMessage}
/>
<Route path="/communities">
<Redirect to={{pathname: "/", search: "?communities=true"}}/>
<Redirect
to={{ pathname: '/', search: '?communities=true' }}
/>
</Route>
<Route
path="/post/:id/comment/:comment_id"

Loading…
Cancel
Save