@grantbii/design-system 1.0.71 → 1.0.73

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.
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import styled from "styled-components";
4
- import { Colors, Icons } from "../foundations";
4
+ import { Colors, Icons, Responsive, Typography } from "../foundations";
5
5
  const Badge = ({ text, Icon, iconSize = 20, iconWeight = "regular", onClickClose, textWidthPixels, backgroundColor, color, }) => (_jsxs(BaseBadge, { "$backgroundColor": backgroundColor, "$color": color, children: [_jsxs(BadgeContent, { "$isCloseable": !!onClickClose, "$widthPixels": textWidthPixels, children: [Icon ? (_jsx(IconContainer, { children: _jsx(Icon, { color: color, size: iconSize, weight: iconWeight }) })) : (_jsx(_Fragment, {})), _jsx(BadgeText, { children: text })] }), onClickClose ? (_jsx(Button, { type: "button", onClick: onClickClose, children: _jsx(Icons.XIcon, { size: 12 }) })) : (_jsx(_Fragment, {}))] }));
6
6
  export default Badge;
7
7
  const BaseBadge = styled.div `
@@ -38,7 +38,14 @@ const BadgeText = styled.p `
38
38
  text-overflow: ellipsis;
39
39
 
40
40
  font-weight: 500;
41
- font-size: 14px;
41
+
42
+ @media (width < ${Responsive.WIDTH_BREAKPOINTS.laptop}) {
43
+ font-size: ${Typography.HELPER_FONT_SIZES.small};
44
+ }
45
+
46
+ @media (width >= ${Responsive.WIDTH_BREAKPOINTS.laptop}) {
47
+ font-size: ${Typography.HELPER_FONT_SIZES.big};
48
+ }
42
49
  `;
43
50
  const Button = styled.button `
44
51
  display: flex;
@@ -11,9 +11,9 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  return t;
12
12
  };
13
13
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
14
- import { LabelInput } from "./shared";
14
+ import { LabelledInput } from "./shared";
15
15
  const Checkbox = (_a) => {
16
16
  var { id, label, labelBefore = false } = _a, checkboxProps = __rest(_a, ["id", "label", "labelBefore"]);
17
- return (_jsxs(LabelInput, { children: [labelBefore ? _jsx("label", { htmlFor: `${id}-checkbox`, children: label }) : _jsx(_Fragment, {}), _jsx("input", Object.assign({}, checkboxProps, { id: `${id}-checkbox`, type: "checkbox" })), labelBefore ? _jsx(_Fragment, {}) : _jsx("label", { htmlFor: `${id}-checkbox`, children: label })] }));
17
+ return (_jsxs(LabelledInput, { children: [labelBefore ? _jsx("label", { htmlFor: `${id}-checkbox`, children: label }) : _jsx(_Fragment, {}), _jsx("input", Object.assign({}, checkboxProps, { id: `${id}-checkbox`, type: "checkbox" })), labelBefore ? _jsx(_Fragment, {}) : _jsx("label", { htmlFor: `${id}-checkbox`, children: label })] }));
18
18
  };
19
19
  export default Checkbox;
@@ -11,9 +11,9 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  return t;
12
12
  };
13
13
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
- import { LabelInput } from "./shared";
14
+ import { LabelledInput } from "./shared";
15
15
  const RadioButton = (_a) => {
16
16
  var { id, label } = _a, radioButtonProps = __rest(_a, ["id", "label"]);
17
- return (_jsxs(LabelInput, { children: [_jsx("input", Object.assign({}, radioButtonProps, { id: `${id}-radio-button`, type: "radio" })), _jsx("label", { htmlFor: `${id}-radio-button`, children: label })] }));
17
+ return (_jsxs(LabelledInput, { children: [_jsx("input", Object.assign({}, radioButtonProps, { id: `${id}-radio-button`, type: "radio" })), _jsx("label", { htmlFor: `${id}-radio-button`, children: label })] }));
18
18
  };
19
19
  export default RadioButton;
@@ -4,4 +4,4 @@ export declare const BaseButton: import("styled-components/dist/types").IStyledC
4
4
  $color?: string;
5
5
  $width?: string;
6
6
  }>> & string;
7
- export declare const LabelInput: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
7
+ export declare const LabelledInput: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
@@ -19,7 +19,7 @@ export const BaseButton = styled.div `
19
19
  color: ${({ $color = Colors.typography.whiteHigh }) => $color};
20
20
  background-color: ${({ $underline = false, $backgroundColor = Colors.main.grantbiiBlue, }) => ($underline ? "transparent" : $backgroundColor)};
21
21
  `;
