@imerss/inat-components 1.1.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/PUBLISH.md +13 -0
  2. package/README.md +20 -0
  3. package/dist/index.d.ts +118 -73
  4. package/dist/index.js +322 -389
  5. package/dist/standalone.min.js +3 -0
  6. package/package.json +17 -5
  7. package/public/standalone.html +55 -0
  8. package/rollup.config.js +15 -4
  9. package/src/__shared/api/common-taxa.ts +3 -3
  10. package/src/__shared/api/favourites.ts +6 -11
  11. package/src/__shared/api/recent-observations.ts +3 -3
  12. package/src/__shared/api/summary.ts +1 -0
  13. package/src/__shared/constants.ts +3 -1
  14. package/src/__shared/index.ts +1 -1
  15. package/src/__shared/typings.ts +58 -44
  16. package/src/__shared/utils/configUtils.ts +10 -6
  17. package/src/__shared/utils/dateUtils.ts +1 -1
  18. package/src/components/common-taxa/common-taxa.tsx +34 -60
  19. package/src/components/error/error.tsx +1 -1
  20. package/src/components/favourites/favourites.tsx +35 -60
  21. package/src/components/hooks/hooks.tsx +20 -10
  22. package/src/components/loader/loader.tsx +1 -1
  23. package/src/components/observation/observation.tsx +5 -4
  24. package/src/components/recent-observations/recent-observations.tsx +43 -60
  25. package/src/components/shared/css/general.module.scss +5 -1
  26. package/src/components/summary/ObserverList.tsx +21 -15
  27. package/src/components/summary/summary.tsx +59 -51
  28. package/src/components/tabs/tabs.tsx +48 -27
  29. package/src/components/taxon-panel/taxon-panel.tsx +83 -77
  30. package/src/components/year-dropdown/year-dropdown.tsx +11 -7
  31. package/src/hooks/useLoadSourceData.ts +81 -0
  32. package/src/index.ts +1 -1
  33. package/src/types/index.d.ts +17 -0
  34. package/src/types/standalone.d.ts +6 -0
@@ -1,20 +1,21 @@
1
- import React, {useEffect, useState} from "react";
2
- import {formatDate, C, getFavourites, FavouritesData, BaseComponentProps, DataSource} from "../../__shared";
3
- import {Observation} from "../observation/observation";
1
+ import React from "react";
2
+ import { formatDate, C, getFavourites, FavouritesData, BaseComponentProps, GeneralClasses } from "../../__shared";
3
+ import { Observation } from "../observation/observation";
4
4
  import Loader from "../loader/loader";
5
- import {NoResults} from "../no-results/no-results";
5
+ import { NoResults } from "../no-results/no-results";
6
+ import { useLoadSourceData } from "../../hooks/useLoadSourceData";
6
7
  import generalStyles from "../shared/css/general.module.scss";
7
8
 
8
9
  export type FavouritesProps = BaseComponentProps & {
9
- year: string | number;
10
+ year: number | string;
10
11
  }
11
12
 
