diff --git a/package-lock.json b/package-lock.json index 90c60e5..813af64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "react-native-reanimated": "~4.1.1", "react-native-safe-area-context": "~5.6.0", "react-native-screens": "~4.16.0", + "react-native-svg": "15.12.1", "react-native-web": "^0.21.0", "react-native-worklets": "^0.6.1" }, @@ -4291,6 +4292,12 @@ "node": ">=0.6" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, "node_modules/bplist-creator": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz", @@ -4922,6 +4929,56 @@ "hyphenate-style-name": "^1.0.3" } }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-tree/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -5057,6 +5114,61 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dotenv": { "version": "16.4.7", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", @@ -5130,6 +5242,18 @@ "node": ">= 0.8" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-editor": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/env-editor/-/env-editor-0.4.2.tgz", @@ -7895,6 +8019,12 @@ "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==", "license": "Apache-2.0" }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "license": "CC0-1.0" + }, "node_modules/memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", @@ -8791,6 +8921,18 @@ "node": ">=8" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/nullthrows": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", @@ -9722,6 +9864,21 @@ "react-native": "*" } }, + "node_modules/react-native-svg": { + "version": "15.12.1", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.12.1.tgz", + "integrity": "sha512-vCuZJDf8a5aNC2dlMovEv4Z0jjEUET53lm/iILFnFewa15b4atjVxU6Wirm6O9y6dEsdjDZVD7Q3QM4T1wlI8g==", + "license": "MIT", + "dependencies": { + "css-select": "^5.1.0", + "css-tree": "^1.1.3", + "warn-once": "0.1.1" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-web": { "version": "0.21.1", "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.1.tgz", diff --git a/package.json b/package.json index dc49bd1..65b620f 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "react-native-reanimated": "~4.1.1", "react-native-safe-area-context": "~5.6.0", "react-native-screens": "~4.16.0", + "react-native-svg": "15.12.1", "react-native-web": "^0.21.0", "react-native-worklets": "^0.6.1" }, diff --git a/src/components/PotteryIcon.tsx b/src/components/PotteryIcon.tsx new file mode 100644 index 0000000..4c74ea8 --- /dev/null +++ b/src/components/PotteryIcon.tsx @@ -0,0 +1,195 @@ +import React from 'react'; +import { View, StyleSheet } from 'react-native'; + +interface PotteryIconProps { + color: string; + finish?: 'glossy' | 'matte' | 'satin' | 'unknown'; + size?: number; +} + +export const PotteryIcon: React.FC = ({ + color, + finish = 'unknown', + size = 60 +}) => { + // Different pottery shapes based on finish type using View components + const renderShape = () => { + const shapeStyle = { + backgroundColor: color, + width: size, + height: size, + }; + + switch (finish) { + case 'glossy': + // Vase shape - wider at bottom, narrower at top + return ( + + + + + + {/* Glossy shine effect */} + + + ); + + case 'matte': + // Bowl shape - smooth trapezoid with many layers + const numLayers = 20; + const startWidth = 0.9; // Top width (90% of size) + const endWidth = 0.4; // Bottom width (40% of size) + const layerHeight = size / numLayers; + + return ( + + {Array.from({ length: numLayers }).map((_, index) => { + // Calculate width for this layer (gradually narrowing from top to bottom) + const progress = index / (numLayers - 1); + const layerWidth = startWidth - (startWidth - endWidth) * progress; + + const isFirst = index === 0; + const isLast = index === numLayers - 1; + + return ( + + ); + })} + + ); + + case 'satin': + // Mug shape - cylinder with handle + return ( + + + + + + + + + ); + + default: + // Simple circular swatch for unknown finish + return ( + + ); + } + }; + + return ( + + {renderShape()} + + ); +}; + +const styles = StyleSheet.create({ + // Vase styles + vaseTop: { + borderTopLeftRadius: 5, + borderTopRightRadius: 5, + borderWidth: 1, + borderColor: '#8B7355', + }, + vaseNeck: { + borderLeftWidth: 1, + borderRightWidth: 1, + borderColor: '#8B7355', + }, + vaseBody: { + borderLeftWidth: 1, + borderRightWidth: 1, + borderColor: '#8B7355', + borderRadius: 8, + }, + vaseBase: { + borderBottomLeftRadius: 5, + borderBottomRightRadius: 5, + borderWidth: 1, + borderColor: '#8B7355', + }, + shine: { + position: 'absolute', + width: 8, + height: 12, + backgroundColor: 'rgba(255, 255, 255, 0.4)', + borderRadius: 4, + }, + + // Bowl styles - trapezoid bowl shape + bowlTop: { + borderTopWidth: 2, + borderLeftWidth: 2, + borderRightWidth: 2, + borderColor: '#8B7355', + marginTop: -1, // Overlap to connect + }, + bowlLayer: { + borderLeftWidth: 2, + borderRightWidth: 2, + borderColor: '#8B7355', + marginTop: -1, // Overlap to connect smoothly + }, + bowlBase: { + borderBottomWidth: 2, + borderLeftWidth: 2, + borderRightWidth: 2, + borderColor: '#8B7355', + marginTop: -1, // Overlap to connect + }, + + // Mug styles + mugRim: { + borderTopLeftRadius: 5, + borderTopRightRadius: 5, + borderTopWidth: 2, + borderLeftWidth: 1, + borderRightWidth: 1, + borderColor: '#8B7355', + }, + mugBody: { + borderLeftWidth: 1, + borderRightWidth: 1, + borderColor: '#8B7355', + }, + mugBase: { + borderBottomLeftRadius: 5, + borderBottomRightRadius: 5, + borderBottomWidth: 2, + borderLeftWidth: 1, + borderRightWidth: 1, + borderColor: '#8B7355', + }, + handle: { + width: 15, + height: 25, + borderWidth: 2, + borderLeftWidth: 0, + borderRadius: 8, + backgroundColor: 'transparent', + }, + + // Default circle + circle: { + borderRadius: 100, + borderWidth: 2, + borderColor: '#8B7355', + }, +}); diff --git a/src/components/index.ts b/src/components/index.ts index 55d1507..0a90405 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,3 +1,4 @@ export * from './Button'; export * from './Card'; export * from './Input'; +export * from './PotteryIcon'; diff --git a/src/screens/GlazeMixerScreen.tsx b/src/screens/GlazeMixerScreen.tsx index 528f288..cd1f349 100644 --- a/src/screens/GlazeMixerScreen.tsx +++ b/src/screens/GlazeMixerScreen.tsx @@ -14,7 +14,7 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { RootStackParamList } from '../navigation/types'; import { Glaze } from '../types'; import { getAllGlazes, searchGlazes } from '../lib/db/repositories'; -import { Button, Card } from '../components'; +import { Button, Card, PotteryIcon } from '../components'; import { colors, spacing, typography, borderRadius } from '../lib/theme'; import { mixColors, generateMixName } from '../lib/utils'; import { useAuth } from '../contexts/AuthContext'; @@ -128,8 +128,14 @@ export const GlazeMixerScreen: React.FC = () => { toggleGlaze(item)}> - {item.color && ( - + {item.color ? ( + + ) : ( + )} @@ -161,7 +167,11 @@ export const GlazeMixerScreen: React.FC = () => { Mix Preview - + {selectedGlazes.map(g => g.name).join(' + ')} diff --git a/src/screens/GlazePickerScreen.tsx b/src/screens/GlazePickerScreen.tsx index d503b33..8b2d4a9 100644 --- a/src/screens/GlazePickerScreen.tsx +++ b/src/screens/GlazePickerScreen.tsx @@ -13,7 +13,7 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { RootStackParamList } from '../navigation/types'; import { Glaze } from '../types'; import { getAllGlazes, searchGlazes } from '../lib/db/repositories'; -import { Button, Card } from '../components'; +import { Button, Card, PotteryIcon } from '../components'; import { colors, spacing, typography, borderRadius } from '../lib/theme'; import { useAuth } from '../contexts/AuthContext'; @@ -100,8 +100,14 @@ export const GlazePickerScreen: React.FC = () => { toggleGlaze(item.id)}> - {item.color && ( - + {item.color ? ( + + ) : ( + )} diff --git a/src/screens/ProjectsScreen.tsx b/src/screens/ProjectsScreen.tsx index 8e6e685..5700014 100644 --- a/src/screens/ProjectsScreen.tsx +++ b/src/screens/ProjectsScreen.tsx @@ -281,7 +281,6 @@ const styles = StyleSheet.create({ backgroundColor: colors.background, }, header: { - paddingHorizontal: spacing.lg, paddingTop: 64, paddingBottom: spacing.lg, backgroundColor: colors.background, @@ -294,6 +293,7 @@ const styles = StyleSheet.create({ color: colors.primary, letterSpacing: 0.5, textTransform: 'uppercase', + paddingHorizontal: spacing.lg, }, searchContainer: { flexDirection: 'row', @@ -359,10 +359,10 @@ const styles = StyleSheet.create({ color: colors.text, }, listContent: { - paddingHorizontal: spacing.md, paddingBottom: 100, // Extra space for floating tab bar }, projectCard: { + marginHorizontal: spacing.md, marginBottom: spacing.md, }, imageContainer: {