From 9030be040cfa9ae44b0d575701347055f112a47e Mon Sep 17 00:00:00 2001 From: Boxuan Lin Date: Fri, 21 Nov 2025 20:40:59 +1100 Subject: [PATCH 1/7] remove modal components --- components/external-link.tsx | 25 --------- components/hello-wave.tsx | 19 ------- components/parallax-scroll-view.tsx | 79 ----------------------------- 3 files changed, 123 deletions(-) delete mode 100644 components/external-link.tsx delete mode 100644 components/hello-wave.tsx delete mode 100644 components/parallax-scroll-view.tsx diff --git a/components/external-link.tsx b/components/external-link.tsx deleted file mode 100644 index 883e515..0000000 --- a/components/external-link.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Href, Link } from 'expo-router'; -import { openBrowserAsync, WebBrowserPresentationStyle } from 'expo-web-browser'; -import { type ComponentProps } from 'react'; - -type Props = Omit, 'href'> & { href: Href & string }; - -export function ExternalLink({ href, ...rest }: Props) { - return ( - { - if (process.env.EXPO_OS !== 'web') { - // Prevent the default behavior of linking to the default browser on native. - event.preventDefault(); - // Open the link in an in-app browser. - await openBrowserAsync(href, { - presentationStyle: WebBrowserPresentationStyle.AUTOMATIC, - }); - } - }} - /> - ); -} diff --git a/components/hello-wave.tsx b/components/hello-wave.tsx deleted file mode 100644 index 5def547..0000000 --- a/components/hello-wave.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import Animated from 'react-native-reanimated'; - -export function HelloWave() { - return ( - - 👋 - - ); -} diff --git a/components/parallax-scroll-view.tsx b/components/parallax-scroll-view.tsx deleted file mode 100644 index 6f674a7..0000000 --- a/components/parallax-scroll-view.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import type { PropsWithChildren, ReactElement } from 'react'; -import { StyleSheet } from 'react-native'; -import Animated, { - interpolate, - useAnimatedRef, - useAnimatedStyle, - useScrollOffset, -} from 'react-native-reanimated'; - -import { ThemedView } from '@/components/themed-view'; -import { useColorScheme } from '@/hooks/use-color-scheme'; -import { useThemeColor } from '@/hooks/use-theme-color'; - -const HEADER_HEIGHT = 250; - -type Props = PropsWithChildren<{ - headerImage: ReactElement; - headerBackgroundColor: { dark: string; light: string }; -}>; - -export default function ParallaxScrollView({ - children, - headerImage, - headerBackgroundColor, -}: Props) { - const backgroundColor = useThemeColor({}, 'background'); - const colorScheme = useColorScheme() ?? 'light'; - const scrollRef = useAnimatedRef(); - const scrollOffset = useScrollOffset(scrollRef); - const headerAnimatedStyle = useAnimatedStyle(() => { - return { - transform: [ - { - translateY: interpolate( - scrollOffset.value, - [-HEADER_HEIGHT, 0, HEADER_HEIGHT], - [-HEADER_HEIGHT / 2, 0, HEADER_HEIGHT * 0.75] - ), - }, - { - scale: interpolate(scrollOffset.value, [-HEADER_HEIGHT, 0, HEADER_HEIGHT], [2, 1, 1]), - }, - ], - }; - }); - - return ( - - - {headerImage} - - {children} - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - }, - header: { - height: HEADER_HEIGHT, - overflow: 'hidden', - }, - content: { - flex: 1, - padding: 32, - gap: 16, - overflow: 'hidden', - }, -}); From aebdd05d94955d2ba0c382dc743ae519d5b008dc Mon Sep 17 00:00:00 2001 From: Boxuan Lin Date: Fri, 21 Nov 2025 20:45:51 +1100 Subject: [PATCH 2/7] remove modal page --- app/_layout.tsx | 1 - app/modal.tsx | 29 ----------------------------- 2 files changed, 30 deletions(-) delete mode 100644 app/modal.tsx diff --git a/app/_layout.tsx b/app/_layout.tsx index 08161fc..4e7d06c 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -16,7 +16,6 @@ export default function RootLayout() { - diff --git a/app/modal.tsx b/app/modal.tsx deleted file mode 100644 index 6dfbc1a..0000000 --- a/app/modal.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Link } from 'expo-router'; -import { StyleSheet } from 'react-native'; - -import { ThemedText } from '@/components/themed-text'; -import { ThemedView } from '@/components/themed-view'; - -export default function ModalScreen() { - return ( - - This is a modal - - Go to home screen - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - padding: 20, - }, - link: { - marginTop: 15, - paddingVertical: 15, - }, -}); From 0fd08a78e0e3d6aabe29f13dc87c1ac4d3b09708 Mon Sep 17 00:00:00 2001 From: Boxuan Lin Date: Sat, 22 Nov 2025 19:33:33 +1100 Subject: [PATCH 3/7] finish adding types for kopi, ready for UI --- types/kopi.ts | 67 ++++++++++++++++++++++++++++++++++++++++++++++ utils/kopiInfer.ts | 62 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 types/kopi.ts create mode 100644 utils/kopiInfer.ts diff --git a/types/kopi.ts b/types/kopi.ts new file mode 100644 index 0000000..28a5d11 --- /dev/null +++ b/types/kopi.ts @@ -0,0 +1,67 @@ +// Define the option groups for kopi customization +export const optionGroups = { + milkiness: ["none", "condensed", "evaporated"], + sweetness: ["no_sugar", "less_sweet", "normal", "sweeter"], + strength: ["weak", "normal", "strong", "stronger_no_water"], + state: ["warm", "lukewarm", "iced"], +} as const; + +// Define TypeScript types based on the option groups +export type Milkiness = (typeof optionGroups.milkiness)[number]; +export type Sweetness = (typeof optionGroups.sweetness)[number]; +export type Strength = (typeof optionGroups.strength)[number]; +export type State = (typeof optionGroups.state)[number]; + +// Define a type for a complete kopi selection +export type KopiSelection = { + milkiness: Milkiness; + sweetness: Sweetness; + strength: Strength; + state: State; +}; + +// Example default kopi selection +export const DEFAULT_SELECTION: KopiSelection = { + milkiness: "condensed", + sweetness: "normal", + strength: "normal", + state: "warm", +}; + +// Define a generic option item type +export type OptionItem = { + code: T; + label: string; // label for UI display + hint?: string; // optional, local term or explanation, e.g., "(Gao)" +}; + +/* 6) Four groups of UI options (directly used for Picker map rendering) */ +export const MILKINESS_OPTIONS: OptionItem[] = [ + { code: "none", label: "No milk", hint: "(O)" }, + { code: "condensed", label: "Condensed milk" }, + { code: "evaporated", label: "Evaporated milk", hint: "(C/Si)" }, +]; + +export const SWEETNESS_OPTIONS: OptionItem[] = [ + { code: "no_sugar", label: "No sugar", hint: "(Kosong)" }, + { code: "less_sweet", label: "Less sweet", hint: "(Siew Dai)" }, + { code: "normal", label: "Normal" }, + { code: "sweeter", label: "Sweeter", hint: "(Gah Dai)" }, +]; + +export const STRENGTH_OPTIONS: OptionItem[] = [ + { code: "weak", label: "Weak", hint: "(Po)" }, + { code: "normal", label: "Normal" }, + { code: "strong", label: "Strong", hint: "(Gao)" }, + { code: "stronger_no_water", label: "Stronger, no water", hint: "(Di Lo)" }, +]; + +export const STATE_OPTIONS: OptionItem[] = [ + { code: "warm", label: "Warm" }, + { code: "lukewarm", label: "Lukewarm", hint: "(Pua Sio)" }, + { code: "iced", label: "Iced", hint: "(Peng)" }, +]; + + + + diff --git a/utils/kopiInfer.ts b/utils/kopiInfer.ts new file mode 100644 index 0000000..e9c502c --- /dev/null +++ b/utils/kopiInfer.ts @@ -0,0 +1,62 @@ +// utils/kopiInfer.ts +import type { + Milkiness, + Sweetness, + Strength, + State, + KopiSelection, +} from "@/types/kopi"; + +// milkiness -> base +export const MILKINESS_TO_BASE = { + none: "Kopi O", + condensed: "Kopi", + evaporated: "Kopi C", +} as const satisfies Record; + +// sweetness -> suffix +export const SWEETNESS_TO_SUFFIX = { + no_sugar: "Kosong", + less_sweet: "Siew Dai", + normal: "", + sweeter: "Gah Dai", +} as const satisfies Record; + +// strength -> suffix +export const STRENGTH_TO_SUFFIX = { + weak: "Po", + normal: "", + strong: "Gao", + stronger_no_water: "Di Lo", +} as const satisfies Record; + +// state -> suffix +export const STATE_TO_SUFFIX = { + warm: "", + lukewarm: "Pua Sio", + iced: "Peng", +} as const satisfies Record; + +// Build the base name from a KopiSelection +export function buildBaseName(sel: KopiSelection) { + return MILKINESS_TO_BASE[sel.milkiness]; +} + +// Build the full order phrase from a KopiSelection +export function buildOrderPhrase(sel: KopiSelection) { + const base = buildBaseName(sel); + + const parts = [ + base, + STRENGTH_TO_SUFFIX[sel.strength], + STATE_TO_SUFFIX[sel.state], + SWEETNESS_TO_SUFFIX[sel.sweetness], + ].filter(Boolean); + + return parts.join(" "); +} + +// Build a display name (same as base name for now) +export function buildDisplayName(sel: KopiSelection) { + return buildBaseName(sel); +} \ No newline at end of file From 5509d351eba3ebe8b4d9131165c92b4832b7cc51 Mon Sep 17 00:00:00 2001 From: Boxuan Lin Date: Sun, 23 Nov 2025 14:09:05 +1100 Subject: [PATCH 4/7] finish adding global state for kopi, ready to develop UI --- stores/kopiMakerStore.ts | 118 +++++++++++++++++++++++++++++++++++++++ types/kopi.ts | 11 ++-- utils/kopiInfer.ts | 12 ++-- 3 files changed, 129 insertions(+), 12 deletions(-) create mode 100644 stores/kopiMakerStore.ts diff --git a/stores/kopiMakerStore.ts b/stores/kopiMakerStore.ts new file mode 100644 index 0000000..f793bb3 --- /dev/null +++ b/stores/kopiMakerStore.ts @@ -0,0 +1,118 @@ +//store/kopiMakerStore.ts +import { create } from "zustand"; +import type { + KopiSelection, + Milkiness, + Sweetness, + Strength, + Temperature, +} from "@/types/kopi"; +import { DEFAULT_SELECTION } from "@/types/kopi"; +import { + buildBaseName, + buildOrderPhrase, + buildDisplayName, +} from "@/utils/kopiInfer"; + +/** + * Zustand store shape for Kopi Maker. + * + * This store keeps the current kopi selection (4 dimensions), + * provides setters for each dimension, and exposes derived helpers + * to build local kopi names and phrases. + */ +type KopiMakerStore = KopiSelection & { + /** + * Update milkiness selection. + * @param v The new milkiness code. + */ + setMilkiness: (v: Milkiness) => void; + + /** + * Update sweetness selection. + * @param v The new sweetness code. + */ + setSweetness: (v: Sweetness) => void; + + /** + * Update strength selection. + * @param v The new strength code. + */ + setStrength: (v: Strength) => void; + + /** + * Update temperature selection. + * @param v The new temperature code. + */ + setTemperature: (v: Temperature) => void; + + /** + * Reset all selections back to the default values. + */ + reset: () => void; + + /** + * Derived base name computed from current selection. + * Example: "Kopi", "Kopi O", "Kopi C". + * @returns The base name string. + */ + baseName: () => string; + + /** + * Derived display name for showing in UI. + * Currently the same as baseName, but reserved for future extensions + * such as Cham or Size. + * @returns The display name string. + */ + displayName: () => string; + + /** + * Derived full local order phrase. + * Example: "Kopi O Gao Peng Siew Dai". + * @returns The full order phrase string. + */ + phrase: () => string; +}; + + /** + * Hook to access Kopi Maker store. + * + * State: + * - milkiness, sweetness, strength, temperature + * + * Actions: + * - setMilkiness, setSweetness, setStrength, setTemperature, reset + * + * Derived helpers: + * - baseName, displayName, phrase + */ +export const useKopiMakerStore = create()((set, get) => ({ + /** + * Initial selection values. + */ + ...DEFAULT_SELECTION, + + /** @inheritdoc */ + setMilkiness: (v) => set({ milkiness: v }), + + /** @inheritdoc */ + setSweetness: (v) => set({ sweetness: v }), + + /** @inheritdoc */ + setStrength: (v) => set({ strength: v }), + + /** @inheritdoc */ + setTemperature: (v) => set({ temperature: v }), + + /** @inheritdoc */ + reset: () => set(DEFAULT_SELECTION), + + /** @inheritdoc */ + baseName: () => buildBaseName(get()), + + /** @inheritdoc */ + displayName: () => buildDisplayName(get()), + + /** @inheritdoc */ + phrase: () => buildOrderPhrase(get()), +})); diff --git a/types/kopi.ts b/types/kopi.ts index 28a5d11..e084fc1 100644 --- a/types/kopi.ts +++ b/types/kopi.ts @@ -3,21 +3,21 @@ export const optionGroups = { milkiness: ["none", "condensed", "evaporated"], sweetness: ["no_sugar", "less_sweet", "normal", "sweeter"], strength: ["weak", "normal", "strong", "stronger_no_water"], - state: ["warm", "lukewarm", "iced"], + temperature: ["warm", "lukewarm", "iced"], } as const; // Define TypeScript types based on the option groups export type Milkiness = (typeof optionGroups.milkiness)[number]; export type Sweetness = (typeof optionGroups.sweetness)[number]; export type Strength = (typeof optionGroups.strength)[number]; -export type State = (typeof optionGroups.state)[number]; +export type Temperature = (typeof optionGroups.temperature)[number]; // Define a type for a complete kopi selection export type KopiSelection = { milkiness: Milkiness; sweetness: Sweetness; strength: Strength; - state: State; + temperature: Temperature; }; // Example default kopi selection @@ -25,7 +25,7 @@ export const DEFAULT_SELECTION: KopiSelection = { milkiness: "condensed", sweetness: "normal", strength: "normal", - state: "warm", + temperature: "warm", }; // Define a generic option item type @@ -56,7 +56,7 @@ export const STRENGTH_OPTIONS: OptionItem[] = [ { code: "stronger_no_water", label: "Stronger, no water", hint: "(Di Lo)" }, ]; -export const STATE_OPTIONS: OptionItem[] = [ +export const TEMPERATURE_OPTIONS: OptionItem[] = [ { code: "warm", label: "Warm" }, { code: "lukewarm", label: "Lukewarm", hint: "(Pua Sio)" }, { code: "iced", label: "Iced", hint: "(Peng)" }, @@ -64,4 +64,3 @@ export const STATE_OPTIONS: OptionItem[] = [ - diff --git a/utils/kopiInfer.ts b/utils/kopiInfer.ts index e9c502c..6fb113c 100644 --- a/utils/kopiInfer.ts +++ b/utils/kopiInfer.ts @@ -3,7 +3,7 @@ import type { Milkiness, Sweetness, Strength, - State, + Temperature, KopiSelection, } from "@/types/kopi"; @@ -30,12 +30,12 @@ export const STRENGTH_TO_SUFFIX = { stronger_no_water: "Di Lo", } as const satisfies Record; -// state -> suffix -export const STATE_TO_SUFFIX = { +// temperature -> suffix +export const TEMPERATURE_TO_SUFFIX = { warm: "", lukewarm: "Pua Sio", iced: "Peng", -} as const satisfies Record; +} as const satisfies Record; // Build the base name from a KopiSelection export function buildBaseName(sel: KopiSelection) { @@ -49,7 +49,7 @@ export function buildOrderPhrase(sel: KopiSelection) { const parts = [ base, STRENGTH_TO_SUFFIX[sel.strength], - STATE_TO_SUFFIX[sel.state], + TEMPERATURE_TO_SUFFIX[sel.temperature], SWEETNESS_TO_SUFFIX[sel.sweetness], ].filter(Boolean); @@ -59,4 +59,4 @@ export function buildOrderPhrase(sel: KopiSelection) { // Build a display name (same as base name for now) export function buildDisplayName(sel: KopiSelection) { return buildBaseName(sel); -} \ No newline at end of file +} From 075d39fb8c14930bf344de0aace053488f45bbe0 Mon Sep 17 00:00:00 2001 From: Boxuan Lin Date: Sun, 23 Nov 2025 14:15:30 +1100 Subject: [PATCH 5/7] update Agents.md --- AGENTS.md | 28 ++++++++++++++++++++++++++++ AGENTS.md | 28 ---------------------------- 2 files changed, 28 insertions(+), 28 deletions(-) create mode 100644 AGENTS.md delete mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..29711a2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,28 @@ +## Runtime & Framework +- Expo SDK `~54.0.25`, React `19.1.0`, React Native `0.81.5`, web via `react-native-web ~0.21.0`. +- `expo-router ~6.0.15` entry (`main: expo-router/entry`), React Navigation 7.x stack libs present. + +## Routing & Navigation +- Root stack in `app/_layout.tsx`; tabs group in `app/(tabs)/_layout.tsx` uses custom bottom bar. +- Tabs: Home, Favorites, Kopi (center FAB-style), Orders, Me. Custom tab bar has rounded background, inset, and lifted Kopi button sized via constants at top of the file. +- Modal example removed (deleted `app/modal.tsx` and stack registration). + +## Styling +- NativeWind `^4.2.1` with Babel `jsxImportSource: "nativewind"` + `nativewind/babel`; Metro wrapped with `withNativeWind(config, { input: "./global.css" })`. +- Tailwind `^3.4.18`; `tailwind.config.js` scans `./App.tsx`, `./app/**/*`, `./components/**/*`. Colors extended to coffee palette via CSS variables in `global.css` (currently only light-mode values). + +## State & Domain +- Zustand store `stores/kopiMakerStore.ts`: holds kopi selection (`milkiness`, `sweetness`, `strength`, `temperature`), setters/reset, derived `baseName/displayName/phrase` via `utils/kopiInfer`. +- Domain types `types/kopi.ts`: option groups, option lists, `KopiSelection`, default selection. Temperature replaces earlier “state” naming. +- `utils/kopiInfer.ts`: maps selection to kopi naming parts. + +## UI Pages +- Tab screens (`app/(tabs)/*.tsx`) are placeholders using Tailwind color tokens; Home/Kopi/Favorites/Orders/Me minimal content. +- Template components `hello-wave`, `parallax-scroll-view`, `external-link` removed. `haptic-tab.tsx` remains for tab haptics; `themed-text`/`themed-view` only used in deleted modal. + +## Tooling +- TypeScript `~5.9.2`, strict with `@/*` alias. ESLint `^9.25.0` + `eslint-config-expo ~10.0.0`; `prettier-plugin-tailwindcss ^0.5.14`. +- Metro customized only for NativeWind; Babel preset is `babel-preset-expo`. + +## Platform Config +- app.json: slug/name `kopi-shop`, scheme `kopishop`, portrait, automatic UI mode, static web output. diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index caccf6c..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,28 +0,0 @@ -## Runtime & Framework -- Expo SDK `~54.0.23` with `newArchEnabled: true`, typed routes, and React Compiler enabled. -- React `19.1.0`, React Native `0.81.5`, web output via `react-native-web ~0.21.0` and `react-dom 19.1.0`. - -## Routing & Navigation -- `expo-router ~6.0.14` as the entry point (`main: expo-router/entry`) and registered in app.json plugins. -- React Navigation 7.x stack: `@react-navigation/native ^7.1.8`, `@react-navigation/bottom-tabs ^7.4.0`, `@react-navigation/elements ^2.6.3`, plus `react-native-screens ~4.16.0` and `react-native-safe-area-context ~5.4.0`. - -## Styling -- NativeWind `^4.2.1`; Babel uses `babel-preset-expo` with `jsxImportSource: "nativewind"` and `nativewind/babel`; Metro is wrapped with `withNativeWind(config, { input: "./global.css" })`. -- TailwindCSS `^3.4.18`; `global.css` contains `@tailwind` directives. `tailwind.config.js` currently scans only `App.tsx` and `components/**/*`, so expand `content` if writing classes in `app/**/*`, `hooks/**/*`, etc. - -## Animation & Gestures -- `react-native-reanimated ~3.17.5` (use updated hooks such as `useScrollViewOffset`). -- `react-native-gesture-handler ~2.28.0`, `expo-haptics ~15.0.7`, `expo-image ~3.0.10`. - -## State & Utilities -- Zustand `^5.0.8`. - -## Tooling -- TypeScript `~5.9.2` with `strict: true` and alias `@/* -> ./*`; `nativewind-env.d.ts` adds typings. -- ESLint `^9.25.0` + `eslint-config-expo ~10.0.0`, formatting with `prettier-plugin-tailwindcss ^0.5.14`. -- Metro config only customizes NativeWind; Babel preset remains `babel-preset-expo`. - -## Platform Config -- app.json: slug/name `kopi-shop`, scheme `kopishop`, portrait orientation, automatic UI mode. -- Splash and icons handled by `expo-splash-screen` plugin and Android adaptive icon assets. -- Web bundler is Metro with `"output": "static"`. From 1bd6f72500aaf3cd874e97019dfbdf44f629eb0e Mon Sep 17 00:00:00 2001 From: Boxuan Lin Date: Sun, 23 Nov 2025 15:46:44 +1100 Subject: [PATCH 6/7] finish designing and implementing the basic UI for kopi.tsx --- app/(tabs)/kopi.tsx | 115 ++++++++++++++++++++++++++++- components/kopi/KopiCupPreview.tsx | 26 +++++++ components/kopi/OptionSelector.tsx | 50 +++++++++++++ constants/theme.ts | 4 + global.css | 3 + package.json | 1 + pnpm-lock.yaml | 106 +++++++++++++++++++++++++- tailwind.config.js | 3 + 8 files changed, 301 insertions(+), 7 deletions(-) create mode 100644 components/kopi/KopiCupPreview.tsx create mode 100644 components/kopi/OptionSelector.tsx diff --git a/app/(tabs)/kopi.tsx b/app/(tabs)/kopi.tsx index 36ecf97..f6ccce6 100644 --- a/app/(tabs)/kopi.tsx +++ b/app/(tabs)/kopi.tsx @@ -1,9 +1,118 @@ -import { Text, View } from 'react-native'; +import { KopiCupPreview } from "@/components/kopi/KopiCupPreview"; +import { OptionSelector } from "@/components/kopi/OptionSelector"; +import { useKopiMakerStore } from "@/stores/kopiMakerStore"; +import { Colors } from "@/constants/theme"; +import { useColorScheme } from "@/hooks/use-color-scheme"; +import { + MILKINESS_OPTIONS, + STRENGTH_OPTIONS, + SWEETNESS_OPTIONS, + TEMPERATURE_OPTIONS, +} from "@/types/kopi"; +import React from "react"; +import { Dimensions, Pressable, ScrollView, Text, View } from "react-native"; +import Svg, { Path } from "react-native-svg"; + +const { width } = Dimensions.get("window"); +const CURVE_HEIGHT = 80; +const CURVE_DEPTH = 50; +const SVG_H = CURVE_HEIGHT + CURVE_DEPTH; export default function KopiScreen() { + const { + milkiness, + sweetness, + strength, + temperature, + setMilkiness, + setSweetness, + setStrength, + setTemperature, + displayName, + } = useKopiMakerStore(); + + const colorScheme = useColorScheme() ?? "light"; + const topBg = Colors[colorScheme].background; + return ( - - Kopi Screen + + {/* Top Section: Preview (40%) */} + + + + {/* 微笑曲线分界 */} + + + + + + + + + {/* Bottom Section: Controls (60%) */} + + + + + + + + + + {/* Brew Button */} + + console.log("Brewing:", displayName())} + > + + BREW + + + ); } diff --git a/components/kopi/KopiCupPreview.tsx b/components/kopi/KopiCupPreview.tsx new file mode 100644 index 0000000..3aa6d8e --- /dev/null +++ b/components/kopi/KopiCupPreview.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Dimensions, Text, View } from 'react-native'; + +interface KopiCupPreviewProps { + name: string; +} + +const { width } = Dimensions.get('window'); +const curveRadius = width; // big radius to form a smooth smile at the bottom edge + +export const KopiCupPreview = ({ name }: KopiCupPreviewProps) => { + return ( + + + + {name} + + + + ); +}; diff --git a/components/kopi/OptionSelector.tsx b/components/kopi/OptionSelector.tsx new file mode 100644 index 0000000..4efe416 --- /dev/null +++ b/components/kopi/OptionSelector.tsx @@ -0,0 +1,50 @@ +import type { OptionItem } from '@/types/kopi'; +import React from 'react'; +import { Pressable, Text, View } from 'react-native'; + +interface OptionSelectorProps { + label: string; + options: OptionItem[]; + value: T; + onChange: (value: T) => void; +} + +export const OptionSelector = ({ + label, + options, + value, + onChange, +}: OptionSelectorProps) => { + return ( + + {label} + + {options.map((option) => { + const isSelected = option.code === value; + return ( + onChange(option.code)} + className={`px-4 py-2 rounded-md border ${ + isSelected + ? 'bg-dark-coffee border-dark-coffee' + : 'bg-cream border-grey' + }`} + > + + {option.label} + + + ); + })} + + + ); +}; diff --git a/constants/theme.ts b/constants/theme.ts index 6786d5b..4934b68 100644 --- a/constants/theme.ts +++ b/constants/theme.ts @@ -14,6 +14,10 @@ const Palette = { grey: '#BCAAA4', white: '#FFFFFF', black: '#000000', + // New colors for Kopi Maker UI + cream: '#F9F5F0', // Lighter background for top section + warmBeige: '#EFEBE9', // Slightly darker background for bottom section + accent: '#D4A017', // Gold/Condensed Milk accent }; export const Colors = { diff --git a/global.css b/global.css index 3cc8733..f448191 100644 --- a/global.css +++ b/global.css @@ -9,5 +9,8 @@ --color-light-coffee: #D7CCC8; --color-medium-coffee: #8D6E63; --color-grey: #BCAAA4; + --color-cream: #F9F5F0; + --color-warm-beige: #EFEBE9; + --color-accent: #D4A017; } } \ No newline at end of file diff --git a/package.json b/package.json index f316e00..a371deb 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "react-native-reanimated": "~4.1.5", "react-native-safe-area-context": "~5.6.2", "react-native-screens": "~4.16.0", + "react-native-svg": "^15.12.1", "react-native-web": "~0.21.0", "react-native-worklets": "0.5.1", "zustand": "^5.0.8" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 529a562..df3074c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -58,7 +58,7 @@ importers: version: 15.0.9(expo@54.0.25)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)) nativewind: specifier: ^4.2.1 - version: 4.2.1(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(tailwindcss@3.4.18(yaml@2.8.1)) + version: 4.2.1(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(tailwindcss@3.4.18(yaml@2.8.1)) react: specifier: 19.1.0 version: 19.1.0 @@ -80,6 +80,9 @@ importers: react-native-screens: specifier: ~4.16.0 version: 4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react-native-svg: + specifier: ^15.12.1 + version: 15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-web: specifier: ~0.21.0 version: 0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -1690,6 +1693,9 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + bplist-creator@0.1.0: resolution: {integrity: sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==} @@ -1887,6 +1893,17 @@ packages: css-in-js-utils@3.1.0: resolution: {integrity: sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==} + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-tree@1.1.3: + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} + engines: {node: '>=8.0.0'} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -1992,6 +2009,19 @@ packages: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + dotenv-expand@11.0.7: resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} engines: {node: '>=12'} @@ -2027,6 +2057,10 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + env-editor@0.4.2: resolution: {integrity: sha512-ObFo8v4rQJAE59M69QzwloxPZtd33TpYEIjtKD1rrFDcM1Gd7IkDxEBU+HriziN6HSHQnBJi8Dmy+JWkav5HKA==} engines: {node: '>=8'} @@ -3091,6 +3125,9 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdn-data@2.0.14: + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + memoize-one@5.2.1: resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} @@ -3334,6 +3371,9 @@ packages: resolution: {integrity: sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==} engines: {node: ^16.14.0 || >=18.0.0} + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} @@ -3746,6 +3786,12 @@ packages: react: '*' react-native: '*' + react-native-svg@15.12.1: + resolution: {integrity: sha512-vCuZJDf8a5aNC2dlMovEv4Z0jjEUET53lm/iILFnFewa15b4atjVxU6Wirm6O9y6dEsdjDZVD7Q3QM4T1wlI8g==} + peerDependencies: + react: '*' + react-native: '*' + react-native-web@0.21.2: resolution: {integrity: sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==} peerDependencies: @@ -6606,6 +6652,8 @@ snapshots: binary-extensions@2.3.0: {} + boolbase@1.0.0: {} + bplist-creator@0.1.0: dependencies: stream-buffers: 2.2.0 @@ -6833,6 +6881,21 @@ snapshots: dependencies: hyphenate-style-name: 1.1.0 + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-tree@1.1.3: + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 + + css-what@6.2.2: {} + cssesc@3.0.0: {} csstype@3.2.2: {} @@ -6911,6 +6974,24 @@ snapshots: dependencies: esutils: 2.0.3 + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dotenv-expand@11.0.7: dependencies: dotenv: 16.4.7 @@ -6937,6 +7018,8 @@ snapshots: encodeurl@2.0.0: {} + entities@4.5.0: {} + env-editor@0.4.2: {} error-stack-parser@2.1.4: @@ -8158,6 +8241,8 @@ snapshots: math-intrinsics@1.1.0: {} + mdn-data@2.0.14: {} + memoize-one@5.2.1: {} memoize-one@6.0.0: {} @@ -8565,11 +8650,11 @@ snapshots: napi-postinstall@0.3.4: {} - nativewind@4.2.1(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(tailwindcss@3.4.18(yaml@2.8.1)): + nativewind@4.2.1(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(tailwindcss@3.4.18(yaml@2.8.1)): dependencies: comment-json: 4.4.1 debug: 4.4.3 - react-native-css-interop: 0.2.1(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(tailwindcss@3.4.18(yaml@2.8.1)) + react-native-css-interop: 0.2.1(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(tailwindcss@3.4.18(yaml@2.8.1)) tailwindcss: 3.4.18(yaml@2.8.1) transitivePeerDependencies: - react @@ -8606,6 +8691,10 @@ snapshots: semver: 7.7.3 validate-npm-package-name: 5.0.1 + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + nullthrows@1.1.1: {} ob1@0.83.2: @@ -8906,7 +8995,7 @@ snapshots: react-is@19.2.0: {} - react-native-css-interop@0.2.1(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(tailwindcss@3.4.18(yaml@2.8.1)): + react-native-css-interop@0.2.1(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(tailwindcss@3.4.18(yaml@2.8.1)): dependencies: '@babel/helper-module-imports': 7.27.1 '@babel/traverse': 7.28.5 @@ -8920,6 +9009,7 @@ snapshots: tailwindcss: 3.4.18(yaml@2.8.1) optionalDependencies: react-native-safe-area-context: 5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react-native-svg: 15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) transitivePeerDependencies: - supports-color @@ -8958,6 +9048,14 @@ snapshots: react-native-is-edge-to-edge: 1.2.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) warn-once: 0.1.1 + react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + dependencies: + css-select: 5.2.2 + css-tree: 1.1.3 + react: 19.1.0 + react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0) + warn-once: 0.1.1 + react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.28.4 diff --git a/tailwind.config.js b/tailwind.config.js index 5b6bf81..e8a018d 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -15,6 +15,9 @@ content: [ 'light-coffee': 'var(--color-light-coffee)', 'medium-coffee': 'var(--color-medium-coffee)', 'grey': 'var(--color-grey)', + 'cream': 'var(--color-cream)', + 'warm-beige': 'var(--color-warm-beige)', + 'accent': 'var(--color-accent)', }, }, }, From 636db6e9bf45dc52765b4fbb17c516ef448912b6 Mon Sep 17 00:00:00 2001 From: Boxuan Lin Date: Sun, 23 Nov 2025 16:11:43 +1100 Subject: [PATCH 7/7] enable real time kopi name viewing --- app/(tabs)/kopi.tsx | 28 ++++++++++++---------------- components/kopi/index.ts | 2 ++ types/kopi.ts | 2 +- 3 files changed, 15 insertions(+), 17 deletions(-) create mode 100644 components/kopi/index.ts diff --git a/app/(tabs)/kopi.tsx b/app/(tabs)/kopi.tsx index f6ccce6..b2566c8 100644 --- a/app/(tabs)/kopi.tsx +++ b/app/(tabs)/kopi.tsx @@ -1,5 +1,4 @@ -import { KopiCupPreview } from "@/components/kopi/KopiCupPreview"; -import { OptionSelector } from "@/components/kopi/OptionSelector"; +import { KopiCupPreview, OptionSelector } from "@/components/kopi"; import { useKopiMakerStore } from "@/stores/kopiMakerStore"; import { Colors } from "@/constants/theme"; import { useColorScheme } from "@/hooks/use-color-scheme"; @@ -9,7 +8,6 @@ import { SWEETNESS_OPTIONS, TEMPERATURE_OPTIONS, } from "@/types/kopi"; -import React from "react"; import { Dimensions, Pressable, ScrollView, Text, View } from "react-native"; import Svg, { Path } from "react-native-svg"; @@ -19,17 +17,15 @@ const CURVE_DEPTH = 50; const SVG_H = CURVE_HEIGHT + CURVE_DEPTH; export default function KopiScreen() { - const { - milkiness, - sweetness, - strength, - temperature, - setMilkiness, - setSweetness, - setStrength, - setTemperature, - displayName, - } = useKopiMakerStore(); + const milkiness = useKopiMakerStore((s) => s.milkiness); + const sweetness = useKopiMakerStore((s) => s.sweetness); + const strength = useKopiMakerStore((s) => s.strength); + const temperature = useKopiMakerStore((s) => s.temperature); + const setMilkiness = useKopiMakerStore((s) => s.setMilkiness); + const setSweetness = useKopiMakerStore((s) => s.setSweetness); + const setStrength = useKopiMakerStore((s) => s.setStrength); + const setTemperature = useKopiMakerStore((s) => s.setTemperature); + const phrase = useKopiMakerStore((s) => s.phrase()); const colorScheme = useColorScheme() ?? "light"; const topBg = Colors[colorScheme].background; @@ -38,7 +34,7 @@ export default function KopiScreen() { {/* Top Section: Preview (40%) */} - + {/* 微笑曲线分界 */} @@ -106,7 +102,7 @@ export default function KopiScreen() { console.log("Brewing:", displayName())} + onPress={() => console.log("Brewing:", phrase)} > BREW diff --git a/components/kopi/index.ts b/components/kopi/index.ts new file mode 100644 index 0000000..92acb26 --- /dev/null +++ b/components/kopi/index.ts @@ -0,0 +1,2 @@ +export * from './KopiCupPreview'; +export * from './OptionSelector'; diff --git a/types/kopi.ts b/types/kopi.ts index e084fc1..f7cebcb 100644 --- a/types/kopi.ts +++ b/types/kopi.ts @@ -53,7 +53,7 @@ export const STRENGTH_OPTIONS: OptionItem[] = [ { code: "weak", label: "Weak", hint: "(Po)" }, { code: "normal", label: "Normal" }, { code: "strong", label: "Strong", hint: "(Gao)" }, - { code: "stronger_no_water", label: "Stronger, no water", hint: "(Di Lo)" }, + { code: "stronger_no_water", label: "No water", hint: "(Di Lo)" }, ]; export const TEMPERATURE_OPTIONS: OptionItem[] = [