diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..b14e83c
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,164 @@
+# Upcoming TODOs
+
+## Development ease
+
+- [X] Create some [common snippets](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_create-your-own-snippets)
+ - Start with below
+ ```javascript
+ // Snippet #1
+
+ // Snippet #2
+ style={ruffwind``}
+ ```
+ - Note: This updated in `~/Library/Application Support/Code/User/snippets/typescriptreact.json`
+- [ ] Personal side quest: what happened with script generation of Week 43?
+ - [ ] Personal side quest: when should it generate?
+- [ ] Personal side quest: when should it generate
+- [ ] Personal side quest: fix tasks from duplicating
+- [ ] Personal side quest: fix typography
+
+## Finish framing and styles on SearchFilters page
+
+- [x] Put in mock time of day (either radio placeholder or input)
+- [x] Radio group - handle flex direction
+- [x] Add styles for search button and clear filters link
+- [ ] Test bottomsheet for date (create a simple button to open bottomsheet just ot make sure things are copasetic)
+- [ ] Test input opens keyboard on dashboard and filters page
+
+## Setup on other computer
+
+- [ ] Merge "Finish framing and styles on SearchFilters page" work
+- [ ] Download onto other laptop
+- [ ] Document any setup discrepencies
+- [ ] Push updates to README
+
+## Add framing and styles on SearchResults page
+
+- [ ] Add base styles and framing
+- [ ] Add filters modal styles and framing
+- [ ] Add mock map (image is fine)
+- [ ] Add mock search results
+
+## Can I send a base build to Sara?
+
+- [ ] Ensure navigation goes to _coming soon_ pages
+- [ ] Final "walk though of pet profiles and dashboard/search pages to confirm workflows"
+- [ ] Add app icon to build
+- [ ] Expo build and export
+
+## Add new form components
+
+- [ ] Add checkbox group component
+- [ ] Implement checkbox group in search filters page
+- [ ] Add date picker component
+- [ ] Handle single date selection
+- [ ] Handle range selection
+- [ ] Add date picker modal
+- [ ] Add date picker dropdown
+- [ ] Implement date picker in search filters page
+- [ ] Update Button with count notification
+- [ ] Add Dropdown with count notification
+- [ ] Add price range selector component
+
+## Handle map
+
+- [ ] Test different maps
+- [ ] Build protoype with behaviors
+ - [ ] Zoom in
+ - [ ] See visible circle range
+ - [ ] Handle grouping caretakers by section to protect privacy
+ - [ ] What is the physical radius we need to adhere to?
+ - [ ] Handle selecting a price on map, opens caretaker profile in modal
+ - [ ] Modal can slide up for more information
+ - [ ] Modal is scrollable
+ - [ ] Handle how multiple prices stacked in a map are still visible
+ - [ ] How can we prevent them from fully stacking? Or... if we stack.. can we open some sort of secondary screen? Or if we stack and click on a stack - can all results in a stack appear in the results bottomsheet? (last option seems smallest level of lift and easiest accessibility)
+ - [ ] Ideation:
+ - [ ] if a stack has <= 3, display all three prices next to each other
+ - [ ] if a stack has > 3, display "from $XX (3+)"
+ - [ ] if they click on that, it opens all results in that stack in the results modal as list instead of a single profile with a header that lets you know your in the stack and can take you back to your previous view/search/etc
+
+## Form and API work
+
+- [ ] Dashboard
+- [ ] Add location search input using mock location API
+- [ ] Implement location serach input in search filters page and search dashboard page
+- [ ] Implement favoriting with mock API call
+- [ ] Ensure navigation goes to _coming soon_ pages
+- [ ] Filters
+- [ ] Convert to form
+- [ ] Manage clear all button
+- [ ] Manage submit to search API
+- [ ] Results
+- [ ] Add styles and framing
+- [ ] Add filters modal styles and framing
+- [ ] Add map handling and styles
+- [ ] Add search results displaying
+- [ ] Handle filters form
+- [ ] Handle filters submission to caretakers search API and refresh on page
+- [ ] Handle dropdown count notification on filters and pets
+- [ ] Handle date dropdown
+- [ ] Handle date submission to caretakers search API and refresh on page
+- [ ] Handle my pets dropdown
+- [ ] Handle my pets submission to caretakers search API and refresh on page
+- [ ] Handle no pets and going to access or pet profile splash pages
+- [ ] Update filters based on selected pet(s)
+
+## Backend work
+
+- [ ] Add caretakers API using mock db data
+- [ ] Source and add location API wrapper
+- [ ] Add caretaker search service
+- [ ] Add petowner api
+- [ ] Add access workflows
+- [ ] account api
+- [ ] passwordless login via email
+- [ ] social via google
+- [ ] social via apple
+- [ ] Add pet profile api
+- [ ] Research a cloud option for things like images and other pieces - probably start with AWS b/c easy enough to get started
+- [ ] Add image saving pathway - firebase? s3? etc?
+
+## Cleanup
+
+- [ ] Jest tests for every UI component
+- [ ] Rspec tests for every unit and integration
+- [ ] Implement basic testing CI
+- [ ] Can I afford to spend time on e2e?
+- [ ] Cypress?
+- [ ] Capybara?
+- [ ] Option 3?
+
+## Access workflows
+
+- [ ] Add onboarding pages
+- [ ] Add email workflow pages
+- [ ] Add guest workflow pages
+- [ ] Add profile setup pages
+- [ ] Add apple login workflow pages
+- [ ] Add google login workflow pages
+- [ ] Add app icon to build
+- [ ] Research and add splash animation
+
+## Next work
+
+- [ ] Access needed profile splash updates
+- [ ] Caretaker profile
+- [ ] Booking prototype
+- [ ] Account prototype
+
+## Next next steps
+
+- [ ] Messaging prototype
+- [ ] Petowner view prototype
+- [ ] Pet profile view prototype
+- [ ] Caretaker account creation prototype
+
+## Future work [if Sara designs]
+
+- [ ] Petowner profile view
+- [ ] Pet profile view
+- [ ] Bookings
+- [ ] Account
+- [ ] Messaging
+- [ ] Caretaker account creation
diff --git a/apps/rufferal/src/app/screens/Screens.tsx b/apps/rufferal/src/app/screens/Screens.tsx
index 44a373f..af5f4b0 100644
--- a/apps/rufferal/src/app/screens/Screens.tsx
+++ b/apps/rufferal/src/app/screens/Screens.tsx
@@ -26,6 +26,11 @@ export const Screens = observer(() => {
{/* ⬇️⬇️⬇️ CURRENT DEVELOPMENT PAGE ⬇️⬇️⬇️ */}
+
+
{/* ⬆️⬆️⬆️ CURRENT DEVELOPMENT PAGE ⬆️⬆️⬆️ */}
@@ -38,11 +43,11 @@ export const Screens = observer(() => {
-
-
+ /> */}
+ {/* */}
diff --git a/assets/src/icons/rotate.png b/assets/src/icons/rotate.png
new file mode 100644
index 0000000..f9846d7
Binary files /dev/null and b/assets/src/icons/rotate.png differ
diff --git a/types/src/ui/atoms.ts b/types/src/ui/atoms.ts
index 15a373a..514cf8a 100644
--- a/types/src/ui/atoms.ts
+++ b/types/src/ui/atoms.ts
@@ -131,6 +131,7 @@ export interface FieldInputProps
export interface LabelProps {
text: string;
+ size?: Extract;
state?: FieldState;
}
@@ -154,6 +155,16 @@ export interface ItemProps extends PressableProps {
height?: string;
}
+export interface LinkButtonProps extends PressableProps {
+ containerStyles?: string;
+ iconLeft?: React.JSX.Element;
+ iconRight?: React.JSX.Element;
+ state?: FieldState;
+ text?: string;
+ textStyles?: string;
+ underlineStyles?: string;
+}
+
export interface ProgressBarProps {
step: number;
total: number;
diff --git a/types/src/ui/fields.ts b/types/src/ui/fields.ts
index 5ed2f69..d537951 100644
--- a/types/src/ui/fields.ts
+++ b/types/src/ui/fields.ts
@@ -1,4 +1,13 @@
-export type FieldSize = 'standard' | 'standard-short' | 'small' | 'small-short';
+export type FieldSize =
+ | 'xxsmall'
+ | 'xsmall'
+ | 'small'
+ | 'medium'
+ | 'default'
+ | 'large'
+ | 'xlarge'
+ | '2xlarge'
+ | '3xlarge';
export type FieldState = 'default' | 'errored' | 'disabled';
diff --git a/types/src/ui/molecules.ts b/types/src/ui/molecules.ts
index d3744d4..2ff7850 100644
--- a/types/src/ui/molecules.ts
+++ b/types/src/ui/molecules.ts
@@ -2,10 +2,34 @@ import {
FieldInputProps,
FieldOption,
FieldSelectProps,
+ FieldSize,
SingleSliderProps,
ToggleProps,
} from '..';
+export interface CheckboxCardProps {
+ containerDirection?: string;
+ containerGap?: string;
+ defaultColumnCount?: number;
+ optionDirection?: string;
+ optionGap?: string;
+ optionHeight?: string;
+ optionSelectedBackground?: string;
+ optionSelectedBorder?: string;
+ optionText?: string;
+ optionUnselectedBackground?: string;
+ optionUnselectedBorder?: string;
+}
+
+export interface CheckboxCardOptionProps
+ extends Omit {
+ heightStyle?: string;
+ icon?: JSX.Element;
+ label: string;
+ onPress: (option: FieldOption) => void;
+ selected?: boolean;
+}
+
export interface CheckToggleProps extends ToggleProps {
disabled?: boolean;
errorMessage?: string;
@@ -53,12 +77,17 @@ export interface PhotoModalProps {
}
export interface RadioGroupProps {
+ containerGap?: string;
data: FieldOption[];
disabled?: boolean;
errorMessage?: string;
label?: string;
+ labelSize?: Extract;
onBlur?: () => void;
onChange: (item: FieldOption) => void;
+ optionsDirection?: string;
+ optionsGap?: string;
+ optionsYPadding?: string;
value?: FieldOption | null;
}
diff --git a/ui/src/components/atoms/button/button.tsx b/ui/src/components/atoms/button/button.tsx
index 8e146e7..748ca7f 100644
--- a/ui/src/components/atoms/button/button.tsx
+++ b/ui/src/components/atoms/button/button.tsx
@@ -9,7 +9,6 @@ import {
import { ActivityIndicator, Pressable, Text, View } from 'react-native';
const BUTTON_STYLES = ruffwind`
- w-full
justify-center
items-center
`;
@@ -21,7 +20,7 @@ export const Button = ({
loading,
onPress,
rounded = true,
- size = 'standard',
+ size = 'default',
state = 'default',
text = 'Continue',
iconRight,
@@ -38,15 +37,15 @@ export const Button = ({
let loadingColor = 'white';
switch (size) {
- case 'standard-short':
+ case 'xsmall':
+ width = `w-${horizontalScaleTW(150)}`;
height = `h-${moderateScaleTW(36)}`;
fontStyles = 'font-bodyBold text-b3';
break;
case 'small':
width = `w-${horizontalScaleTW(150)}`;
break;
- case 'small-short':
- width = `w-${horizontalScaleTW(150)}`;
+ case 'medium':
height = `h-${moderateScaleTW(36)}`;
fontStyles = 'font-bodyBold text-b3';
break;
diff --git a/ui/src/components/atoms/checkbox-card-option/checkbox-card-option.spec.tsx b/ui/src/components/atoms/checkbox-card-option/checkbox-card-option.spec.tsx
new file mode 100644
index 0000000..79b44a2
--- /dev/null
+++ b/ui/src/components/atoms/checkbox-card-option/checkbox-card-option.spec.tsx
@@ -0,0 +1,52 @@
+import { fireEvent, render } from '@testing-library/react-native';
+import { CheckboxCardOption } from './checkbox-card-option';
+
+describe('CheckboxCardOption', () => {
+ const mockOnPress = jest.fn();
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders with correct label and default state', () => {
+ const { getByText } = render(
+
+ );
+
+ const label = getByText('Test Option');
+ expect(label).toBeTruthy();
+ });
+
+ it('toggles selection state when pressed', () => {
+ const { getByText } = render(
+
+ );
+
+ const option = getByText('Test Option').parent.parent;
+
+ expect(option).not.toHaveStyle({
+ backgroundColor: '#F6E8FF',
+ borderColor: '#9525CB',
+ });
+
+ // Press the option
+ fireEvent.press(option.parent);
+
+ // Check if onPress was called with correct arguments
+ expect(mockOnPress).toHaveBeenCalledWith({
+ label: 'Test Option',
+ value: 'Test Option',
+ });
+
+ expect(option).toHaveStyle({
+ backgroundColor: '#F6E8FF',
+ borderColor: '#9525CB',
+ });
+
+ // Press again to toggle back
+ fireEvent.press(option.parent);
+
+ // Check if onPress was called again
+ expect(mockOnPress).toHaveBeenCalledTimes(2);
+ });
+});
diff --git a/ui/src/components/atoms/checkbox-card-option/checkbox-card-option.tsx b/ui/src/components/atoms/checkbox-card-option/checkbox-card-option.tsx
new file mode 100644
index 0000000..3093ffe
--- /dev/null
+++ b/ui/src/components/atoms/checkbox-card-option/checkbox-card-option.tsx
@@ -0,0 +1,55 @@
+import { ruffwind } from '@rufferal/tailwind';
+import { CheckboxCardOptionProps } from '@rufferal/types';
+import { moderateScaleTW } from '@rufferal/utils';
+import { useState } from 'react';
+import { Pressable, Text } from 'react-native';
+
+export const CheckboxCardOption = ({
+ defaultColumnCount = 3,
+ icon,
+ label,
+ onPress,
+ optionDirection = `flex-column`,
+ optionGap = `gap-${moderateScaleTW(8)}`,
+ optionHeight = `h-${moderateScaleTW(82)}`,
+ optionSelectedBackground = `bg-electricViolet-100`,
+ optionSelectedBorder = `
+ border-${moderateScaleTW(2)}
+ border-electricViolet-700
+ `,
+ optionText = `font-bodySemibold text-b2 text-electricViolet-950 text-center`,
+ optionUnselectedBackground = `bg-transparent`,
+ optionUnselectedBorder = `border-${moderateScaleTW(1)} border-chatelle-400`,
+ selected,
+}: CheckboxCardOptionProps) => {
+ const [isSelected, setIsSelected] = useState(selected || false);
+
+ const handlePress = () => {
+ setIsSelected(!isSelected);
+ onPress && onPress({ label, value: label });
+ };
+
+ return (
+
+ {icon}
+
+ {label}
+
+
+ );
+};
diff --git a/ui/src/components/atoms/field-input-area/field-input-area.tsx b/ui/src/components/atoms/field-input-area/field-input-area.tsx
index ecec2dd..a6e5446 100644
--- a/ui/src/components/atoms/field-input-area/field-input-area.tsx
+++ b/ui/src/components/atoms/field-input-area/field-input-area.tsx
@@ -1,6 +1,10 @@
import { ruffwind } from '@rufferal/tailwind';
import { FieldInputProps } from '@rufferal/types';
-import { GLOBAL_ICON_SIZE_SMALL, horizontalScaleTW, moderateScaleTW } from '@rufferal/utils';
+import {
+ GLOBAL_ICON_SIZE_SMALL,
+ horizontalScaleTW,
+ moderateScaleTW,
+} from '@rufferal/utils';
import { Image } from 'expo-image';
import { Platform, TextInput, View } from 'react-native';
@@ -8,7 +12,7 @@ export const FieldInputArea = ({
onChange,
onSubmit,
placeholder,
- size = 'standard',
+ size = 'default',
state = 'default',
value,
...inputProps
diff --git a/ui/src/components/atoms/field-input/field-input.tsx b/ui/src/components/atoms/field-input/field-input.tsx
index d509032..cdc83e2 100644
--- a/ui/src/components/atoms/field-input/field-input.tsx
+++ b/ui/src/components/atoms/field-input/field-input.tsx
@@ -12,13 +12,13 @@ export const FieldInput = ({
onChange,
onSubmit,
placeholder,
- size = 'standard',
+ size = 'default',
state = 'default',
value,
...inputProps
}: FieldInputProps) => {
const isMobile = Platform.OS === 'ios' || Platform.OS === 'android';
-
+
let width = `w-full`;
switch (size) {
case 'small':
diff --git a/ui/src/components/atoms/field-label/field-label.tsx b/ui/src/components/atoms/field-label/field-label.tsx
index c8a155d..c710a06 100644
--- a/ui/src/components/atoms/field-label/field-label.tsx
+++ b/ui/src/components/atoms/field-label/field-label.tsx
@@ -3,8 +3,21 @@ import { LabelProps } from '@rufferal/types';
import { capitalize } from '@rufferal/utils';
import { Text } from 'react-native';
-export const FieldLabel = ({ text, state }: LabelProps) => {
+export const FieldLabel = ({ text, size = 'default', state }: LabelProps) => {
+ let textSize = 'text-b1';
let textStyle = `text-balticSea-950`;
+
+ switch (size) {
+ case 'large':
+ textSize = `text-b2`;
+ break;
+ case 'xlarge':
+ textSize = `text-b3`;
+ break;
+ case '2xlarge':
+ textSize = `text-b4`;
+ break;
+ }
switch (state) {
case 'errored':
textStyle = `text-red-600`;
@@ -15,7 +28,7 @@ export const FieldLabel = ({ text, state }: LabelProps) => {
}
return (
-
+
{capitalize(text)}
);
diff --git a/ui/src/components/atoms/field-select/field-select.tsx b/ui/src/components/atoms/field-select/field-select.tsx
index bd24067..aa39498 100644
--- a/ui/src/components/atoms/field-select/field-select.tsx
+++ b/ui/src/components/atoms/field-select/field-select.tsx
@@ -28,7 +28,7 @@ export const FieldSelect = ({
onChange,
placeholder = 'Select...',
searchField,
- size = 'standard',
+ size = 'default',
state = 'default',
valueField,
value,
diff --git a/ui/src/components/atoms/index.ts b/ui/src/components/atoms/index.ts
index 42fc0d0..7f1f87b 100644
--- a/ui/src/components/atoms/index.ts
+++ b/ui/src/components/atoms/index.ts
@@ -16,6 +16,7 @@ export { Accordian } from './accordian/accordian';
export { Bottomsheet } from './bottomsheet/bottomsheet';
export { Button } from './button/button';
export { CheckLabel } from './check-label/check-label';
+export { CheckboxCardOption } from './checkbox-card-option/checkbox-card-option';
export { FieldCharacterCount } from './field-character-count/field-character-count';
export { FieldHelper } from './field-helper/field-helper';
export { FieldInputArea } from './field-input-area/field-input-area';
@@ -26,6 +27,7 @@ export { H2 } from './h2/h2';
export { H3 } from './h3/h3';
export { HorizontalDivider } from './horizontal-divider/horizontal-divider';
export { Item } from './item/item';
+export { LinkButton } from './link-button/link-button';
export { ProgressBar } from './progress-bar/progress-bar';
export { Radio } from './radio/radio';
export { RangeSlider } from './range-slider/range-slider';
diff --git a/ui/src/components/atoms/link-button/link-button.spec.tsx b/ui/src/components/atoms/link-button/link-button.spec.tsx
new file mode 100644
index 0000000..7257ecb
--- /dev/null
+++ b/ui/src/components/atoms/link-button/link-button.spec.tsx
@@ -0,0 +1,10 @@
+import { render } from '@testing-library/react-native';
+
+import { LinkButton } from './link-button';
+
+describe('LinkButton', () => {
+ it('should render successfully', () => {
+ const { root } = render();
+ expect(root).toBeTruthy();
+ });
+});
diff --git a/ui/src/components/atoms/link-button/link-button.tsx b/ui/src/components/atoms/link-button/link-button.tsx
new file mode 100644
index 0000000..e19fc9a
--- /dev/null
+++ b/ui/src/components/atoms/link-button/link-button.tsx
@@ -0,0 +1,45 @@
+// import React from 'react';
+import { ruffwind } from '@rufferal/tailwind';
+import { LinkButtonProps } from '@rufferal/types';
+import { horizontalScaleTW, moderateScaleTW } from '@rufferal/utils';
+import { Pressable, Text, View } from 'react-native';
+
+export const LinkButton = ({
+ containerStyles = `items-center`,
+ iconLeft,
+ iconRight,
+ onPress,
+ state = 'default',
+ text = 'Continue',
+ textStyles = `font-bodySemibold text-b3 text-saltBox-700`,
+ underlineStyles = `border-b-saltBox-700 border-b-${moderateScaleTW(1)}`,
+}: LinkButtonProps) => {
+ // Manage state themes
+ if (state === 'errored') {
+ textStyles = 'text-red-900';
+ } else if (state === 'disabled') {
+ textStyles = 'text-iron-500';
+ }
+
+ return (
+
+
+ {iconLeft && iconLeft}
+ {text}
+ {iconRight && iconRight}
+
+
+ );
+};
diff --git a/ui/src/components/atoms/radio/radio.tsx b/ui/src/components/atoms/radio/radio.tsx
index 37bff21..ad41b67 100644
--- a/ui/src/components/atoms/radio/radio.tsx
+++ b/ui/src/components/atoms/radio/radio.tsx
@@ -26,7 +26,8 @@ export const Radio = ({ item, onPress, selected = false }: RadioProps) => {
return (
onPress(item)}
id={item.id}
>
diff --git a/ui/src/components/molecules/checkbox-card/checkbox-card.spec.tsx b/ui/src/components/molecules/checkbox-card/checkbox-card.spec.tsx
new file mode 100644
index 0000000..26bd3ac
--- /dev/null
+++ b/ui/src/components/molecules/checkbox-card/checkbox-card.spec.tsx
@@ -0,0 +1,28 @@
+import { fireEvent, render } from '@testing-library/react-native';
+
+import { CheckboxCard } from './checkbox-card';
+
+describe('CheckboxCard', () => {
+ const handlePress = jest.fn();
+
+ const testComponent = (
+
+
+
+ );
+
+ it('should render successfully', () => {
+ const { root } = render(testComponent);
+ expect(root).toBeTruthy();
+ });
+
+ it('calls onPress when an option is pressed', () => {
+ const { getByText } = render(testComponent);
+
+ fireEvent.press(getByText('Test option'));
+ expect(handlePress).toHaveBeenCalledWith({
+ label: 'Test option',
+ value: 'Test option',
+ });
+ });
+});
diff --git a/ui/src/components/molecules/checkbox-card/checkbox-card.tsx b/ui/src/components/molecules/checkbox-card/checkbox-card.tsx
new file mode 100644
index 0000000..9541cec
--- /dev/null
+++ b/ui/src/components/molecules/checkbox-card/checkbox-card.tsx
@@ -0,0 +1,59 @@
+import { ruffwind } from '@rufferal/tailwind';
+import { CheckboxCardOptionProps, CheckboxCardProps } from '@rufferal/types';
+import { moderateScaleTW } from '@rufferal/utils';
+import {
+ Children,
+ cloneElement,
+ isValidElement,
+ PropsWithChildren,
+ ReactElement,
+} from 'react';
+import { View } from 'react-native';
+
+import { CheckboxCardOption } from '../../atoms';
+
+const CheckboxCard = ({
+ children,
+ containerDirection = `flex-row`,
+ containerGap = `gap-${moderateScaleTW(8)}`,
+ optionDirection,
+ optionGap,
+ optionHeight,
+ optionSelectedBackground,
+ optionSelectedBorder,
+ optionText,
+ optionUnselectedBackground,
+ optionUnselectedBorder,
+}: PropsWithChildren) => {
+ const childrenWithStyles = Children.map(children, (child) => {
+ if (isValidElement(child)) {
+ return cloneElement(child as ReactElement, {
+ optionHeight,
+ optionDirection,
+ optionGap,
+ optionSelectedBackground,
+ optionSelectedBorder,
+ optionText,
+ optionUnselectedBackground,
+ optionUnselectedBorder,
+ });
+ }
+ return child;
+ });
+
+ return (
+
+ {childrenWithStyles}
+
+ );
+};
+
+CheckboxCard.Option = CheckboxCardOption;
+
+export { CheckboxCard };
diff --git a/ui/src/components/molecules/index.ts b/ui/src/components/molecules/index.ts
index 23def51..f1f2124 100644
--- a/ui/src/components/molecules/index.ts
+++ b/ui/src/components/molecules/index.ts
@@ -11,6 +11,7 @@
*/
export { CheckToggle } from './check-toggle/check-toggle';
+export { CheckboxCard } from './checkbox-card/checkbox-card';
export { InputArea } from './input-area/input-area';
export { InputPhoto } from './input-photo/input-photo';
export { InputSlider } from './input-slider/input-slider';
diff --git a/ui/src/components/molecules/input-area/input-area.tsx b/ui/src/components/molecules/input-area/input-area.tsx
index de999ba..a991d3e 100644
--- a/ui/src/components/molecules/input-area/input-area.tsx
+++ b/ui/src/components/molecules/input-area/input-area.tsx
@@ -16,7 +16,7 @@ export const InputArea = ({
label,
maxCharacters = 250,
onChange,
- size = 'standard',
+ size = 'default',
...inputProps
}: InputAreaProps) => {
let state: FieldState = 'default';
diff --git a/ui/src/components/molecules/input/input.tsx b/ui/src/components/molecules/input/input.tsx
index 1a45407..cdabe51 100644
--- a/ui/src/components/molecules/input/input.tsx
+++ b/ui/src/components/molecules/input/input.tsx
@@ -8,7 +8,7 @@ export const Input = ({
disabled = false,
errorMessage,
label,
- size = 'standard',
+ size = 'default',
...inputProps
}: InputProps) => {
let state: FieldState = 'default';
diff --git a/ui/src/components/molecules/radio-group/radio-group.tsx b/ui/src/components/molecules/radio-group/radio-group.tsx
index 9be0666..0f22010 100644
--- a/ui/src/components/molecules/radio-group/radio-group.tsx
+++ b/ui/src/components/molecules/radio-group/radio-group.tsx
@@ -5,11 +5,16 @@ import { View } from 'react-native';
import { FieldHelper, FieldLabel, Radio } from '../../atoms';
export const RadioGroup = ({
+ containerGap = `gap-${moderateScaleTW(8)}`,
data,
disabled,
errorMessage,
label,
+ labelSize,
onChange,
+ optionsDirection = `flex-column`,
+ optionsGap = `gap-${moderateScaleTW(8)}`,
+ optionsYPadding,
value,
}: RadioGroupProps) => {
let state: FieldState = 'default';
@@ -27,18 +32,20 @@ export const RadioGroup = ({
};
return (
-
- {label && }
- {data.map((item) => {
- return (
-
- );
- })}
+
+ {label && }
+
+ {data.map((item) => {
+ return (
+
+ );
+ })}
+
{errorMessage && }
);
diff --git a/ui/src/components/molecules/select/select.tsx b/ui/src/components/molecules/select/select.tsx
index 2fa4f22..e8eef65 100644
--- a/ui/src/components/molecules/select/select.tsx
+++ b/ui/src/components/molecules/select/select.tsx
@@ -10,7 +10,7 @@ export const Select = ({
errorMessage,
label,
other,
- size = 'standard',
+ size = 'default',
...selectProps
}: SelectProps) => {
let state: FieldState = 'default';
@@ -23,7 +23,7 @@ export const Select = ({
// Handle optional "Other" option
if (other && Object.keys(other).length > 0) {
- const filteredOthers = data.filter(item => item.id !== 'other');
+ const filteredOthers = data.filter((item) => item.id !== 'other');
filteredOthers.push({
id: 'other',
label: other.label,
diff --git a/ui/src/components/organisms/caretaker-search-header/caretaker-search-header.tsx b/ui/src/components/organisms/caretaker-search-header/caretaker-search-header.tsx
index fe2618b..6cfd4b8 100644
--- a/ui/src/components/organisms/caretaker-search-header/caretaker-search-header.tsx
+++ b/ui/src/components/organisms/caretaker-search-header/caretaker-search-header.tsx
@@ -36,7 +36,7 @@ export const CaretakerSearchHeader = ({ navigation }: PageNavigationProps) => (
in{` `}
- San Antonio, TX
+ {'{'}location{'}'}
{
diff --git a/ui/src/components/pages/profile/profile-splash/profile-splash.tsx b/ui/src/components/pages/profile/profile-splash/profile-splash.tsx
index fb71b19..4efe804 100644
--- a/ui/src/components/pages/profile/profile-splash/profile-splash.tsx
+++ b/ui/src/components/pages/profile/profile-splash/profile-splash.tsx
@@ -54,7 +54,7 @@ const ProfileSplashSheet = ({ navigation }: PageNavigationProps) => {
onPress={() => navigation.navigate('Search Dashboard')}
text="Skip for now"
type="transparent"
- size="standard-short"
+ size="medium"
rounded={false}
/>
diff --git a/ui/src/components/pages/search/search-dashboard/search-dashboard.tsx b/ui/src/components/pages/search/search-dashboard/search-dashboard.tsx
index 7097085..cea21ae 100644
--- a/ui/src/components/pages/search/search-dashboard/search-dashboard.tsx
+++ b/ui/src/components/pages/search/search-dashboard/search-dashboard.tsx
@@ -20,6 +20,7 @@ export const SearchDashboard = observer(
Platform.OS === 'ios' && `pt-${moderateScaleTW(insets.top)}`
)}
>
+ {/* BLARG:TODO: update from tailwind config */}
{/* HEADER + FILTERS + ALL RESULTS */}
-// style={ruffwind``}
+const TIME_OF_DAY_OPTIONS: FieldOption[] = [
+ {
+ id: 'tod-morning',
+ label: 'Morning',
+ value: 'morning',
+ },
+ {
+ id: 'tod-midday',
+ label: 'Mid-day',
+ value: 'midday',
+ },
+ {
+ id: 'tod-afternoon',
+ label: 'Afternoon',
+ value: 'afternoon',
+ },
+ {
+ id: 'tod-evening',
+ label: 'Evening',
+ value: 'evening',
+ },
+];
export const SearchFilters = observer(({ navigation }: PageNavigationProps) => {
+ const [isDog, setIsDog] = useState(true);
+ const [isCat, setIsCat] = useState(false);
+ const [isDogWalking, setIsDogWalking] = useState(true);
+ const [isCatHarness, setIsCatHarness] = useState(false);
+ const [isPlayDate, setIsPlayDate] = useState(false);
+ const [isFeeding, setIsFeeding] = useState(false);
+ const [isOvernight, setIsOvernight] = useState(false);
+ const [selectedCareOptions, setSelectedCareOptions] = useState([
+ 'isDogWalking',
+ ]);
+
+ const handleDog = (option: FieldOption) => {
+ setIsDog((prev) => !prev);
+ };
+ const handleCat = (option: FieldOption) => {
+ setIsCat((prev) => !prev);
+ };
+
+ const handleDogWalking = (option: FieldOption) => {
+ setIsDogWalking((prev) => !prev);
+ handleCareOptionChange('isDogWalking');
+ };
+ const handleCatHarness = (option: FieldOption) => {
+ setIsCatHarness((prev) => !prev);
+ handleCareOptionChange('isCatHarness');
+ };
+ const handlePlayDate = (option: FieldOption) => {
+ setIsPlayDate((prev) => !prev);
+ handleCareOptionChange('isPlayDate');
+ };
+ const handleFeeding = (option: FieldOption) => {
+ setIsFeeding((prev) => !prev);
+ handleCareOptionChange('isFeeding');
+ };
+ const handleOvernight = (option: FieldOption) => {
+ setIsOvernight((prev) => !prev);
+ handleCareOptionChange('isOvernight');
+ };
+
+ const handleCareOptionChange = (option: string) => {
+ setSelectedCareOptions((prevOptions) => {
+ if (prevOptions.includes(option)) {
+ // Deselect the option if it's already selected
+ return prevOptions.filter((item) => item !== option);
+ } else {
+ // Add the option if it's not selected
+ return [...prevOptions, option];
+ }
+ });
+ };
+
+ const determineExerciseOption = () => {
+ if ((isCat && isDog) || (!isCat && !isDog)) {
+ return undefined;
+ } else if (isCat) {
+ return (
+
+ }
+ label="Harness"
+ selected={isCatHarness}
+ />
+ );
+ } else {
+ return (
+
+ }
+ label="Walking"
+ selected={isDogWalking}
+ />
+ );
+ }
+ };
+
+ const renderHelperText = (text: string) => (
+
+ {text}
+
+ );
+
+ const renderCareHelperText = () => {
+ const helperTexts: Record = {
+ noSelection:
+ 'To generate better search results, please select at least one care type',
+ isDogWalking: 'A 30- or 60-minute walk in your neighborhood.',
+ isCatHarness:
+ 'A 30-minute harness walk in your home, backyard or neighborhood.',
+ isPlayDate:
+ 'Up to 60 minutes of one-on-one play time in your home, backyard or neighborhood.',
+ isFeeding:
+ '30-minute drop-in visit for food, water and treats. Includes administering medications, as needed.',
+ isOvernight:
+ 'Overnight pet sitting, including feeding and play time. Walking must be scheduled separately.',
+ };
+
+ const displayText =
+ selectedCareOptions.length > 0
+ ? helperTexts[selectedCareOptions[selectedCareOptions.length - 1]]
+ : helperTexts.noSelection;
+
+ return renderHelperText(displayText);
+ };
+
return (
-
+ navigation.navigate('Search Dashboard')}
+ >
- {/* Header and Search */}
-
- {/* Header */}
-
+
-
+ {/* BLARG:TODO - with form handling, this may convert to a molecule like Input */}
+
+
+
+
+ }
+ label="Dogs"
+ selected={isDog}
+ />
+
+ }
+ label="Cats"
+ selected={isCat}
+ />
+
+
+ {/* BLARG:TODO - with form handling, this may convert to a molecule like Input */}
+
+
+
+ {determineExerciseOption()}
+
+ }
+ label="Play date"
+ selected={isPlayDate}
+ />
+
+ }
+ label="Feeding"
+ selected={isFeeding}
+ />
+
+ }
+ label="Overnight"
+ selected={isOvernight}
+ />
+ {renderCareHelperText()}
+
+
+
+
+ console.log('BLARG add date')}
+ onSubmit={() => null}
+ value=""
/>
-
-
- in{` `}
-
-
- San Antonio, TX
-
-
-
-
-
- Edit location
-
-
-
-
- {/* Search */}
-
- THE STUFF - FILL OUT
-
-
- {/* Navigation */}
-
-
-
-
-
- Search
-
-
-
-
-
+ Recurring weekly
+
+ null}
+ onChange={() => console.log('BLARG add toggle handling')}
+ handleChange={() => console.log('BLARG add toggle handling')}
/>
-
- Bookings
-
-
-
-
-
-
- Messages
-
+
+ null}
+ onChange={() =>
+ console.log('BLARG add checkbox and time of day handling')
+ }
+ optionsDirection={`flex-row`}
+ optionsYPadding={`py-${moderateScaleTW(7)}`}
+ value={null}
+ />
-
-
-
-
-
- Account
-
+
+
+ console.log('BLARG add location search')}
+ onSubmit={() => null}
+ value=""
+ />
+
+ console.log('BLARG add clear all behavior')}
+ />
+ console.log('BLARG add submit behavior')}
+ />
+
-
+
);
});
diff --git a/ui/src/components/templates/feature-template/feature-template.tsx b/ui/src/components/templates/feature-template/feature-template.tsx
index e912a4d..cd716d1 100644
--- a/ui/src/components/templates/feature-template/feature-template.tsx
+++ b/ui/src/components/templates/feature-template/feature-template.tsx
@@ -13,7 +13,7 @@ export const FeatureTemplate = ({
paddingX = `px-${horizontalScaleTW(20)}`,
forwardNavigation,
}: PropsWithChildren) => {
- let paddingY = `pt-${verticalScaleTW(32)}`;
+ let paddingY = `pt-${verticalScaleTW(32)} pb-${verticalScaleTW(16)}`;
switch (Platform.OS) {
case 'android':
paddingY = `pt-${verticalScaleTW(48)}`;
@@ -25,6 +25,7 @@ export const FeatureTemplate = ({
return (
+ {/* */}
{backNavigation && (