12
- export const FavouritesLabel = (data: FavouritesData) => (
13
+ export const FavouritesLabel = (data: FavouritesData & { classes: GeneralClasses }) => (
13
14
  <div className={generalStyles.obsLabel}>
14
- <h3>{data.taxonCommonName || data.taxonName}</h3>
15
- <div>{data.observerUsername}</div>
16
- <div>{formatDate(data.obsDate)}</div>
17
- <label className={generalStyles.count}>{data.numFaves}</label>
15
+ <h3 className={data.classes?.observationLabelTitle}>{data.taxonCommonName || data.taxonName}</h3>
16
+ <div className={data.classes?.observationLabelTitle}>{data.observerUsername}</div>
17
+ <div className={data.classes?.observationLabelDate}>{formatDate(data.obsDate)}</div>
18
+ <label className={`${data.classes?.observationCount || ''} ${generalStyles.count}`}>{data.numFaves}</label>
18
19
  </div>
19
20
  );
20
21
 
@@ -23,73 +24,47 @@ export const Favourites = ({
23
24
  source,
24
25
  taxonId,
25
26
  placeId,
26
- data,
27
27
  dataUrl,
28
28
  components,
29
29
  className,
30
- perPage = C.PER_PAGE
30
+ tabDesc,
31
+ generalClasses,
32
+ itemWidth = C.DEFAULT_ITEM_WIDTH,
33
+ numResults = C.NUM_RESULTS
31
34
  }: FavouritesProps) => {
32
- const [observations, setObservations] = useState<any>(() => (source === DataSource.dataProp) ? data : []);
33
- const [loading, setLoading] = useState(false);
34
-
35
- useEffect(() => {
36
- if (source !== DataSource.autoLoad) {
37
- return;
38
- }
39
- if (!taxonId) {
40
- console.error("Please supply a `taxonId` prop for the `autoLoad` source prop option.");
41
- return;
42
- }
43
- if (!placeId) {
44
- console.error("Please supply a `placeId` prop for the `autoLoad` source prop option.");
45
- return;
46
- }
47
-
48
- (async () => {
49
- setLoading(true);
50
- const resp = await getFavourites({taxonId, placeId, year, perPage});
51
- setObservations(resp.results);
52
- setLoading(false);
53
- })();
54
- }, [source, year, placeId, taxonId, perPage]);
55
-
56
- useEffect(() => {
57
- if (source !== DataSource.url) {
58
- return;
59
- }
60
-
61
- if (!dataUrl) {
62
- console.error("Please supply a `dataUrl` prop for the `url` source prop option.");
63
- return;
64
- }
65
-
66
- (async () => {
67
- setLoading(true);
68
- const obs = await fetch(dataUrl);
69
- const json = await obs.json();
70
- setObservations(json.results);
71
- setLoading(false);
72
- })();
73
- }, [source, dataUrl]);
74
-
35
+ const { loading, results: observations } = useLoadSourceData({
36
+ year,
37
+ taxonId,
38
+ placeId,
39
+ numResults,
40
+ dataUrl,
41
+ source,
42
+ action: getFavourites
43
+ });
75
44
  const Load = components?.loader ? components.loader as any : Loader;
76
45
  const Label = components?.label ? components.label as any : FavouritesLabel;
77
46
 
78
- let classes = generalStyles.panel;
47
+ let elClasses = generalStyles.panel;
79
48
  if (className) {
80
- classes += ` ${className}`;
49
+ elClasses += ` ${className}`;
50
+ }
51
+ let descClasses = generalStyles.tabDesc;
52
+ if (generalClasses?.tabDesc) {
53
+ descClasses += ` ${generalClasses.tabDesc}`;
81
54
  }
82
55
  return (
83
- <div className={classes}>
56
+ <div className={elClasses}>
57
+ {tabDesc && <p className={descClasses}>{tabDesc}</p>}
84
58
  <Load loading={loading}/>
85
59
  {!loading && observations.length === 0 && <NoResults/>}
86
- <div className={generalStyles.grid}>
60
+ <div className={generalStyles.grid} style={{ gridTemplateColumns: `repeat(auto-fit, minmax(${itemWidth}px, 1fr))` }}>
87
61
  {observations.map((obs: any) => (
88
62
  <Observation
89
63
  key={obs.id}
90
64
  imageUrl={obs.imageUrl.replace(/square/, "medium")}
65
+ itemWidth={itemWidth}
91
66
  linkUrl={obs.obsUrl}>
92
- <Label {...obs} />
67
+ <Label {...obs} classes={generalClasses} />
93
68
  </Observation>
94
69
  ))}
95
70
  </div>
@@ -1,11 +1,11 @@
1
- import {useEffect, useState} from "react";
2
- import {Tab} from "../../__shared";
1
+ import { useEffect, useState } from "react";
2
+ import { Feature, TaxonPanelFeatures } from "../../__shared";
3
3
 
4
4
  const defaultTitles = {
5
- [Tab.recent]: "Recent",
6
- [Tab.commonTaxa]: "Most common",
7
- [Tab.favourites]: "Most favourited",
8
- [Tab.stats]: "General stats"
5
+ [Feature.recentObservations]: "Recent",
6
+ [Feature.commonTaxa]: "Most common",
7
+ [Feature.favourites]: "Most favourited",
8
+ [Feature.stats]: "General stats"
9
9
  };
10
10
 
11
11
  export const useFeatureTitles = (features: any) => {
@@ -13,12 +13,22 @@ export const useFeatureTitles = (features: any) => {
13
13
 
14
14
  useEffect(() => {
15
15
  setTitles({
16
- [Tab.recent]: features.recentObservations?.label || defaultTitles[Tab.recent],
17
- [Tab.commonTaxa]: features.commonTaxa?.label || defaultTitles[Tab.commonTaxa],
18
- [Tab.favourites]: features.favourites?.label || defaultTitles[Tab.favourites],
19
- [Tab.stats]: features.stats?.label || defaultTitles[Tab.stats]
16
+ [Feature.recentObservations]: features.recentObservations?.title || defaultTitles[Feature.recentObservations],
17
+ [Feature.commonTaxa]: features.commonTaxa?.title || defaultTitles[Feature.commonTaxa],
18
+ [Feature.favourites]: features.favourites?.title || defaultTitles[Feature.favourites],
19
+ [Feature.stats]: features.stats?.title || defaultTitles[Feature.stats]
20
20
  });
21
21
  }, [features]);
22
22
 
23
23
  return titles;
24
24
  };
25
+
26
+ export const convertFeaturesToObj = (features: TaxonPanelFeatures) => {
27
+ const featuresObj: any = {};
28
+ features.forEach((row) => {
29
+ const { feature, ...other } = row;
30
+ featuresObj[feature] = other;
31
+ });
32
+
33
+ return featuresObj;
34
+ }
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import LoadingSpinner from "react-spinners/MoonLoader.js"; // TODO. Interesting. Why is this required?
2
+ import LoadingSpinner from "react-spinners/MoonLoader.js";
3
3
  import styles from "../shared/css/general.module.scss";
4
4
 
5
5
  export type LoaderProps = {
@@ -1,13 +1,14 @@
1
- import React, {useCallback} from 'react';
1
+ import React, { useCallback } from "react";
2
2
  import styles from "./observation.module.scss";
3
3
 
4
4
  export type ObservationProps = {
5
5
  imageUrl: string;
6
6
  linkUrl: string;
7
+ itemWidth: number;
7
8
  children: any;
8
9
  };
9
10
 
10
- export const Observation = ({imageUrl, linkUrl, children}: ObservationProps) => {
11
+ export const Observation = ({imageUrl, linkUrl, itemWidth, children}: ObservationProps) => {
11
12
  const gotoObservation = useCallback(() => {
12
13
  window.open(linkUrl);
13
14
  }, [linkUrl]);
@@ -15,8 +16,8 @@ export const Observation = ({imageUrl, linkUrl, children}: ObservationProps) =>
15
16
  return (
16
17
  <article className={styles.obs} onClick={gotoObservation}>
17
18
  <div style={{
18
- width: 200,
19
- height: 200,
19
+ width: itemWidth,
20
+ height: itemWidth,
20
21
  overflow: "hidden",
21
22
  borderRadius: 5,
22
23
  background: `url(${imageUrl}) 50% 50% no-repeat`,
@@ -1,17 +1,26 @@
1
- import React, {useEffect, useState} from "react";
2
- import {formatDate, C, getRecentObservations, RecentObservationData, BaseComponentProps, DataSource} from "../../__shared";
3
- import {Observation} from "../observation/observation";
1
+ import React from "react";
2
+ import {
3
+ formatDate,
4
+ C,
5
+ getRecentObservations,
6
+ RecentObservationData,
7
+ BaseComponentProps,
8
+ DataSourceEnum,
9
+ GeneralClasses
10
+ } from "../../__shared";
11
+ import { Observation } from "../observation/observation";
4
12
  import Loader from "../loader/loader";
5
- import {NoResults} from "../no-results/no-results";
13
+ import { NoResults } from "../no-results/no-results";
14
+ import { useLoadSourceData } from "../../hooks/useLoadSourceData";
6
15
  import styles from "../shared/css/general.module.scss";
7
16
 
8
17
  export type RecentObservationsProps = BaseComponentProps;
9
18
 
10
- export const RecentObservationLabel = (obs: RecentObservationData) => (
19
+ export const RecentObservationLabel = (obs: RecentObservationData & { classes: GeneralClasses }) => (
11
20
  <div className={styles.obsLabel}>
12
- <h3>{obs.taxonCommonName || obs.taxonName}</h3>
13
- <div>{formatDate(obs.obsDate)}</div>
14
- <div>
21
+ <h3 className={obs.classes?.observationLabelTitle}>{obs.taxonCommonName || obs.taxonName}</h3>
22
+ <div className={obs.classes?.observationLabelDate}>{formatDate(obs.obsDate)}</div>
23
+ <div className={obs.classes?.observationLabelName}>
15
24
  <a href={`${C.BASE_URL}/people/${obs.observerUsername}`}
16
25
  target="_blank"
17
26
  rel="noreferrer"
@@ -25,74 +34,48 @@ export const RecentObservationLabel = (obs: RecentObservationData) => (
25
34
  export const RecentObservations = ({
26
35
  taxonId,
27
36
  placeId,
28
- data,
29
37
  dataUrl,
30
- source = DataSource.autoLoad,
31
- perPage = C.PER_PAGE,
32
38
  components,
33
- className
39
+ className,
40
+ generalClasses,
41
+ tabDesc,
42
+ source = DataSourceEnum.autoLoad,
43
+ numResults = C.NUM_RESULTS,
44
+ itemWidth = C.DEFAULT_ITEM_WIDTH
34
45
  }: RecentObservationsProps) => {
35
- const [loading, setLoading] = useState(false);
36
- const [observations, setObservations] = useState<any>(() => (source === DataSource.dataProp) ? data : []);
37
-
38
- useEffect(() => {
39
- if (source !== DataSource.autoLoad) {
40
- return;
41
- }
42
- if (!taxonId) {
43
- console.error("Please supply a `taxonId` prop for the `autoLoad` source prop option.");
44
- return;
45
- }
46
- if (!placeId) {
47
- console.error("Please supply a `placeId` prop for the `autoLoad` source prop option.");
48
- return;
49
- }
50
-
51
- (async () => {
52
- setLoading(true);
53
- const obs = await getRecentObservations({taxonId, placeId, perPage});
54
- setObservations(obs.results);
55
- setLoading(false);
56
- })();
57
- }, [source, taxonId, placeId, perPage]);
58
-
59
- useEffect(() => {
60
- if (source !== DataSource.url) {
61
- return;
62
- }
63
-
64
- if (!dataUrl) {
65
- console.error("Please supply a `dataUrl` prop for the `url` source prop option.");
66
- return;
67
- }
68
-
69
- (async () => {
70
- setLoading(true);
71
- const obs = await fetch(dataUrl);
72
- const json = await obs.json();
73
- setObservations(json.results);
74
- setLoading(false);
75
- })();
76
- }, [source, dataUrl]);
77
-
46
+ const { loading, results: observations } = useLoadSourceData({
47
+ taxonId,
48
+ placeId,
49
+ numResults,
50
+ dataUrl,
51
+ source,
52
+ action: getRecentObservations
53
+ });
78
54
  const Load = components?.loader ? components.loader as any : Loader;
79
55
  const Label = components?.label ? components.label as any : RecentObservationLabel;
80
56
 
81
- let classes = styles.panel;
57
+ let componentClasses = styles.panel;
82
58
  if (className) {
83
- classes += ` ${className}`;
59
+ componentClasses += ` ${className}`;
84
60
  }
61
+ let descClasses = styles.tabDesc;
62
+ if (generalClasses?.tabDesc) {
63
+ descClasses += ` ${generalClasses.tabDesc}`;
64
+ }
65
+
85
66
  return (
86
- <div className={classes}>
67
+ <div className={componentClasses}>
68
+ {tabDesc && <p className={descClasses}>{tabDesc}</p>}
87
69
  <Load loading={loading}/>
88
- <div className={styles.grid}>
70
+ <div className={styles.grid} style={{ gridTemplateColumns: `repeat(auto-fit, minmax(${itemWidth}px, 1fr))` }}>
89
71
  {!loading && observations.length === 0 && <NoResults/>}
90
72
  {observations.map((obs: RecentObservationData) => (
91
73
  <Observation
92
74
  key={obs.id}
93
75
  imageUrl={obs.imageUrl.replace(/square/, "medium")}
76
+ itemWidth={itemWidth}
94
77
  linkUrl={obs.obsUrl}>
95
- <Label {...obs} />
78
+ <Label {...obs} classes={generalClasses} />
96
79
  </Observation>
97
80
  ))}
98
81
  </div>
@@ -16,7 +16,6 @@
16
16
 
17
17
  .grid {
18
18
  display: grid;
19
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr) );
20
19
  width: 100%;
21
20
  grid-gap: 8px;
22
21
  text-align: center;
@@ -67,3 +66,8 @@
67
66
  font-weight: bold;
68
67
  font-size: 18px;
69
68
  }
69
+
70
+ .tabDesc {
71
+ font-size: 14px;
72
+ margin-bottom: 20px;
73
+ }
@@ -1,18 +1,24 @@
1
1
  import React from "react";
2
- import {numberWithCommas, Observer, C} from "../../__shared";
2
+ import { numberWithCommas, Observer, C, GeneralClasses } from "../../__shared";
3
3
  import styles from "./summary.module.scss";
4
4
 
5
- export const ObserverList = ({observers}: { observers: Observer[] }) => (
6
- <ul className={styles.top}>
7
- {observers.map((obs) => (
8
- <li key={obs.id}>
9
- <a href={`${C.BASE_URL}/people/${obs.userName}`} target="_blank" rel="noreferrer">
10
- {obs.iconUrl ? <img src={obs.iconUrl || ""} className={styles.avatar} alt="User icon"/> :
11
- <div className={styles.noAvatar}/>}
12
- <h3>{obs.userName}</h3>
13
- <label>{numberWithCommas(obs.numObservations)}</label>
14
- </a>
15
- </li>
16
- ))}
17
- </ul>
18
- );
5
+ export const ObserverList = ({ observers, generalClasses, className = '' }: { observers: Observer[], generalClasses?: GeneralClasses, className?: string }) => {
6
+ let classes = styles.top;
7
+ if (className) {
8
+ classes += ` ${className}`;
9
+ }
10
+ return (
11
+ <ul className={classes}>
12
+ {observers.map((obs) => (
13
+ <li key={obs.id}>
14
+ <a href={`${C.BASE_URL}/people/${obs.userName}`} target="_blank" rel="noreferrer">
15
+ {obs.iconUrl ? <img src={obs.iconUrl || ""} className={styles.avatar} alt="User icon"/> :
16
+ <div className={styles.noAvatar}/>}
17
+ <h3>{obs.userName}</h3>
18
+ <label className={generalClasses?.observationCount}>{numberWithCommas(obs.numObservations)}</label>
19
+ </a>
20
+ </li>
21
+ ))}
22
+ </ul>
23
+ );
24
+ };
@@ -1,83 +1,91 @@
1
- import React, {useEffect, useState} from "react";
2
- import {numberWithCommas, DataSource, getSummary, FavouritesRespData} from "../../__shared";
1
+ import React from "react";
2
+ import { numberWithCommas, DataSource, getSummary, GeneralClasses } from "../../__shared";
3
3
  import { ObserverList } from './ObserverList';
4
4
  import Loader from "../loader/loader";
5
- import {SeasonalityGraph} from "../seasonality-graph/seasonality-graph";
5
+ import { SeasonalityGraph } from "../seasonality-graph/seasonality-graph";
6
6
  import styles from "./summary.module.scss";
7
+ import { useLoadSourceData } from "../../hooks/useLoadSourceData";
7
8
 
8
9
  export type SummaryProps = {
9
- source: DataSource;
10
10
  taxonId: number;
11
11
  placeId: number;
12
- year: string | number;
13
- data?: FavouritesRespData; /// TODO
12
+ year: number;
13
+ source: DataSource;
14
14
  dataUrl?: string;
15
+ generalClasses?: GeneralClasses;
16
+ className?: string;
17
+ tabDesc?: string;
18
+ observersListClass?: string;
19
+ statsCountSummaryClass?: string;
15
20
  };
16
21
 
17
- export const Summary = ({ source, data, dataUrl, taxonId, placeId, year }: SummaryProps) => {
18
- const [summaryData, setSummaryData] = useState<any>({});
19
- const [loading, setLoading] = useState(false);
20
-
21
- useEffect(() => {
22
- if (source !== DataSource.autoLoad) {
23
- return;
24
- }
25
- if (!taxonId) {
26
- console.error("Please supply a `taxonId` prop for the `autoLoad` source prop option.");
27
- return;
28
- }
29
- if (!placeId) {
30
- console.error("Please supply a `placeId` prop for the `autoLoad` source prop option.");
31
- return;
32
- }
22
+ export const Summary = ({
23
+ source,
24
+ dataUrl,
25
+ taxonId,
26
+ placeId,
27
+ year,
28
+ generalClasses,
29
+ className,
30
+ tabDesc,
31
+ observersListClass,
32
+ statsCountSummaryClass
33
+ }: SummaryProps) => {
34
+ const { loading, results: summaryData } = useLoadSourceData({
35
+ year,
36
+ taxonId,
37
+ placeId,
38
+ dataUrl,
39
+ source,
40
+ isSummaryData: true,
33
41
 
34
- (async () => {
35
- setLoading(true);
36
- const data = await getSummary({ taxonId, placeId, year });
37
- setSummaryData(data);
38
- setLoading(false);
39
- })();
40
- }, [source, year, placeId, taxonId]);
42
+ // shim to standardize the response from the `action` method with a top-level `results` property. This
43
+ // just lets the useLoadSourceData hook work for all tab components
44
+ action: async (data) => {
45
+ const results = await getSummary(data);
41
46
 
42
- useEffect(() => {
43
- if (source !== DataSource.url) {
44
- return;
47
+ return { results };
45
48
  }
49
+ });
46
50
 
47
- if (!dataUrl) {
48
- console.error("Please supply a `dataUrl` prop for the `url` source prop option.");
49
- return;
50
- }
51
+ let countSummaryClasses = styles.countSummary;
52
+ if (statsCountSummaryClass) {
53
+ countSummaryClasses += ` ${statsCountSummaryClass}`;
54
+ }
55
+ let descClasses = styles.tabDesc;
56
+ if (generalClasses?.tabDesc) {
57
+ descClasses += ` ${generalClasses.tabDesc}`;
58
+ }
51
59
 
52
- (async () => {
53
- setLoading(true);
54
- const obs = await fetch(dataUrl);
55
- const json = await obs.json();
56
- setSummaryData(json);
57
- setLoading(false);
58
- })();
59
- }, [source, dataUrl]);
60
+ let classList = styles.summaryPage;
61
+ if (className) {
62
+ classList += ` ${className}`;
63
+ }
60
64
 
61
65
  return (
62
- <div className={styles.summaryPage}>
66
+ <div className={classList}>
67
+ {tabDesc && <p className={descClasses}>{tabDesc}</p>}
63
68
  <Loader loading={loading} />
64
-
65
- <ul className={styles.countSummary}>
69
+ <ul className={countSummaryClasses}>
66
70
  <li>Total number of observers: <b>{summaryData.observers?.totalCount ? numberWithCommas(summaryData.observers.totalCount): ""}</b></li>
67
71
  <li>Total number of observations: <b>{summaryData.observations?.totalCount ? numberWithCommas(summaryData.observations.totalCount): ""}</b></li>
68
72
  </ul>
69
-
70
73
  <section>
71
74
  <div className={styles.seasonalityBlock}>
72
75
  <h1>Seasonality</h1>
73
76
  {summaryData.seasonalityData && <SeasonalityGraph data={summaryData.seasonalityData.monthOfYear} />}
74
77
  </div>
75
-
76
78
  <div className={styles.observersBlock}>
77
79
  <h1>Top observers</h1>
78
- {summaryData.observers?.top && <ObserverList observers={summaryData.observers.top} />}
80
+ {summaryData.observers?.top && (
81
+ <ObserverList
82
+ observers={summaryData.observers.top}
83
+ className={observersListClass}
84
+ generalClasses={generalClasses}
85
+ />
86
+ )}
79
87
  </div>
80
88
  </section>
81
89
  </div>
82
90
  );
83
- }
91
+ }
@@ -1,42 +1,63 @@
1
1
  import React from "react";
2
- import {Tab} from "../../__shared";
2
+ import { Feature, TaxonPanelFeatures } from "../../__shared";
3
3
  import styles from "./tabs.module.scss";
4
- import {useFeatureTitles} from "../hooks/hooks";
4
+ import { useFeatureTitles } from "../hooks/hooks";
5
5
 
6
6
  export type TabsProps = {
7
- selectedTab: Tab;
8
- onChangeTab: (tab: Tab) => void;
9
- features: any; // TODO
7
+ selectedTab: Feature;
8
+ onChangeTab: (tab: Feature) => void;
9
+ features: TaxonPanelFeatures;
10
+ className?: string;
10
11
  }
11
12
 
12
- const Tabs = ({ selectedTab, onChangeTab, features }: TabsProps) => {
13
+ const Tabs = ({ selectedTab, onChangeTab, features, className }: TabsProps) => {
13
14
  const titles = useFeatureTitles(features);
14
15
 
15
- return (
16
- <ul className={styles.tabs}>
17
- {features.recentObservations && (
18
- <li onClick={() => onChangeTab(Tab.recent)}
19
- className={selectedTab === Tab.recent ? styles.selected : ""}>
20
- {titles[Tab.recent]}
16
+ let classes = styles.tabs;
17
+ if (className) {
18
+ classes += ` ${className}`;
19
+ }
20
+
21
+ const items = features.map((item) => {
22
+ if (item.feature == Feature.recentObservations) {
23
+ return (
24
+ <li key={Feature.recentObservations}
25
+ onClick={() => onChangeTab(Feature.recentObservations)}
26
+ className={selectedTab === Feature.recentObservations ? styles.selected : ""}>
27
+ {titles[Feature.recentObservations]}
21
28
  </li>
22
- )}
23
- {features.commonTaxa && (
24
- <li onClick={() => onChangeTab(Tab.commonTaxa)}
25
- className={selectedTab === Tab.commonTaxa ? styles.selected : ""}>
26
- {titles[Tab.commonTaxa]}
29
+ );
30
+ } else if (item.feature == Feature.commonTaxa) {
31
+ return (
32
+ <li key={Feature.commonTaxa}
33
+ onClick={() => onChangeTab(Feature.commonTaxa)}
34
+ className={selectedTab === Feature.commonTaxa ? styles.selected : ""}>
35
+ {titles[Feature.commonTaxa]}
27
36
  </li>
28
- )}
29
- {features.favourites && (
30
- <li onClick={() => onChangeTab(Tab.favourites)}
31
- className={selectedTab === Tab.favourites ? styles.selected : ""}>
32
- {titles[Tab.favourites]}
37
+ );
38
+ } else if (item.feature == Feature.favourites) {
39
+ return (
40
+ <li key={Feature.favourites}
41
+ onClick={() => onChangeTab(Feature.favourites)}
42
+ className={selectedTab === Feature.favourites ? styles.selected : ""}>
43
+ {titles[Feature.favourites]}
33
44
  </li>
34
- )}
35
- {features.stats && (
36
- <li onClick={() => onChangeTab(Tab.stats)} className={selectedTab === Tab.stats ? styles.selected : ""}>
37
- {titles[Tab.stats]}
45
+ );
46
+ } else if (item.feature == Feature.stats) {
47
+ return (
48
+ <li key={Feature.stats} onClick={() => onChangeTab(Feature.stats)} className={selectedTab === Feature.stats ? styles.selected : ""}>
49
+ {titles[Feature.stats]}
38
50
  </li>
39
- )}
51
+ );
52
+ }
53
+
54
+ // possibly a user has included something wacky
55
+ return undefined;
56
+ });
57
+
58
+ return (
59
+ <ul className={classes}>
60
+ {items}
40
61
  </ul>
41
62
  );
42
63
  }