From 6af0194561ba1a70e368f8a2c2dcf8d6373b1895 Mon Sep 17 00:00:00 2001 From: barshathakuri Date: Mon, 25 Mar 2024 14:21:31 +0545 Subject: [PATCH] Add NavBar --- .gitignore | 2 +- package.json | 7 +- src/App/routes.tsx | 6 +- src/assets/icons/go-logo-2020.svg | 112 ++++++++++++++++++ src/components/DropdownMenuItem/index.tsx | 94 +++++++++++++++ .../Navbar/LanguageDropdown/index.tsx | 93 +++++++++++++++ .../Navbar/LanguageDropdown/styles.module.css | 8 ++ src/components/Navbar/i18n.json | 10 ++ src/components/Navbar/index.tsx | 73 ++++++++++++ src/components/Navbar/styles.module.css | 40 +++++++ src/hooks/useBooleanState.ts | 24 ++++ src/hooks/useDebouncedValue.ts | 34 ++++++ src/views/AlertMap/i18n.json | 6 + src/views/AlertMap/index.tsx | 31 +++++ src/views/AlertMap/styles.module.css | 9 ++ src/views/Home/i18n.json | 8 ++ src/views/Home/index.tsx | 24 +++- src/views/Home/styles.module.css | 7 ++ src/views/Root/index.tsx | 10 -- src/views/RootLayout/index.tsx | 38 ++++++ src/views/RootLayout/styles.module.css | 94 +++++++++++++++ tsconfig.json | 3 +- 22 files changed, 710 insertions(+), 23 deletions(-) create mode 100644 src/assets/icons/go-logo-2020.svg create mode 100644 src/components/DropdownMenuItem/index.tsx create mode 100644 src/components/Navbar/LanguageDropdown/index.tsx create mode 100644 src/components/Navbar/LanguageDropdown/styles.module.css create mode 100644 src/components/Navbar/i18n.json create mode 100644 src/components/Navbar/index.tsx create mode 100644 src/components/Navbar/styles.module.css create mode 100644 src/hooks/useBooleanState.ts create mode 100644 src/hooks/useDebouncedValue.ts create mode 100644 src/views/AlertMap/i18n.json create mode 100644 src/views/AlertMap/index.tsx create mode 100644 src/views/AlertMap/styles.module.css create mode 100644 src/views/Home/i18n.json create mode 100644 src/views/Home/styles.module.css delete mode 100644 src/views/Root/index.tsx create mode 100644 src/views/RootLayout/index.tsx create mode 100644 src/views/RootLayout/styles.module.css diff --git a/.gitignore b/.gitignore index 4f1c24c5..f3f3b0a2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* - +ui node_modules dist dist-ssr diff --git a/package.json b/package.json index 45fbf4c7..24060e19 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,12 @@ "@graphql-codegen/introspection": "^4.0.3", "@graphql-codegen/typescript-operations": "^4.2.0", "@ifrc-go/icons": "^1.3.3", - "@sentry/react": "^7.108.0", + "@ifrc-go/ui": "^1.0.0", + "@mapbox/mapbox-gl-draw": "^1.4.3", + "@sentry/react": "^7.81.1", "@togglecorp/fujs": "^2.1.1", - "@togglecorp/re-map": "^0.2.0-beta-6", + "@togglecorp/re-map": "^0.1.4", + "@turf/bbox": "^6.5.0", "graphql": "^16.8.1", "mapbox-gl": "^1.13.0", "react": "^18.2.0", diff --git a/src/App/routes.tsx b/src/App/routes.tsx index fb5b28ea..a3749bb9 100644 --- a/src/App/routes.tsx +++ b/src/App/routes.tsx @@ -1,12 +1,10 @@ -import type { +import { MyInputIndexRouteObject, MyInputNonIndexRouteObject, MyInputRouteObject, MyOutputIndexRouteObject, MyOutputNonIndexRouteObject, MyOutputRouteObject, -} from '#utils/routes'; -import { unwrapRoute, wrapRoute, } from '#utils/routes'; @@ -31,7 +29,7 @@ const myWrapRoute: MyWrapRoute = wrapRoute; const root = myWrapRoute({ title: '', path: '/', - component: () => import('#views/Root'), + component: () => import('#views/RootLayout'), componentProps: {}, errorElement: , }); diff --git a/src/assets/icons/go-logo-2020.svg b/src/assets/icons/go-logo-2020.svg new file mode 100644 index 00000000..aca6329b --- /dev/null +++ b/src/assets/icons/go-logo-2020.svg @@ -0,0 +1,112 @@ + + + +image/svg+xml + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/DropdownMenuItem/index.tsx b/src/components/DropdownMenuItem/index.tsx new file mode 100644 index 00000000..35f755ab --- /dev/null +++ b/src/components/DropdownMenuItem/index.tsx @@ -0,0 +1,94 @@ +import { + useCallback, + useContext, +} from 'react'; +import { LinkProps } from 'react-router-dom'; +import { + Button, + ButtonProps, + ConfirmButton, + ConfirmButtonProps, +} from '@ifrc-go/ui'; +import { DropdownMenuContext } from '@ifrc-go/ui/contexts'; +import { isDefined } from '@togglecorp/fujs'; + +type CommonProp = { + persist?: boolean; +} + +type ButtonTypeProps = Omit, 'type'> & { + type: 'button'; +} + +type LinkTypeProps = LinkProps & { + type: 'link'; +} + +type ConfirmButtonTypeProps = Omit, 'type'> & { + type: 'confirm-button', +} + +type Props = CommonProp & (ButtonTypeProps | LinkTypeProps | ConfirmButtonTypeProps); + +function DropdownMenuItem(props: Props) { + const { + type, + onClick, + persist = false, + } = props; + const { setShowDropdown } = useContext(DropdownMenuContext); + + const handleButtonClick = useCallback( + (name: NAME, e: React.MouseEvent) => { + if (!persist) { + setShowDropdown(false); + } + if (isDefined(onClick) && type !== 'link') { + onClick(name, e); + } + }, + [setShowDropdown, type, onClick, persist], + ); + + if (type === 'button') { + const { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + type: _, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + persist: __, + variant = 'dropdown-item', + ...otherProps + } = props; + + return ( + + + + + ); +} + +export default Navbar; diff --git a/src/components/Navbar/styles.module.css b/src/components/Navbar/styles.module.css new file mode 100644 index 00000000..821a044d --- /dev/null +++ b/src/components/Navbar/styles.module.css @@ -0,0 +1,40 @@ +.navbar { + border-bottom: var(--go-ui-width-separator-thin) solid var(--go-ui-color-separator); + background-color: var(--go-ui-color-white); + + .top { + border-bottom: var(--go-ui-width-separator-thin) solid var(--go-ui-color-primary-red); + + .top-content { + display: flex; + align-items: center; + gap: var(--go-ui-spacing-md) var(--go-ui-spacing-lg); + flex-wrap: wrap; + padding: var(--go-ui-spacing-md) var(--go-ui-spacing-lg); + + .brand { + display: flex; + align-items: top; + flex-grow: 1; + flex-wrap: wrap; + gap: var(--go-ui-spacing-sm); + + .go-icon { + height: var(--go-ui-height-brand-icon); + } + } + + .actions { + display: flex; + align-items: center; + flex-wrap: wrap; + + .action-item { + text-decoration: none; + font-weight: var(--go-ui-font-weight-medium); + color: var(--go-ui-color-text); + } + } + } + } +} diff --git a/src/hooks/useBooleanState.ts b/src/hooks/useBooleanState.ts new file mode 100644 index 00000000..3ebf05e6 --- /dev/null +++ b/src/hooks/useBooleanState.ts @@ -0,0 +1,24 @@ +import { + useCallback, + useState, +} from 'react'; + +function useBooleanState(defaultValue?: boolean) { + const [value, setValue] = useState(!!defaultValue); + + const setTrue = useCallback(() => setValue(true), []); + const setFalse = useCallback(() => setValue(false), []); + const toggle = useCallback(() => setValue((x) => !x), []); + + return [ + value, + { + setValue, + setTrue, + setFalse, + toggle, + }, + ] as const; +} + +export default useBooleanState; diff --git a/src/hooks/useDebouncedValue.ts b/src/hooks/useDebouncedValue.ts new file mode 100644 index 00000000..5cb59a4f --- /dev/null +++ b/src/hooks/useDebouncedValue.ts @@ -0,0 +1,34 @@ +import { + useEffect, + useState, +} from 'react'; + +function useDebouncedValue( + input: T, + debounceTime?: number, +): T +function useDebouncedValue( + input: T, + debounceTime: number | undefined, + transformer: (value: T) => V, +): V +function useDebouncedValue( + input: T, + debounceTime?: number, + transformer?: (value: T) => V, +) { + const [debounceValue, setDebouncedValue] = useState( + () => (transformer ? transformer(input) : input), + ); + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedValue(transformer ? transformer(input) : input); + }, debounceTime ?? 300); + return () => { + clearTimeout(handler); + }; + }, [input, debounceTime, transformer]); + return debounceValue; +} + +export default useDebouncedValue; diff --git a/src/views/AlertMap/i18n.json b/src/views/AlertMap/i18n.json new file mode 100644 index 00000000..60c6a445 --- /dev/null +++ b/src/views/AlertMap/i18n.json @@ -0,0 +1,6 @@ +{ + "namespace": "ongoingAlertMap", + "strings": { + "mapHeading": "All Ongoing Alerts" + } +} \ No newline at end of file diff --git a/src/views/AlertMap/index.tsx b/src/views/AlertMap/index.tsx new file mode 100644 index 00000000..4c83ec6a --- /dev/null +++ b/src/views/AlertMap/index.tsx @@ -0,0 +1,31 @@ +import { useTranslation } from '@ifrc-go/ui/hooks'; +import { _cs } from '@togglecorp/fujs'; + +import Container from '#ui/Container'; + +import i18n from './i18n.json'; +import styles from './styles.module.css'; + +type Props = { + className?: string; +} + +function OngoingAlertMap(props: Props) { + const { + className, + } = props; + + const strings = useTranslation(i18n); + + return ( + + Map + + ); +} + +export default OngoingAlertMap; diff --git a/src/views/AlertMap/styles.module.css b/src/views/AlertMap/styles.module.css new file mode 100644 index 00000000..bed4a1d1 --- /dev/null +++ b/src/views/AlertMap/styles.module.css @@ -0,0 +1,9 @@ +.alert-map { + .content { + position: relative; + } + + .map-container { + height: 40rem; + } +} diff --git a/src/views/Home/i18n.json b/src/views/Home/i18n.json new file mode 100644 index 00000000..85373a1c --- /dev/null +++ b/src/views/Home/i18n.json @@ -0,0 +1,8 @@ +{ + "namespace": "home", + "strings": { + "homeTitle": "Alert Hub - Home", + "homeHeading": "IFRC Alert Hub", + "homeDescription": "IFRC Alert Hub provides global emergency alerts, empowering communities to protect lives and livelihoods." + } +} \ No newline at end of file diff --git a/src/views/Home/index.tsx b/src/views/Home/index.tsx index c87824b8..c24473ab 100644 --- a/src/views/Home/index.tsx +++ b/src/views/Home/index.tsx @@ -1,12 +1,26 @@ -import { GoMainIcon } from '@ifrc-go/icons'; +import { useTranslation } from '@ifrc-go/ui/hooks'; + +import Page from '#ui/Page'; + +import OngoingAlertMap from '../AlertMap'; + +import i18n from './i18n.json'; +import styles from './styles.module.css'; // eslint-disable-next-line import/prefer-default-export export function Component() { + const strings = useTranslation(i18n); + return ( - <> - - Alert Hub - + + + ); } diff --git a/src/views/Home/styles.module.css b/src/views/Home/styles.module.css new file mode 100644 index 00000000..25a65936 --- /dev/null +++ b/src/views/Home/styles.module.css @@ -0,0 +1,7 @@ +.home { + .content { + display: flex; + flex-direction: column; + gap: var(--go-ui-spacing-2xl); + } +} diff --git a/src/views/Root/index.tsx b/src/views/Root/index.tsx deleted file mode 100644 index 8e80a9ec..00000000 --- a/src/views/Root/index.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { Outlet } from 'react-router-dom'; - -// eslint-disable-next-line import/prefer-default-export -export function Component() { - return ( - - ); -} - -Component.displayName = 'App'; diff --git a/src/views/RootLayout/index.tsx b/src/views/RootLayout/index.tsx new file mode 100644 index 00000000..4a81e5c0 --- /dev/null +++ b/src/views/RootLayout/index.tsx @@ -0,0 +1,38 @@ +import { + Outlet, + useNavigation, +} from 'react-router-dom'; +import { AlertContainer } from '@ifrc-go/ui'; +import { _cs } from '@togglecorp/fujs'; + +import Navbar from '#components/Navbar'; +import useDebouncedValue from '#hooks/useDebouncedValue'; + +import styles from './styles.module.css'; + +// eslint-disable-next-line import/prefer-default-export +export function Component() { + const { state } = useNavigation(); + const isLoading = state === 'loading'; + const isLoadingDebounced = useDebouncedValue(isLoading); + + return ( +
+ {(isLoading || isLoadingDebounced) && ( +
+ )} + +
+ +
+ +
+ ); +} + +Component.displayName = 'Root'; diff --git a/src/views/RootLayout/styles.module.css b/src/views/RootLayout/styles.module.css new file mode 100644 index 00000000..9dc1bba3 --- /dev/null +++ b/src/views/RootLayout/styles.module.css @@ -0,0 +1,94 @@ +.root { + display: flex; + position: relative; + flex-direction: column; + min-height: 100vh; + + .banner { + position: sticky; + bottom: 0; + background-color: var(--go-ui-color-orange); + padding: var(--go-ui-spacing-xs); + text-align: center; + text-transform: uppercase; + color: var(--go-ui-color-white); + font-weight: var(--go-ui-font-weight-medium); + + @media print { + display: none; + } + } + + .navigation-loader { + position: fixed; + transition: var(--go-ui-duration-animation-medium) width ease-out; + z-index: 1; + background-color: var(--go-ui-color-primary-blue); + width: 100%; + height: var(--go-ui-width-separator-lg); + animation: bounce-back-and-forth var(--go-ui-duration-animation-slow) ease-in-out infinite; + + &.disappear { + width: 0; + animation: fade-out var(--go-ui-duration-animation-medium) ease-in forwards; + } + } + + .navbar { + flex-shrink: 0; + animation: slide-down var(--go-ui-duration-animation-medium) ease-in forwards; + } + + .page-content { + display: flex; + flex-direction: column; + flex-grow: 1; + opacity: 0; + animation: appear var(--go-ui-duration-animation-medium) ease-in-out forwards; + } + + .footer { + flex-shrink: 0; + } +} + +@keyframes bounce-back-and-forth { + 0% { + left: 0; + width: 0%; + } + 50% { + width: 50%; + } + 100% { + left: 100%; + width: 0%; + } +} + +@keyframes slide-down { + from { + transform: translateY(-0.25rem); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes fade-out { + from { opacity: 1; } + to { opacity: 0; } +} + +@keyframes appear { + from { + transform: translateY(0.25rem); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} diff --git a/tsconfig.json b/tsconfig.json index de29453c..63ac743f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,8 @@ "#hooks/*": ["./src/hooks/*"], "#strings/*": ["./src/strings/*"], "#utils/*": ["./src/utils/*"], - "#views/*": ["./src/views/*"] + "#views/*": ["./src/views/*"], + "#ui/*": ["./src/ui/src/components/*"], }, "target": "ESNext",