diff --git a/src/app/sandbox/combobox/page.tsx b/src/app/sandbox/combobox/page.tsx
new file mode 100644
index 00000000..273314bf
--- /dev/null
+++ b/src/app/sandbox/combobox/page.tsx
@@ -0,0 +1,66 @@
+"use client";
+import { useState } from "react";
+import Combobox from "@/components/ui/combobox";
+import populateCragsAction from "./server-actions/populate-crags-action";
+import populateRoutesAction from "./server-actions/populate-routes-action";
+
+function ComboboxPage() {
+
+ async function populateCrags(text: string) {
+ if (text === "") {
+ return [];
+ }
+ const crags = await populateCragsAction(text);
+ return crags.map((crag) => ({
+ value: crag.id,
+ name: crag.name,
+ }));
+ }
+
+ async function populateRoutes(text: string) {
+ if (text === "") {
+ return [];
+ }
+ const routes = await populateRoutesAction(text);
+ return routes.map((route) => ({
+ value: route.id,
+ name: `${route.name}, ${route.crag.name}`,
+ }));
+ }
+
+ function handleChange(a) {
+ console.log("handleChange", a);
+ }
+
+ return (
+
+ );
+}
+
+export default ComboboxPage;
diff --git a/src/app/sandbox/combobox/server-actions/populate-crags-action.ts b/src/app/sandbox/combobox/server-actions/populate-crags-action.ts
new file mode 100644
index 00000000..30f0bd35
--- /dev/null
+++ b/src/app/sandbox/combobox/server-actions/populate-crags-action.ts
@@ -0,0 +1,42 @@
+"use server";
+
+import { gql } from "urql/core";
+import {
+ ComboboxPopulateCragsDocument,
+ Crag,
+ RouteDifficultyVotesDocument,
+} from "@/graphql/generated";
+import urqlServer from "@/graphql/urql-server";
+
+async function populateCragsAction(
+ query: string
+): Promise {
+ const result = await urqlServer().query(ComboboxPopulateCragsDocument, {
+ input: {
+ query,
+ pageSize: 10,
+ orderBy: { field: "popularity", direction: "DESC" },
+ },
+ });
+
+ if (result.error) {
+ return Promise.reject(result.error);
+ }
+
+ return result.data.searchCrags.items;
+}
+
+export default populateCragsAction;
+
+gql`
+ query ComboboxPopulateCrags($input: SearchCragsInput!) {
+ searchCrags(input: $input) {
+ items {
+ __typename
+ id
+ name
+ slug
+ }
+ }
+ }
+`;
diff --git a/src/app/sandbox/combobox/server-actions/populate-routes-action.ts b/src/app/sandbox/combobox/server-actions/populate-routes-action.ts
new file mode 100644
index 00000000..c3f0f028
--- /dev/null
+++ b/src/app/sandbox/combobox/server-actions/populate-routes-action.ts
@@ -0,0 +1,40 @@
+"use server";
+
+import { gql } from "urql/core";
+import { ComboboxPopulateCragsDocument, ComboboxPopulateRoutesDocument, Route } from "@/graphql/generated";
+import urqlServer from "@/graphql/urql-server";
+
+async function populateRoutesAction(query: string): Promise {
+ const result = await urqlServer().query(ComboboxPopulateRoutesDocument, {
+ input: {
+ query,
+ pageSize: 10,
+ orderBy: { field: "popularity", direction: "DESC" },
+ },
+ });
+
+ if (result.error) {
+ return Promise.reject(result.error);
+ }
+
+ return result.data.searchRoutes.items;
+}
+
+export default populateRoutesAction;
+
+gql`
+ query ComboboxPopulateRoutes($input: SearchRoutesInput!) {
+ searchRoutes(input: $input) {
+ items {
+ __typename
+ id
+ name
+ slug
+ crag {
+ id
+ name
+ }
+ }
+ }
+ }
+`;
diff --git a/src/components/ui/combobox.tsx b/src/components/ui/combobox.tsx
new file mode 100644
index 00000000..541f941f
--- /dev/null
+++ b/src/components/ui/combobox.tsx
@@ -0,0 +1,121 @@
+import useForwardedRef from "@/hooks/useForwardedRef";
+import {
+ Combobox as HUICombobox,
+ ComboboxInput,
+ ComboboxOption,
+ ComboboxOptions,
+ Field,
+ ComboboxButton,
+ Label,
+} from "@headlessui/react";
+import { ForwardedRef, forwardRef, useEffect, useState } from "react";
+import IconSearch from "./icons/search";
+
+type TComboboxProps = {
+ name?: string;
+ value?: TComboboxValue | null;
+ type?: string;
+ onChange: (value: string | null) => void;
+ label?: string;
+ placeholder?: string;
+ description?: string;
+ errorMessage?: string;
+ disabled?: boolean;
+ onBlur?: () => void;
+ populate: (text: string) => Promise;
+};
+
+type TComboboxValue = {
+ value: string;
+ name: string;
+};
+
+const Combobox = forwardRef(function Combobox(
+ {
+ value,
+ onChange,
+ label,
+ errorMessage,
+ disabled,
+ populate,
+ }: TComboboxProps,
+ forwardedRef: ForwardedRef
+) {
+ const inputRef = useForwardedRef(forwardedRef);
+
+ const focusInput = () => {
+ inputRef.current?.focus();
+ };
+
+ const [selectedValue, setSelectedValue] = useState(value);
+ const [query, setQuery] = useState("");
+
+ const [options, setOptions] = useState([]);
+
+ useEffect(() => {
+ populate(query).then((result) => {
+ setOptions(result);
+ });
+ }, [query, populate]);
+
+ function handleChange(newValue: TComboboxValue) {
+ setSelectedValue(newValue);
+ onChange(newValue?.value || null);
+ }
+
+ return (
+
+ {label && }
+ setQuery("")}
+ >
+
+
value?.name}
+ onChange={(event) => setQuery(event.target.value)}
+ className="flex-1 outline-none min-w-0 rounded-lg w-full py-2 placeholder:text-neutral-400 pl-4"
+ autoComplete="off"
+ />
+
+
+
+
+
+
+ {options.length > 0 && (
+
+ {options.map((option: TComboboxValue) => (
+
+ {option.name}
+
+ ))}
+
+ )}
+
+
+ );
+});
+
+export default Combobox;
diff --git a/src/graphql/generated.ts b/src/graphql/generated.ts
index bfdb9a3a..ed96893e 100644
--- a/src/graphql/generated.ts
+++ b/src/graphql/generated.ts
@@ -817,12 +817,24 @@ export type PaginatedComments = {
meta: PaginationMeta;
};
+export type PaginatedCrags = {
+ __typename?: 'PaginatedCrags';
+ items: Array;
+ meta: PaginationMeta;
+};
+
export type PaginatedDifficultyVotes = {
__typename?: 'PaginatedDifficultyVotes';
items: Array;
meta: PaginationMeta;
};
+export type PaginatedRoutes = {
+ __typename?: 'PaginatedRoutes';
+ items: Array;
+ meta: PaginationMeta;
+};
+
export type PaginationMeta = {
__typename?: 'PaginationMeta';
itemCount: Scalars['Float']['output'];
@@ -935,6 +947,8 @@ export type Query = {
routeBySlug: Route;
routesTouches: RoutesTouches;
search: SearchResults;
+ searchCrags: PaginatedCrags;
+ searchRoutes: PaginatedRoutes;
sector: Sector;
starRatingVotes: Array;
users: Array;
@@ -1107,6 +1121,16 @@ export type QuerySearchArgs = {
};
+export type QuerySearchCragsArgs = {
+ input: SearchCragsInput;
+};
+
+
+export type QuerySearchRoutesArgs = {
+ input: SearchRoutesInput;
+};
+
+
export type QuerySectorArgs = {
id: Scalars['String']['input'];
};
@@ -1211,6 +1235,13 @@ export type RoutesTouches = {
tried: Array;
};
+export type SearchCragsInput = {
+ orderBy?: InputMaybe;
+ pageNumber?: InputMaybe;
+ pageSize?: InputMaybe;
+ query: Scalars['String']['input'];
+};
+
export type SearchResults = {
__typename?: 'SearchResults';
comments?: Maybe>;
@@ -1220,6 +1251,13 @@ export type SearchResults = {
users?: Maybe>;
};
+export type SearchRoutesInput = {
+ orderBy?: InputMaybe;
+ pageNumber?: InputMaybe;
+ pageSize?: InputMaybe;
+ query: Scalars['String']['input'];
+};
+
export enum Season {
Autumn = 'autumn',
Spring = 'spring',
@@ -1411,6 +1449,8 @@ export const MyCragSummaryDocument = {"kind":"Document","definitions":[{"kind":"
export const AllCragsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AllCrags"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"crags"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"type"},"value":{"kind":"StringValue","value":"sport","block":false}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"country"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}}]}},{"kind":"Field","name":{"kind":"Name","value":"area"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"country"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slug"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"orientations"}},{"kind":"Field","name":{"kind":"Name","value":"minDifficulty"}},{"kind":"Field","name":{"kind":"Name","value":"maxDifficulty"}},{"kind":"Field","name":{"kind":"Name","value":"seasons"}},{"kind":"Field","name":{"kind":"Name","value":"rainproof"}},{"kind":"Field","name":{"kind":"Name","value":"wallAngles"}},{"kind":"Field","name":{"kind":"Name","value":"approachTime"}},{"kind":"Field","name":{"kind":"Name","value":"nrRoutesByGrade"}},{"kind":"Field","name":{"kind":"Name","value":"hasSport"}},{"kind":"Field","name":{"kind":"Name","value":"hasBoulder"}},{"kind":"Field","name":{"kind":"Name","value":"hasMultipitch"}},{"kind":"Field","name":{"kind":"Name","value":"nrRoutes"}}]}}]}}]} as unknown as DocumentNode;
export const AllCountriesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AllCountries"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"countries"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"nrCrags"}},{"kind":"Field","name":{"kind":"Name","value":"areas"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"nrCrags"}}]}}]}}]}}]} as unknown as DocumentNode;
export const LoginDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"Login"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"email"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"password"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"login"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"email"},"value":{"kind":"Variable","name":{"kind":"Name","value":"email"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"password"},"value":{"kind":"Variable","name":{"kind":"Name","value":"password"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"token"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"fullName"}},{"kind":"Field","name":{"kind":"Name","value":"firstname"}},{"kind":"Field","name":{"kind":"Name","value":"lastname"}},{"kind":"Field","name":{"kind":"Name","value":"gender"}},{"kind":"Field","name":{"kind":"Name","value":"roles"}}]}}]}}]}}]} as unknown as DocumentNode;
+export const ComboboxPopulateCragsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ComboboxPopulateCrags"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SearchCragsInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"searchCrags"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}}]}}]}}]}}]} as unknown as DocumentNode;
+export const ComboboxPopulateRoutesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ComboboxPopulateRoutes"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SearchRoutesInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"searchRoutes"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"crag"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]} as unknown as DocumentNode;
export const CragDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Crag"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"crag"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cragBySlug"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"crag"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"sectors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"routes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"difficulty"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"defaultGradingSystem"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode;
export const CragActivitiesByMonthDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CragActivitiesByMonth"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"crag"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cragBySlug"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"crag"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"activityByMonth"}}]}}]}}]} as unknown as DocumentNode;
export const CreateActivityDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateActivity"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateActivityInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"routes"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateActivityRouteInput"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createActivity"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}},{"kind":"Argument","name":{"kind":"Name","value":"routes"},"value":{"kind":"Variable","name":{"kind":"Name","value":"routes"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode;
@@ -1429,6 +1469,8 @@ export const namedOperations = {
MyCragSummary: 'MyCragSummary',
AllCrags: 'AllCrags',
AllCountries: 'AllCountries',
+ ComboboxPopulateCrags: 'ComboboxPopulateCrags',
+ ComboboxPopulateRoutes: 'ComboboxPopulateRoutes',
Crag: 'Crag',
CragActivitiesByMonth: 'CragActivitiesByMonth',
DryRunCreateActivity: 'DryRunCreateActivity',
@@ -1551,6 +1593,20 @@ export type LoginMutationVariables = Exact<{
export type LoginMutation = { __typename?: 'Mutation', login: { __typename?: 'LoginResponse', token: string, user: { __typename?: 'User', id: string, email?: string | null, fullName: string, firstname: string, lastname: string, gender?: string | null, roles: Array } } };
+export type ComboboxPopulateCragsQueryVariables = Exact<{
+ input: SearchCragsInput;
+}>;
+
+
+export type ComboboxPopulateCragsQuery = { __typename?: 'Query', searchCrags: { __typename?: 'PaginatedCrags', items: Array<{ __typename: 'Crag', id: string, name: string, slug: string }> } };
+
+export type ComboboxPopulateRoutesQueryVariables = Exact<{
+ input: SearchRoutesInput;
+}>;
+
+
+export type ComboboxPopulateRoutesQuery = { __typename?: 'Query', searchRoutes: { __typename?: 'PaginatedRoutes', items: Array<{ __typename: 'Route', id: string, name: string, slug: string, crag: { __typename?: 'Crag', id: string, name: string } }> } };
+
export type CragQueryVariables = Exact<{
crag: Scalars['String']['input'];
}>;