22
- export const LabelInput = styled.div `
22
+ export const LabelledInput = styled.div `
23
23
  display: flex;
24
24
  align-items: center;
25
25
  gap: 8px;
@@ -0,0 +1,2 @@
1
+ declare const ActiveQueryFiles: () => import("react/jsx-runtime").JSX.Element;
2
+ export default ActiveQueryFiles;
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import styled from "styled-components";
3
+ import { Badge, Button } from "../../atoms";
4
+ import { Colors, Icons } from "../../foundations";
5
+ import { useGrantMatchContext } from "./context";
6
+ const ActiveQueryFiles = () => (_jsxs(BaseActiveFiles, { children: [_jsx(FileBadges, {}), _jsx(ResetFilesButton, {})] }));
7
+ export default ActiveQueryFiles;
8
+ const BaseActiveFiles = styled.div `
9
+ display: flex;
10
+ align-items: center;
11
+ justify-content: space-between;
12
+ `;
13
+ const FileBadges = () => {
14
+ const { activeQuery, updateActiveQuery } = useGrantMatchContext();
15
+ const removeActiveQueryFile = (fileName) => {
16
+ const newQuery = {
17
+ files: activeQuery.files.filter((file) => file.name !== fileName),
18
+ text: activeQuery.text,
19
+ };
20
+ updateActiveQuery(newQuery);
21
+ };
22
+ return (_jsx(BaseFileBadges, { children: activeQuery.files.map((file) => {
23
+ var _a;
24
+ return (_jsx(Badge, { text: file.name.substring(0, file.name.lastIndexOf(".")), Icon: (_a = FILE_TYPE_ICON_MAP[file.type]) !== null && _a !== void 0 ? _a : Icons.FileIcon, onClickClose: () => removeActiveQueryFile(file.name), textWidthPixels: 160 }, file.name));
25
+ }) }));
26
+ };
27
+ const BaseFileBadges = styled.div `
28
+ display: flex;
29
+ align-items: center;
30
+ gap: 8px;
31
+
32
+ width: 100%;
33
+
34
+ overflow-x: auto;
35
+
36
+ /* hide scrollbar but still allow for scrolling */
37
+ -ms-overflow-style: none;
38
+ scrollbar-width: none;
39
+ ::-webkit-scrollbar {
40
+ display: none;
41
+ }
42
+
43
+ /* TODO: fade effect on overflow-x */
44
+ `;
45
+ const FILE_TYPE_ICON_MAP = {
46
+ "application/pdf": Icons.FilePdfIcon,
47
+ };
48
+ const ResetFilesButton = () => {
49
+ const { activeQuery, updateActiveQuery } = useGrantMatchContext();
50
+ return (_jsx(Button, { text: "Reset", onClick: () => updateActiveQuery({ files: [], text: activeQuery.text }), color: Colors.typography.blackMedium, underline: true }));
51
+ };
@@ -0,0 +1,2 @@
1
+ declare const GrantMatchModal: () => import("react/jsx-runtime").JSX.Element;
2
+ export default GrantMatchModal;
@@ -0,0 +1,35 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import styled from "styled-components";
4
+ import { Button, Textarea } from "../../atoms";
5
+ import { Colors } from "../../foundations";
6
+ import { FileDrop, Modal, useFileDrop } from "../../molecules";
7
+ import { useGrantMatchContext } from "./context";
8
+ const GrantMatchModal = () => {
9
+ const { activeQuery, closeModal } = useGrantMatchContext();
10
+ const { files, uploadFiles, removeFile } = useFileDrop(activeQuery.files);
11
+ return (_jsx(Modal, { header: _jsx("div", { children: "Grant Match" }), content: _jsx(ModalContent, { files: files, uploadFiles: uploadFiles, removeFile: removeFile }), footer: _jsx(FindGrantsButton, { files: files }), onClickCancel: () => closeModal(), width: "480px", height: "600px" }));
12
+ };
13
+ export default GrantMatchModal;
14
+ const ModalContent = ({ files, uploadFiles, removeFile, }) => {
15
+ const { queryText, updateQueryText } = useGrantMatchContext();
16
+ return (_jsxs(BaseContent, { children: [_jsx(FileDrop, { uploadedFiles: files, uploadFiles: uploadFiles, removeFile: removeFile }), _jsxs(ModalQueryText, { children: [_jsx("label", { htmlFor: QUERY_TEXTAREA_ID, children: "Search Grants Opportunities" }), _jsx(Textarea, { id: QUERY_TEXTAREA_ID, value: queryText, onChange: (event) => updateQueryText(event.target.value), placeholder: "Explore by grant name or share what your project is about..." })] })] }));
17
+ };
18
+ const QUERY_TEXTAREA_ID = "query-textarea";
19
+ const BaseContent = styled.div `
20
+ display: flex;
21
+ flex-direction: column;
22
+ gap: 12px;
23
+ `;
24
+ const ModalQueryText = styled.div `
25
+ display: flex;
26
+ flex-direction: column;
27
+ `;
28
+ const FindGrantsButton = ({ files }) => {
29
+ const { updateActiveQuery, queryText, closeModal } = useGrantMatchContext();
30
+ const onClick = () => {
31
+ updateActiveQuery({ files, text: queryText });
32
+ closeModal();
33
+ };
34
+ return (_jsx(Button, { text: "Find My Grants", onClick: onClick, backgroundColor: Colors.accent.yellow1 }));
35
+ };
@@ -0,0 +1,2 @@
1
+ declare const SearchBar: () => import("react/jsx-runtime").JSX.Element;
2
+ export default SearchBar;
@@ -0,0 +1,133 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import styled, { css } from "styled-components";
3
+ import { Colors, Icons, Responsive, Typography } from "../../foundations";
4
+ import { useGrantMatchContext } from "./context";
5
+ const SearchBar = () => {
6
+ const { activeQuery } = useGrantMatchContext();
7
+ return (_jsxs(BaseSearchBar, { "$hasActiveQueryText": activeQuery.text !== "", children: [_jsx(QueryTextInput, {}), _jsxs(Buttons, { children: [_jsx(ResetTextButton, {}), _jsx(SearchButton, {}), _jsx(FileDropButton, {})] })] }));
8
+ };
9
+ export default SearchBar;
10
+ const BaseSearchBar = styled.div `
11
+ display: flex;
12
+ align-items: center;
13
+ justify-content: space-between;
14
+
15
+ background-color: ${Colors.base.white};
16
+ color: ${Colors.typography.blackHigh};
17
+
18
+ border: 1px solid
19
+ ${({ $hasActiveQueryText }) => $hasActiveQueryText ? Colors.main.grantbiiOrange : Colors.neutral.grey3};
20
+ border-radius: 12px;
21
+
22
+ @media (width < ${Responsive.WIDTH_BREAKPOINTS.laptop}) {
23
+ gap: 8px;
24
+ width: 100%;
25
+ padding: 6px 16px;
26
+ }
27
+
28
+ @media (width >= ${Responsive.WIDTH_BREAKPOINTS.laptop}) {
29
+ gap: 10px;
30
+ width: 480px;
31
+ padding: 10px;
32
+ }
33
+ `;
34
+ const QueryTextInput = () => {
35
+ const { activeQuery, updateActiveQuery, queryText, updateQueryText } = useGrantMatchContext();
36
+ const onKeyDown = (event) => {
37
+ if (event.key === "Enter" && !event.repeat) {
38
+ event.preventDefault();
39
+ updateActiveQuery({ files: activeQuery.files, text: queryText });
40
+ }
41
+ };
42
+ return (_jsx(BaseQueryTextInput, { value: queryText, onChange: (event) => updateQueryText(event.target.value), onKeyDown: onKeyDown, placeholder: "Find grants that match your needs" }));
43
+ };
44
+ const BaseQueryTextInput = styled.input `
45
+ border: none;
46
+ outline: none;
47
+
48
+ width: 100%;
49
+ `;
50
+ const Buttons = styled.div `
51
+ display: flex;
52
+ align-items: center;
53
+ gap: 8px;
54
+ `;
55
+ const BaseIconButton = styled.button `
56
+ display: flex;
57
+ align-items: center;
58
+ justify-content: center;
59
+
60
+ height: 32px;
61
+ width: 32px;
62
+ min-width: 32px;
63
+
64
+ border-radius: 8px;
65
+ `;
66
+ const ResetTextButton = () => {
67
+ const { activeQuery, updateActiveQuery, updateQueryText } = useGrantMatchContext();
68
+ const onClick = () => {
69
+ updateQueryText("");
70
+ updateActiveQuery({ files: activeQuery.files, text: "" });
71
+ };
72
+ return (_jsx(BaseResetTextButton, { type: "button", onClick: onClick, children: _jsx(Icons.XIcon, { size: 16, color: Colors.neutral.grey1 }) }));
73
+ };
74
+ const BaseResetTextButton = styled(BaseIconButton) `
75
+ background-color: ${Colors.base.white};
76
+ `;
77
+ const SearchButton = () => {
78
+ const { activeQuery, updateActiveQuery, queryText } = useGrantMatchContext();
79
+ const onClick = () => updateActiveQuery({ files: activeQuery.files, text: queryText });
80
+ return (_jsx(BaseSearchButton, { type: "button", onClick: onClick, children: _jsx(Icons.MagnifyingGlassIcon, { size: 16, color: Colors.neutral.grey1 }) }));
81
+ };
82
+ const BaseSearchButton = styled(BaseIconButton) `
83
+ background-color: ${Colors.neutral.grey3};
84
+ `;
85
+ const FileDropButton = () => {
86
+ const { activeQuery, openModal } = useGrantMatchContext();
87
+ return (_jsxs(BaseFileDropButton, { onClick: () => openModal(), "$hasActiveQueryFiles": activeQuery.files.length > 0, children: [_jsx(Icons.FileArrowUpIcon, { size: 16 }), _jsx(FileDropButtonText, { children: "File Drop" })] }));
88
+ };
89
+ const BaseFileDropButton = styled.button `
90
+ display: flex;
91
+ align-items: center;
92
+ justify-content: center;
93
+ gap: 4px;
94
+
95
+ height: 31px;
96
+ min-width: 31px;
97
+
98
+ border: 1px solid ${Colors.main.grantbiiOrange};
99
+ border-radius: 8px;
100
+
101
+ ${({ $hasActiveQueryFiles }) => $hasActiveQueryFiles
102
+ ? css `
103
+ background-color: ${Colors.main.grantbiiOrange};
104
+ color: ${Colors.typography.whiteHigh};
105
+ `
106
+ : css `
107
+ background-color: ${Colors.base.white};
108
+ color: ${Colors.main.grantbiiOrange};
109
+ `}
110
+
111
+ @media (width < ${Responsive.WIDTH_BREAKPOINTS.laptop}) {
112
+ padding: 0px;
113
+ font-size: ${Typography.HELPER_FONT_SIZES.small};
114
+ }
115
+
116
+ @media (width >= ${Responsive.WIDTH_BREAKPOINTS.laptop}) {
117
+ padding: 0px 8px;
118
+ font-size: ${Typography.HELPER_FONT_SIZES.big};
119
+ }
120
+ `;
121
+ const FileDropButtonText = styled.p `
122
+ overflow: hidden;
123
+ white-space: nowrap;
124
+ text-overflow: ellipsis;
125
+
126
+ @media (width < ${Responsive.WIDTH_BREAKPOINTS.laptop}) {
127
+ display: none;
128
+ }
129
+
130
+ @media (width >= ${Responsive.WIDTH_BREAKPOINTS.laptop}) {
131
+ display: inline;
132
+ }
133
+ `;
@@ -0,0 +1,12 @@
1
+ import { GrantMatchQuery } from "@grantbii/ui-base/match/models";
2
+ type GrantMatchCommonProps = {
3
+ activeQuery: GrantMatchQuery;
4
+ updateActiveQuery: (query: GrantMatchQuery) => void;
5
+ queryText: string;
6
+ updateQueryText: (newText: string) => void;
7
+ openModal: () => void;
8
+ closeModal: () => void;
9
+ };
10
+ export declare const GrantMatchContext: import("react").Context<GrantMatchCommonProps | null>;
11
+ export declare const useGrantMatchContext: () => GrantMatchCommonProps;
12
+ export {};
@@ -0,0 +1,9 @@
1
+ import { createContext, useContext } from "react";
2
+ export const GrantMatchContext = createContext(null);
3
+ export const useGrantMatchContext = () => {
4
+ const context = useContext(GrantMatchContext);
5
+ if (!context) {
6
+ throw new Error("useGrantMatchContext must be used within its Provider");
7
+ }
8
+ return context;
9
+ };
@@ -0,0 +1,49 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { checkGrantMatchActive } from "@grantbii/ui-base/match/mappings";
4
+ import { useState } from "react";
5
+ import styled from "styled-components";
6
+ import { useModal } from "../../molecules";
7
+ import ActiveQueryFiles from "./ActiveQueryFiles";
8
+ import GrantMatchModal from "./GrantMatchModal";
9
+ import SearchBar from "./SearchBar";
10
+ import { GrantMatchContext } from "./context";
11
+ const GrantMatch = ({ activeQuery, updateActiveQuery }) => {
12
+ const { showModal, openModal, closeModal } = useModal();
13
+ const [queryText, setQueryText] = useState(activeQuery.text);
14
+ const updateQueryText = (newText) => setQueryText(newText);
15
+ const commonProps = {
16
+ activeQuery,
17
+ updateActiveQuery,
18
+ queryText,
19
+ updateQueryText,
20
+ openModal,
21
+ closeModal,
22
+ };
23
+ return (_jsx(GrantMatchContext.Provider, { value: commonProps, children: _jsxs(BaseGrantMatch, { children: [_jsx(SearchBar, {}), activeQuery.files.length > 0 ? _jsx(ActiveQueryFiles, {}) : _jsx(_Fragment, {}), showModal ? _jsx(GrantMatchModal, {}) : _jsx(_Fragment, {})] }) }));
24
+ };
25
+ export default GrantMatch;
26
+ const BaseGrantMatch = styled.div `
27
+ display: flex;
28
+ flex-direction: column;
29
+ gap: 8px;
30
+
31
+ width: 100%;
32
+ max-width: 100vw;
33
+ `;
34
+ export const useGrantMatchActiveQuery = (performGrantMatch, resetGrantMatch) => {
35
+ const [activeQuery, setActiveQuery] = useState({
36
+ files: [],
37
+ text: "",
38
+ });
39
+ const updateActiveQuery = (newQuery) => {
40
+ setActiveQuery(Object.assign({}, newQuery));
41
+ if (checkGrantMatchActive(newQuery)) {
42
+ performGrantMatch(newQuery);
43
+ }
44
+ else {
45
+ resetGrantMatch();
46
+ }
47
+ };
48
+ return { activeQuery, updateActiveQuery };
49
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grantbii/design-system",
3
- "version": "1.0.71",
3
+ "version": "1.0.73",
4
4
  "description": "Grantbii's Design System",
5
5
  "homepage": "https://design.grantbii.com",
6
6
  "repository": {
@@ -18,10 +18,10 @@
18
18
  "build-storybook": "storybook build"
19
19
  },
20
20
  "dependencies": {
21
- "@grantbii/ui-base": "1.0.19",
21
+ "@grantbii/ui-base": "1.0.20",
22
22
  "@phosphor-icons/react": "^2.1.10",
23
23
  "country-flag-icons": "^1.5.19",
24
- "next": "^15.5.0",
24
+ "next": "^15.5.2",
25
25
  "react": "^19.1.1",
26
26
  "react-dom": "^19.1.1",
27
27
  "react-dropzone": "^14.3.8",
@@ -31,7 +31,7 @@
31
31
  "devDependencies": {
32
32
  "@chromatic-com/storybook": "^4.1.1",
33
33
  "@eslint/js": "^9.34.0",
34
- "@next/eslint-plugin-next": "^15.5.0",
34
+ "@next/eslint-plugin-next": "^15.5.2",
35
35
  "@storybook/addon-a11y": "^9.1.3",
36
36
  "@storybook/addon-docs": "^9.1.3",
37
37
  "@storybook/addon-onboarding": "^9.1.3",
@@ -39,15 +39,15 @@
39
39
  "@storybook/nextjs-vite": "^9.1.3",
40
40
  "@types/node": "^20",
41
41
  "@types/react": "^19.1.11",
42
- "@types/react-dom": "^19.1.7",
42
+ "@types/react-dom": "^19.1.8",
43
43
  "@vitest/browser": "^3.2.4",
44
44
  "@vitest/coverage-v8": "^3.2.4",
45
45
  "eslint": "^9.34.0",
46
- "eslint-config-next": "15.5.0",
46
+ "eslint-config-next": "15.5.2",
47
47
  "eslint-plugin-storybook": "^9.1.3",
48
48
  "husky": "^9.1.7",
49
49
  "lint-staged": "^16.1.5",
50
- "playwright": "^1.54.2",
50
+ "playwright": "^1.55.0",
51
51
  "prettier": "^3.6.2",
52
52
  "storybook": "^9.1.3",
53
53
  "typescript": "^5.9.2",
@@ -8,8 +8,8 @@ const GrantMatchExample = () => {
8
8
  const performGrantMatch = (newQuery) => {
9
9
  const fileNames = newQuery.files.map((file) => file.name).join(", ");
10
10
  const printableQuery = `files [${fileNames}] and text [${newQuery.text}]`;
11
- setStatus(`finding grants using ${printableQuery}`);
12
- setTimeout(() => setStatus(`found grants with ${printableQuery}`), 3000);
11
+ setStatus(`trying to find grants using ${printableQuery}`);
12
+ setTimeout(() => setStatus(`found grants with ${printableQuery}`), 1000);
13
13
  };
14
14
  const resetGrantMatch = () => setStatus("pending query");
15
15
  const grantMatchQueryProps = useGrantMatchActiveQuery(performGrantMatch, resetGrantMatch);