feat : Composant Bouton
This commit is contained in:
178
package-lock.json
generated
178
package-lock.json
generated
@@ -9,6 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo-google-fonts/inter": "^0.4.2",
|
"@expo-google-fonts/inter": "^0.4.2",
|
||||||
|
"@expo-google-fonts/jetbrains-mono": "^0.4.1",
|
||||||
"@expo-google-fonts/space-grotesk": "^0.4.1",
|
"@expo-google-fonts/space-grotesk": "^0.4.1",
|
||||||
"@expo/ui": "~56.0.14",
|
"@expo/ui": "~56.0.14",
|
||||||
"@react-navigation/bottom-tabs": "^7.18.18",
|
"@react-navigation/bottom-tabs": "^7.18.18",
|
||||||
@@ -33,15 +34,17 @@
|
|||||||
"expo-system-ui": "~56.0.5",
|
"expo-system-ui": "~56.0.5",
|
||||||
"expo-web-browser": "~56.0.5",
|
"expo-web-browser": "~56.0.5",
|
||||||
"lucide-react": "^1.38.0",
|
"lucide-react": "^1.38.0",
|
||||||
|
"lucide-react-native": "^1.38.0",
|
||||||
"nativewind": "^4.2.6",
|
"nativewind": "^4.2.6",
|
||||||
"prettier-plugin-tailwindcss": "^0.5.14",
|
"prettier-plugin-tailwindcss": "^0.5.14",
|
||||||
"react": "19.2.3",
|
"react": "19.2.3",
|
||||||
"react-dom": "19.2.3",
|
"react-dom": "19.2.3",
|
||||||
"react-native": "0.85.3",
|
"react-native": "0.85.3",
|
||||||
"react-native-gesture-handler": "~2.31.1",
|
"react-native-gesture-handler": "~2.31.1",
|
||||||
"react-native-reanimated": "^4.3.1",
|
"react-native-reanimated": "4.3.1",
|
||||||
"react-native-safe-area-context": "~5.7.0",
|
"react-native-safe-area-context": "~5.7.0",
|
||||||
"react-native-screens": "4.25.2",
|
"react-native-screens": "4.25.2",
|
||||||
|
"react-native-svg": "15.15.4",
|
||||||
"react-native-web": "~0.21.0",
|
"react-native-web": "~0.21.0",
|
||||||
"react-native-worklets": "0.8.3",
|
"react-native-worklets": "0.8.3",
|
||||||
"tailwindcss": "^3.4.19"
|
"tailwindcss": "^3.4.19"
|
||||||
@@ -1288,6 +1291,12 @@
|
|||||||
"integrity": "sha512-syfiImMaDmq7cFi0of+waE2M4uSCyd16zgyWxdPOY7fN2VBmSLKEzkfbZgeOjJq61kSqPBNNtXjggiQiSD6gMQ==",
|
"integrity": "sha512-syfiImMaDmq7cFi0of+waE2M4uSCyd16zgyWxdPOY7fN2VBmSLKEzkfbZgeOjJq61kSqPBNNtXjggiQiSD6gMQ==",
|
||||||
"license": "MIT AND OFL-1.1"
|
"license": "MIT AND OFL-1.1"
|
||||||
},
|
},
|
||||||
|
"node_modules/@expo-google-fonts/jetbrains-mono": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@expo-google-fonts/jetbrains-mono/-/jetbrains-mono-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-CslACrtMOcRwoWXCO7OMEI+9w3fukWSoBtvNz46OqPoogEuuoY0tkDY1O8sFumk8t0pC6Cx0Xr95O0TOQhpkug==",
|
||||||
|
"license": "MIT AND OFL-1.1"
|
||||||
|
},
|
||||||
"node_modules/@expo-google-fonts/material-symbols": {
|
"node_modules/@expo-google-fonts/material-symbols": {
|
||||||
"version": "0.4.38",
|
"version": "0.4.38",
|
||||||
"resolved": "https://registry.npmjs.org/@expo-google-fonts/material-symbols/-/material-symbols-0.4.38.tgz",
|
"resolved": "https://registry.npmjs.org/@expo-google-fonts/material-symbols/-/material-symbols-0.4.38.tgz",
|
||||||
@@ -3656,6 +3665,12 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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": {
|
"node_modules/bplist-creator": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz",
|
||||||
@@ -4139,6 +4154,56 @@
|
|||||||
"hyphenate-style-name": "^1.0.3"
|
"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/css.escape": {
|
"node_modules/css.escape": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
|
||||||
@@ -4285,6 +4350,61 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"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/ee-first": {
|
"node_modules/ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
@@ -4312,6 +4432,18 @@
|
|||||||
"node": ">= 0.8"
|
"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/error-stack-parser": {
|
"node_modules/error-stack-parser": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
|
||||||
@@ -6655,6 +6787,17 @@
|
|||||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lucide-react-native": {
|
||||||
|
"version": "1.38.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lucide-react-native/-/lucide-react-native-1.38.0.tgz",
|
||||||
|
"integrity": "sha512-wOgCXC8VZxIkrDK1160JA+7cBQ9njTFnFLQHLvrxLI5Cdhgz/dNN0j9+MS9aUJnYdBi0y3mNj73ZLnuzV6pYPg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-native": "*",
|
||||||
|
"react-native-svg": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lz-string": {
|
"node_modules/lz-string": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||||
@@ -6680,6 +6823,12 @@
|
|||||||
"integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==",
|
"integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==",
|
||||||
"license": "Apache-2.0"
|
"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": {
|
"node_modules/memoize-one": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
|
||||||
@@ -7315,6 +7464,18 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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": {
|
"node_modules/nullthrows": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz",
|
||||||
@@ -8585,6 +8746,21 @@
|
|||||||
"react-native": ">=0.82.0"
|
"react-native": ">=0.82.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-native-svg": {
|
||||||
|
"version": "15.15.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.15.4.tgz",
|
||||||
|
"integrity": "sha512-boT/vIRgj6zZKBpfTPJJiYWMbZE9duBMOwPK6kCSTgxsS947IFMOq9OgIFkpWZTB7t229H24pDRkh3W9ZK/J1A==",
|
||||||
|
"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": {
|
"node_modules/react-native-web": {
|
||||||
"version": "0.21.2",
|
"version": "0.21.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo-google-fonts/inter": "^0.4.2",
|
"@expo-google-fonts/inter": "^0.4.2",
|
||||||
|
"@expo-google-fonts/jetbrains-mono": "^0.4.1",
|
||||||
"@expo-google-fonts/space-grotesk": "^0.4.1",
|
"@expo-google-fonts/space-grotesk": "^0.4.1",
|
||||||
"@expo/ui": "~56.0.14",
|
"@expo/ui": "~56.0.14",
|
||||||
"@react-navigation/bottom-tabs": "^7.18.18",
|
"@react-navigation/bottom-tabs": "^7.18.18",
|
||||||
@@ -28,15 +29,17 @@
|
|||||||
"expo-system-ui": "~56.0.5",
|
"expo-system-ui": "~56.0.5",
|
||||||
"expo-web-browser": "~56.0.5",
|
"expo-web-browser": "~56.0.5",
|
||||||
"lucide-react": "^1.38.0",
|
"lucide-react": "^1.38.0",
|
||||||
|
"lucide-react-native": "^1.38.0",
|
||||||
"nativewind": "^4.2.6",
|
"nativewind": "^4.2.6",
|
||||||
"prettier-plugin-tailwindcss": "^0.5.14",
|
"prettier-plugin-tailwindcss": "^0.5.14",
|
||||||
"react": "19.2.3",
|
"react": "19.2.3",
|
||||||
"react-dom": "19.2.3",
|
"react-dom": "19.2.3",
|
||||||
"react-native": "0.85.3",
|
"react-native": "0.85.3",
|
||||||
"react-native-gesture-handler": "~2.31.1",
|
"react-native-gesture-handler": "~2.31.1",
|
||||||
"react-native-reanimated": "^4.3.1",
|
"react-native-reanimated": "4.3.1",
|
||||||
"react-native-safe-area-context": "~5.7.0",
|
"react-native-safe-area-context": "~5.7.0",
|
||||||
"react-native-screens": "4.25.2",
|
"react-native-screens": "4.25.2",
|
||||||
|
"react-native-svg": "15.15.4",
|
||||||
"react-native-web": "~0.21.0",
|
"react-native-web": "~0.21.0",
|
||||||
"react-native-worklets": "0.8.3",
|
"react-native-worklets": "0.8.3",
|
||||||
"tailwindcss": "^3.4.19"
|
"tailwindcss": "^3.4.19"
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
import { Stack } from 'expo-router';
|
import { Stack } from 'expo-router';
|
||||||
|
|
||||||
import { ThemeProvider } from '../theme';
|
import { ThemeProvider } from '../theme';
|
||||||
|
import { useAppFonts } from '../theme/typography';
|
||||||
|
|
||||||
export default function RootLayout() {
|
export default function RootLayout() {
|
||||||
|
const [fontsLoaded] = useAppFonts();
|
||||||
|
|
||||||
|
if (!fontsLoaded) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<Stack />
|
<Stack />
|
||||||
|
|||||||
@@ -1,48 +1,137 @@
|
|||||||
import { StyleSheet, Text, View } from 'react-native';
|
import { ArrowUp, ArrowUpRight, Plus } from 'lucide-react-native';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { ScrollView, StyleSheet, Text, View } from 'react-native';
|
||||||
|
|
||||||
|
import { Button } from '@/components/Button';
|
||||||
|
import { useTheme } from '@/theme/useTheme';
|
||||||
|
|
||||||
import '../global.css';
|
import '../global.css';
|
||||||
import { useTheme } from '../theme';
|
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
const { colors, spacing, typography } = useTheme();
|
const { colors, spacing, typography } = useTheme();
|
||||||
|
|
||||||
|
const simulateSubmit = useCallback(async () => {
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
setTimeout(resolve, 1300);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={[styles.screen, { backgroundColor: colors.deepBackground }]}>
|
||||||
|
<ScrollView
|
||||||
|
contentContainerStyle={[
|
||||||
|
styles.content,
|
||||||
|
{
|
||||||
|
padding: spacing[24],
|
||||||
|
gap: spacing[24],
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
>
|
||||||
|
<View style={{ gap: spacing[8] }}>
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
styles.title,
|
typography.sectionLabel,
|
||||||
{
|
{
|
||||||
color: colors.textSecondary,
|
color: colors.textSecondary,
|
||||||
fontFamily: typography.sectionLabel.fontFamily,
|
letterSpacing: 1.1,
|
||||||
fontSize: typography.sectionLabel.fontSize,
|
|
||||||
lineHeight: typography.sectionLabel.lineHeight,
|
|
||||||
letterSpacing: typography.sectionLabel.letterSpacing,
|
|
||||||
},
|
},
|
||||||
]}>
|
]}
|
||||||
GradientButton variants
|
>
|
||||||
|
BOUTONS · HAUTEUR 52 PX · RAYON 14 PX
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.row}>
|
||||||
|
<View style={styles.cell}>
|
||||||
|
<Button label="Publier" variant="primary" />
|
||||||
|
<Text style={[styles.caption, typography.digits, { color: colors.textSecondary }]}>Primaire</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.cell}>
|
||||||
|
<Button label="Publier" variant="primary" />
|
||||||
|
<Text style={[styles.caption, typography.digits, { color: colors.textSecondary }]}>Pressé (tap)</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.row}>
|
||||||
|
<View style={styles.cell}>
|
||||||
|
<Button label="Intéressé" variant="secondary" rightIcon={ArrowUpRight} />
|
||||||
|
<Text style={[styles.caption, typography.digits, { color: colors.textSecondary }]}>Secondaire</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.cell}>
|
||||||
|
<Button label="Publier" variant="primary" disabled />
|
||||||
|
<Text style={[styles.caption, typography.digits, { color: colors.textSecondary }]}>Désactivé</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.row}>
|
||||||
|
<View style={styles.cell}>
|
||||||
|
<Button label="Tout voir" variant="ghost" fullWidth={false} style={styles.autoWidthButton} />
|
||||||
|
<Text style={[styles.caption, typography.digits, { color: colors.textSecondary }]}>Texte</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.cell}>
|
||||||
|
<Button label="Envoyer" variant="primary" autoLoading loadingLabel="Envoi..." onPress={simulateSubmit} />
|
||||||
|
<Text style={[styles.caption, typography.digits, { color: colors.textSecondary }]}>Chargement</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.row}>
|
||||||
|
<View style={styles.cell}>
|
||||||
|
<Button label="Supprimer" variant="destructive" />
|
||||||
|
<Text style={[styles.caption, typography.digits, { color: colors.textSecondary }]}>Destructif</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.cell}>
|
||||||
|
<View style={styles.iconRow}>
|
||||||
|
<Button
|
||||||
|
iconOnly
|
||||||
|
variant="secondary"
|
||||||
|
fullWidth={false}
|
||||||
|
icon={ArrowUp}
|
||||||
|
iconOnlyShape="default"
|
||||||
|
accessibilityLabel="Partager"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
iconOnly
|
||||||
|
variant="primary"
|
||||||
|
fullWidth={false}
|
||||||
|
icon={Plus}
|
||||||
|
iconOnlyShape="round"
|
||||||
|
accessibilityLabel="Ajouter"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<Text style={[styles.caption, typography.digits, { color: colors.textSecondary }]}>Icône / FAB</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
safeArea: {
|
screen: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
container: {
|
content: {
|
||||||
|
paddingBottom: 40,
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: 14,
|
||||||
|
},
|
||||||
|
cell: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
paddingHorizontal: 20,
|
gap: 10,
|
||||||
paddingTop: 24,
|
},
|
||||||
|
caption: {
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
iconRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
gap: 12,
|
||||||
},
|
},
|
||||||
title: {
|
autoWidthButton: {
|
||||||
textTransform: 'uppercase',
|
alignSelf: 'center',
|
||||||
marginBottom: 20,
|
width: 140,
|
||||||
},
|
|
||||||
stack: {
|
|
||||||
width: '100%',
|
|
||||||
},
|
|
||||||
disabledWrap: {
|
|
||||||
marginTop: 24,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
261
src/components/Button.tsx
Normal file
261
src/components/Button.tsx
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
|
import type { LucideIcon } from 'lucide-react-native';
|
||||||
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
import {
|
||||||
|
ActivityIndicator,
|
||||||
|
type GestureResponderEvent,
|
||||||
|
Pressable,
|
||||||
|
type StyleProp,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
type TextStyle,
|
||||||
|
View,
|
||||||
|
type ViewStyle,
|
||||||
|
} from 'react-native';
|
||||||
|
import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
|
||||||
|
|
||||||
|
import { useTheme } from '@/theme/useTheme';
|
||||||
|
|
||||||
|
const BUTTON_HEIGHT = 52;
|
||||||
|
const PRESSED_SCALE = 0.97;
|
||||||
|
|
||||||
|
const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
|
||||||
|
|
||||||
|
type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'destructive';
|
||||||
|
type IconOnlyShape = 'round' | 'default';
|
||||||
|
|
||||||
|
type ButtonProps = {
|
||||||
|
label?: string;
|
||||||
|
variant?: ButtonVariant;
|
||||||
|
disabled?: boolean;
|
||||||
|
loading?: boolean;
|
||||||
|
loadingLabel?: string;
|
||||||
|
autoLoading?: boolean;
|
||||||
|
iconOnly?: boolean;
|
||||||
|
iconOnlyShape?: IconOnlyShape;
|
||||||
|
icon?: LucideIcon;
|
||||||
|
leftIcon?: LucideIcon;
|
||||||
|
rightIcon?: LucideIcon;
|
||||||
|
iconSize?: number;
|
||||||
|
iconStrokeWidth?: number;
|
||||||
|
fullWidth?: boolean;
|
||||||
|
onPress?: (event: GestureResponderEvent) => void | Promise<void>;
|
||||||
|
style?: StyleProp<ViewStyle>;
|
||||||
|
textStyle?: StyleProp<TextStyle>;
|
||||||
|
accessibilityLabel?: string;
|
||||||
|
testID?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Button({
|
||||||
|
label,
|
||||||
|
variant = 'primary',
|
||||||
|
disabled = false,
|
||||||
|
loading = false,
|
||||||
|
loadingLabel,
|
||||||
|
autoLoading = false,
|
||||||
|
iconOnly = false,
|
||||||
|
iconOnlyShape = 'round',
|
||||||
|
icon,
|
||||||
|
leftIcon,
|
||||||
|
rightIcon,
|
||||||
|
iconSize = 20,
|
||||||
|
iconStrokeWidth = 2,
|
||||||
|
fullWidth = true,
|
||||||
|
onPress,
|
||||||
|
style,
|
||||||
|
textStyle,
|
||||||
|
accessibilityLabel,
|
||||||
|
testID,
|
||||||
|
}: ButtonProps) {
|
||||||
|
const { colors, borderRadius, typography, gradients, gradientDirection } = useTheme();
|
||||||
|
const [internalLoading, setInternalLoading] = useState(false);
|
||||||
|
const [isPressed, setIsPressed] = useState(false);
|
||||||
|
|
||||||
|
const isLoading = loading || internalLoading;
|
||||||
|
const isDisabled = disabled || isLoading;
|
||||||
|
const scale = useSharedValue(1);
|
||||||
|
|
||||||
|
const animatedStyle = useAnimatedStyle(() => ({
|
||||||
|
transform: [{ scale: scale.value }],
|
||||||
|
}));
|
||||||
|
|
||||||
|
const handlePressIn = useCallback(() => {
|
||||||
|
if (isDisabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsPressed(true);
|
||||||
|
scale.value = withTiming(PRESSED_SCALE, { duration: 120 });
|
||||||
|
}, [isDisabled, scale]);
|
||||||
|
|
||||||
|
const handlePressOut = useCallback(() => {
|
||||||
|
setIsPressed(false);
|
||||||
|
scale.value = withTiming(1, { duration: 140 });
|
||||||
|
}, [scale]);
|
||||||
|
|
||||||
|
const handlePress = useCallback(
|
||||||
|
async (event: GestureResponderEvent) => {
|
||||||
|
if (!onPress || isDisabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = onPress(event);
|
||||||
|
|
||||||
|
if (!autoLoading || !(result instanceof Promise)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setInternalLoading(true);
|
||||||
|
try {
|
||||||
|
await result;
|
||||||
|
} finally {
|
||||||
|
setInternalLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[autoLoading, isDisabled, onPress],
|
||||||
|
);
|
||||||
|
|
||||||
|
const labelColor = useMemo(() => {
|
||||||
|
if (isDisabled) {
|
||||||
|
return colors.textDisabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variant === 'primary') {
|
||||||
|
return colors.textPrimary;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variant === 'destructive') {
|
||||||
|
return colors.danger;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variant === 'ghost') {
|
||||||
|
return colors.tertiary;
|
||||||
|
}
|
||||||
|
|
||||||
|
return colors.textPrimary;
|
||||||
|
}, [colors, isDisabled, variant]);
|
||||||
|
|
||||||
|
const loadingColor = variant === 'primary' && !isDisabled ? colors.textPrimary : labelColor;
|
||||||
|
const iconOnlyRadius = iconOnlyShape === 'round' ? BUTTON_HEIGHT / 2 : borderRadius.button;
|
||||||
|
const sideIconColor = !isDisabled && variant === 'secondary' ? colors.tertiary : labelColor;
|
||||||
|
const iconOnlyColor = labelColor;
|
||||||
|
|
||||||
|
const IconOnly = icon;
|
||||||
|
const LeftIcon = leftIcon;
|
||||||
|
const RightIcon = rightIcon;
|
||||||
|
|
||||||
|
const containerStyle: ViewStyle = {
|
||||||
|
width: fullWidth && !iconOnly ? '100%' : BUTTON_HEIGHT,
|
||||||
|
height: BUTTON_HEIGHT,
|
||||||
|
borderRadius: iconOnly ? iconOnlyRadius : borderRadius.button,
|
||||||
|
overflow: 'hidden',
|
||||||
|
};
|
||||||
|
|
||||||
|
const baseFillStyle: ViewStyle = {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: 8,
|
||||||
|
borderRadius: iconOnly ? iconOnlyRadius : borderRadius.button,
|
||||||
|
};
|
||||||
|
|
||||||
|
const variantFillStyle: ViewStyle = (() => {
|
||||||
|
if (isDisabled) {
|
||||||
|
return {
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.border,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variant === 'secondary') {
|
||||||
|
return {
|
||||||
|
backgroundColor: colors.surfaceElevated,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.border,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variant === 'ghost') {
|
||||||
|
return {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variant === 'destructive') {
|
||||||
|
return {
|
||||||
|
backgroundColor: 'rgba(255, 77, 109, 0.08)',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.danger,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
const contentPaddingStyle: ViewStyle = {
|
||||||
|
paddingHorizontal: iconOnly ? 0 : 24,
|
||||||
|
};
|
||||||
|
|
||||||
|
const textStyles: TextStyle = {
|
||||||
|
...typography.button,
|
||||||
|
color: labelColor,
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = (
|
||||||
|
<>
|
||||||
|
{isLoading ? <ActivityIndicator color={loadingColor} size="small" /> : null}
|
||||||
|
{!isLoading && iconOnly && IconOnly ? (
|
||||||
|
<IconOnly color={iconOnlyColor} size={iconSize} strokeWidth={iconStrokeWidth} />
|
||||||
|
) : null}
|
||||||
|
{!iconOnly && !isLoading && LeftIcon ? (
|
||||||
|
<LeftIcon color={sideIconColor} size={iconSize} strokeWidth={iconStrokeWidth} />
|
||||||
|
) : null}
|
||||||
|
{!iconOnly && label ? <Text style={[textStyles, textStyle]}>{isLoading && loadingLabel ? loadingLabel : label}</Text> : null}
|
||||||
|
{!iconOnly && !isLoading && RightIcon ? (
|
||||||
|
<RightIcon color={sideIconColor} size={iconSize} strokeWidth={iconStrokeWidth} />
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const fillNode =
|
||||||
|
variant === 'primary' && !isDisabled ? (
|
||||||
|
<LinearGradient
|
||||||
|
colors={isPressed ? gradients.signaturePressed : gradients.signature}
|
||||||
|
start={gradientDirection.start}
|
||||||
|
end={gradientDirection.end}
|
||||||
|
style={[baseFillStyle, contentPaddingStyle]}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</LinearGradient>
|
||||||
|
) : (
|
||||||
|
<View style={[baseFillStyle, contentPaddingStyle, variantFillStyle]}>{content}</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatedPressable
|
||||||
|
testID={testID}
|
||||||
|
accessibilityRole="button"
|
||||||
|
accessibilityState={{ disabled: isDisabled, busy: isLoading }}
|
||||||
|
accessibilityLabel={accessibilityLabel ?? label}
|
||||||
|
disabled={isDisabled}
|
||||||
|
onPress={handlePress}
|
||||||
|
onPressIn={handlePressIn}
|
||||||
|
onPressOut={handlePressOut}
|
||||||
|
style={[styles.root, containerStyle, animatedStyle, style]}
|
||||||
|
>
|
||||||
|
{fillNode}
|
||||||
|
</AnimatedPressable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Button;
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
root: {
|
||||||
|
alignSelf: 'stretch',
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { type PropsWithChildren, useMemo } from 'react';
|
import { type PropsWithChildren, useMemo } from 'react';
|
||||||
|
|
||||||
import colors from './colors';
|
import { colors, glow, gradientDirection, gradients, palette } from './colors';
|
||||||
import spacing, { borderRadius } from './spacing';
|
import spacing, { borderRadius } from './spacing';
|
||||||
import typography from './typography';
|
import typography from './typography';
|
||||||
import { ThemeContext } from './useTheme';
|
import { ThemeContext } from './useTheme';
|
||||||
@@ -9,6 +9,10 @@ export function ThemeProvider({ children }: PropsWithChildren) {
|
|||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
colors,
|
colors,
|
||||||
|
gradients,
|
||||||
|
glow,
|
||||||
|
gradientDirection,
|
||||||
|
palette,
|
||||||
typography,
|
typography,
|
||||||
spacing,
|
spacing,
|
||||||
borderRadius,
|
borderRadius,
|
||||||
|
|||||||
@@ -1,17 +1,173 @@
|
|||||||
export const colors = {
|
import type { ColorValue, ViewStyle } from 'react-native';
|
||||||
deepBackground: '#0A0A12',
|
|
||||||
surface: '#14141F',
|
/* ------------------------------------------------------------------ *
|
||||||
surfaceElevated: '#1E1E2D',
|
* Palette brute — 1:1 avec les tokens :root de la maquette
|
||||||
border: '#2A2A3C',
|
* ------------------------------------------------------------------ */
|
||||||
primary: '#FF2E88',
|
export const palette = {
|
||||||
secondary: '#7B3DFF',
|
/** Fond profond de l'app */
|
||||||
tertiary: '#28E0F0',
|
ink900: '#0A0A12',
|
||||||
success: '#3DDC97',
|
/** Fond du canvas hors écran (planche de style / desktop) */
|
||||||
textPrimary: '#F4F4FA',
|
ink950: '#06060C',
|
||||||
textSecondary: '#9A9AB4',
|
/** Surface / carte */
|
||||||
signatureGradient: ['#7B3DFF', '#FF2E88'] as const,
|
ink800: '#14141F',
|
||||||
|
/** Surface élevée (champ, bouton secondaire, sheet) */
|
||||||
|
ink700: '#1E1E2D',
|
||||||
|
/** Bordure subtile */
|
||||||
|
ink600: '#2A2A3C',
|
||||||
|
|
||||||
|
magenta: '#FF2E88',
|
||||||
|
/** Magenta pressé — extrémité du dégradé assombri */
|
||||||
|
magentaPressed: '#E5216F',
|
||||||
|
/** Teinte magenta claire (texte sur fond magenta translucide) */
|
||||||
|
magentaTint: '#FFB4D4',
|
||||||
|
|
||||||
|
violet: '#7B3DFF',
|
||||||
|
violetPressed: '#6A2FE6',
|
||||||
|
violetTint: '#C9B6FF',
|
||||||
|
|
||||||
|
cyan: '#28E0F0',
|
||||||
|
green: '#3DDC97',
|
||||||
|
/** Ambre — seule teinte chaude du système (placeholders, alertes) */
|
||||||
|
amber: '#FFC478',
|
||||||
|
|
||||||
|
white: '#F4F4FA',
|
||||||
|
grey100: '#D6D6E6',
|
||||||
|
grey200: '#C9C9DC',
|
||||||
|
grey400: '#9A9AB4',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const signatureGradient = colors.signatureGradient;
|
/* ------------------------------------------------------------------ *
|
||||||
|
* Tokens sémantiques
|
||||||
|
* ------------------------------------------------------------------ */
|
||||||
|
export const colors = {
|
||||||
|
/* --- Fonds & surfaces ------------------------------------------- */
|
||||||
|
deepBackground: palette.ink900,
|
||||||
|
/** Fond hors-écran / derrière les sheets */
|
||||||
|
canvas: palette.ink950,
|
||||||
|
surface: palette.ink800,
|
||||||
|
surfaceElevated: palette.ink700,
|
||||||
|
border: palette.ink600,
|
||||||
|
/** Séparateur intra-carte, plus léger que `border` */
|
||||||
|
divider: 'rgba(255,255,255,0.12)',
|
||||||
|
/** Bloc squelette de chargement */
|
||||||
|
skeleton: 'rgba(244,244,250,0.07)',
|
||||||
|
/** Piste de barre de progression / slider */
|
||||||
|
track: 'rgba(244,244,250,0.28)',
|
||||||
|
|
||||||
|
/* --- Accents ---------------------------------------------------- */
|
||||||
|
primary: palette.magenta,
|
||||||
|
primaryPressed: palette.magentaPressed,
|
||||||
|
/** Fond de badge / chip actif teinté magenta */
|
||||||
|
primarySoft: 'rgba(255,46,136,0.14)',
|
||||||
|
/** Bordure de bouton destructif */
|
||||||
|
primaryBorder: 'rgba(255,46,136,0.55)',
|
||||||
|
onPrimary: '#FFFFFF',
|
||||||
|
|
||||||
|
secondary: palette.violet,
|
||||||
|
secondaryPressed: palette.violetPressed,
|
||||||
|
secondarySoft: 'rgba(123,61,255,0.14)',
|
||||||
|
|
||||||
|
tertiary: palette.cyan,
|
||||||
|
/** Fond de badge teinté cyan (line-up, « en cours ») */
|
||||||
|
tertiarySoft: 'rgba(40,224,240,0.12)',
|
||||||
|
|
||||||
|
/* --- États ------------------------------------------------------ */
|
||||||
|
success: palette.green,
|
||||||
|
successSoft: 'rgba(61,220,151,0.14)',
|
||||||
|
successBorder: 'rgba(61,220,151,0.5)',
|
||||||
|
/** Pas de rouge dans le système : le destructif emprunte le magenta */
|
||||||
|
danger: palette.magenta,
|
||||||
|
dangerSoft: 'rgba(255,46,136,0.14)',
|
||||||
|
warning: palette.amber,
|
||||||
|
warningSoft: 'rgba(255,196,120,0.14)',
|
||||||
|
|
||||||
|
/* --- Texte ------------------------------------------------------ */
|
||||||
|
textPrimary: palette.white,
|
||||||
|
/** Titres et libellés secondaires (le plus utilisé après textPrimary) */
|
||||||
|
textStrong: palette.grey100,
|
||||||
|
/** Corps de texte long — jamais du blanc pur sur fond sombre */
|
||||||
|
textBody: palette.grey200,
|
||||||
|
textSecondary: palette.grey400,
|
||||||
|
textOnAccent: '#FFFFFF',
|
||||||
|
textOnPrimarySoft: palette.magentaTint,
|
||||||
|
textOnSecondarySoft: palette.violetTint,
|
||||||
|
/** Texte désactivé (bouton off) — appliquer aussi opacity 0.45 */
|
||||||
|
textDisabled: palette.grey400,
|
||||||
|
|
||||||
|
/* --- Voiles sur média ------------------------------------------- */
|
||||||
|
/** Header / tab bar en blur */
|
||||||
|
scrimHeader: 'rgba(10,10,18,0.94)',
|
||||||
|
/** Bas d'image de couverture (B4, C3) */
|
||||||
|
scrimHeavy: 'rgba(10,10,18,0.86)',
|
||||||
|
scrimMedium: 'rgba(10,10,18,0.6)',
|
||||||
|
scrimLight: 'rgba(10,10,18,0.15)',
|
||||||
|
/** Bouton flottant sur photo */
|
||||||
|
glassFill: 'rgba(255,255,255,0.14)',
|
||||||
|
|
||||||
|
/* --- Carte (B7) ------------------------------------------------- */
|
||||||
|
mapBackground: '#0C1424',
|
||||||
|
mapWater: '#101A2E',
|
||||||
|
mapLand: '#0A0E1A',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ *
|
||||||
|
* Dégradés — tuples pour expo-linear-gradient.
|
||||||
|
* La maquette utilise 135deg (haut-gauche → bas-droite) : d'où
|
||||||
|
* start {0,0} / end {1,1}.
|
||||||
|
* ------------------------------------------------------------------ */
|
||||||
|
export const gradientDirection = {
|
||||||
|
start: { x: 0.2, y: 0 },
|
||||||
|
end: { x: 0.8, y: 1 },
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const gradients = {
|
||||||
|
/** Dégradé signature — bouton primaire, FAB, logo */
|
||||||
|
signature: [palette.violet, palette.magenta],
|
||||||
|
/** Même dégradé à l'état pressé */
|
||||||
|
signaturePressed: [palette.violetPressed, palette.magentaPressed],
|
||||||
|
/** Voile vertical sur image de couverture (start {0,0} end {0,1}) */
|
||||||
|
coverScrim: [
|
||||||
|
colors.scrimMedium,
|
||||||
|
colors.scrimLight,
|
||||||
|
'rgba(10,10,18,0.98)',
|
||||||
|
],
|
||||||
|
} as const satisfies Record<string, readonly ColorValue[]>;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ *
|
||||||
|
* Glow — les box-shadow CSS n'existent pas en RN.
|
||||||
|
* iOS lit shadow*, Android ne lit que elevation (et ignore la couleur
|
||||||
|
* avant API 28) : le glow rose y sera un simple relief.
|
||||||
|
* ------------------------------------------------------------------ */
|
||||||
|
export const glow = {
|
||||||
|
/** --gl · sous un bouton primaire / une carte magenta */
|
||||||
|
primary: {
|
||||||
|
shadowColor: palette.magenta,
|
||||||
|
shadowOffset: { width: 0, height: 12 },
|
||||||
|
shadowOpacity: 0.55,
|
||||||
|
shadowRadius: 25,
|
||||||
|
elevation: 12,
|
||||||
|
},
|
||||||
|
/** --glv · variante violette */
|
||||||
|
violet: {
|
||||||
|
shadowColor: palette.violet,
|
||||||
|
shadowOffset: { width: 0, height: 12 },
|
||||||
|
shadowOpacity: 0.6,
|
||||||
|
shadowRadius: 25,
|
||||||
|
elevation: 12,
|
||||||
|
},
|
||||||
|
/** Ombre neutre de carte élevée */
|
||||||
|
card: {
|
||||||
|
shadowColor: '#000000',
|
||||||
|
shadowOffset: { width: 0, height: 8 },
|
||||||
|
shadowOpacity: 0.5,
|
||||||
|
shadowRadius: 18,
|
||||||
|
elevation: 6,
|
||||||
|
},
|
||||||
|
} as const satisfies Record<string, ViewStyle>;
|
||||||
|
|
||||||
|
export type ColorToken = keyof typeof colors;
|
||||||
|
|
||||||
|
/** @deprecated utiliser `gradients.signature` */
|
||||||
|
export const signatureGradient = gradients.signature;
|
||||||
|
|
||||||
export default colors;
|
export default colors;
|
||||||
|
|||||||
@@ -1,67 +1,226 @@
|
|||||||
import { Inter_400Regular, Inter_500Medium } from '@expo-google-fonts/inter';
|
import {
|
||||||
import { SpaceGrotesk_600SemiBold, SpaceGrotesk_700Bold } from '@expo-google-fonts/space-grotesk';
|
Inter_400Regular,
|
||||||
|
Inter_500Medium,
|
||||||
|
Inter_600SemiBold,
|
||||||
|
Inter_700Bold,
|
||||||
|
} from '@expo-google-fonts/inter';
|
||||||
|
import {
|
||||||
|
JetBrainsMono_400Regular,
|
||||||
|
JetBrainsMono_500Medium,
|
||||||
|
JetBrainsMono_700Bold,
|
||||||
|
} from '@expo-google-fonts/jetbrains-mono';
|
||||||
|
import {
|
||||||
|
SpaceGrotesk_600SemiBold,
|
||||||
|
SpaceGrotesk_700Bold,
|
||||||
|
} from '@expo-google-fonts/space-grotesk';
|
||||||
import { useFonts } from 'expo-font';
|
import { useFonts } from 'expo-font';
|
||||||
|
import type { TextStyle } from 'react-native';
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ *
|
||||||
|
* Familles
|
||||||
|
* Maquette Bahla : Space Grotesk (display) / Inter (UI) / JetBrains Mono
|
||||||
|
* (labels + chiffres). Le mono n'est PAS la mono système : c'est un
|
||||||
|
* choix de marque, il doit être chargé.
|
||||||
|
* ------------------------------------------------------------------ */
|
||||||
export const fontFamily = {
|
export const fontFamily = {
|
||||||
display: 'SpaceGrotesk_600SemiBold',
|
display: 'SpaceGrotesk_600SemiBold',
|
||||||
displayBold: 'SpaceGrotesk_700Bold',
|
displayBold: 'SpaceGrotesk_700Bold',
|
||||||
body: 'Inter_400Regular',
|
body: 'Inter_400Regular',
|
||||||
bodyMedium: 'Inter_500Medium',
|
bodyMedium: 'Inter_500Medium',
|
||||||
mono: 'monospace',
|
bodySemi: 'Inter_600SemiBold',
|
||||||
|
bodyBold: 'Inter_700Bold',
|
||||||
|
mono: 'JetBrainsMono_400Regular',
|
||||||
|
monoMedium: 'JetBrainsMono_500Medium',
|
||||||
|
monoBold: 'JetBrainsMono_700Bold',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ *
|
||||||
|
* Tokens
|
||||||
|
* letterSpacing est en px sur RN (pas en em) : chaque valeur ci-dessous
|
||||||
|
* est la conversion em × fontSize de la maquette.
|
||||||
|
* ------------------------------------------------------------------ */
|
||||||
export const typography = {
|
export const typography = {
|
||||||
display: {
|
/* --- Display · Space Grotesk ------------------------------------ */
|
||||||
fontFamily: fontFamily.display,
|
hero: {
|
||||||
fontSize: 40,
|
fontFamily: fontFamily.displayBold,
|
||||||
|
fontSize: 48,
|
||||||
lineHeight: 48,
|
lineHeight: 48,
|
||||||
letterSpacing: -0.8,
|
letterSpacing: -1.44,
|
||||||
|
},
|
||||||
|
display: {
|
||||||
|
fontFamily: fontFamily.displayBold,
|
||||||
|
fontSize: 40,
|
||||||
|
lineHeight: 42,
|
||||||
|
letterSpacing: -1.2,
|
||||||
|
},
|
||||||
|
/** Grand titre d'écran (« Mes events », dashboard) */
|
||||||
|
screenTitleLg: {
|
||||||
|
fontFamily: fontFamily.displayBold,
|
||||||
|
fontSize: 28,
|
||||||
|
lineHeight: 32,
|
||||||
|
letterSpacing: -0.56,
|
||||||
},
|
},
|
||||||
screenTitle: {
|
screenTitle: {
|
||||||
fontFamily: fontFamily.displayBold,
|
fontFamily: fontFamily.displayBold,
|
||||||
fontSize: 26,
|
fontSize: 26,
|
||||||
lineHeight: 32,
|
lineHeight: 30,
|
||||||
letterSpacing: -0.6,
|
letterSpacing: -0.52,
|
||||||
|
},
|
||||||
|
/** Titre de bloc / nom d'artiste en tête de line-up */
|
||||||
|
sectionTitle: {
|
||||||
|
fontFamily: fontFamily.displayBold,
|
||||||
|
fontSize: 20,
|
||||||
|
lineHeight: 24,
|
||||||
|
letterSpacing: -0.2,
|
||||||
},
|
},
|
||||||
cardTitle: {
|
cardTitle: {
|
||||||
fontFamily: fontFamily.display,
|
fontFamily: fontFamily.display,
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
lineHeight: 24,
|
lineHeight: 22,
|
||||||
letterSpacing: -0.2,
|
letterSpacing: 0,
|
||||||
},
|
},
|
||||||
sectionLabel: {
|
/** Titre de carte compacte (grille, slot de line-up) */
|
||||||
fontFamily: fontFamily.mono,
|
cardTitleSm: {
|
||||||
fontSize: 11,
|
fontFamily: fontFamily.display,
|
||||||
lineHeight: 16,
|
fontSize: 16,
|
||||||
letterSpacing: 0.8,
|
lineHeight: 20,
|
||||||
textTransform: 'uppercase' as const,
|
letterSpacing: -0.16,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/* --- Corps & UI · Inter ---------------------------------------- */
|
||||||
body: {
|
body: {
|
||||||
fontFamily: fontFamily.body,
|
fontFamily: fontFamily.body,
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
|
lineHeight: 24,
|
||||||
|
letterSpacing: 0,
|
||||||
|
},
|
||||||
|
bodySm: {
|
||||||
|
fontFamily: fontFamily.body,
|
||||||
|
fontSize: 14,
|
||||||
lineHeight: 22,
|
lineHeight: 22,
|
||||||
letterSpacing: 0,
|
letterSpacing: 0,
|
||||||
},
|
},
|
||||||
|
/** Libellé de bouton — 52 px de haut, rayon 14 px */
|
||||||
|
button: {
|
||||||
|
fontFamily: fontFamily.bodySemi,
|
||||||
|
fontSize: 15,
|
||||||
|
lineHeight: 20,
|
||||||
|
letterSpacing: 0,
|
||||||
|
},
|
||||||
|
/** Nom d'organisateur, en-tête de ligne, valeur de champ */
|
||||||
|
label: {
|
||||||
|
fontFamily: fontFamily.bodySemi,
|
||||||
|
fontSize: 13,
|
||||||
|
lineHeight: 18,
|
||||||
|
letterSpacing: 0,
|
||||||
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
fontFamily: fontFamily.bodyMedium,
|
fontFamily: fontFamily.bodyMedium,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
lineHeight: 18,
|
lineHeight: 18,
|
||||||
letterSpacing: 0,
|
letterSpacing: 0,
|
||||||
},
|
},
|
||||||
|
/** Texte de chip / filtre */
|
||||||
|
chip: {
|
||||||
|
fontFamily: fontFamily.bodySemi,
|
||||||
|
fontSize: 12,
|
||||||
|
lineHeight: 16,
|
||||||
|
letterSpacing: 0,
|
||||||
|
},
|
||||||
|
metadataSm: {
|
||||||
|
fontFamily: fontFamily.bodyMedium,
|
||||||
|
fontSize: 12,
|
||||||
|
lineHeight: 16,
|
||||||
|
letterSpacing: 0,
|
||||||
|
},
|
||||||
|
caption: {
|
||||||
|
fontFamily: fontFamily.body,
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 15,
|
||||||
|
letterSpacing: 0,
|
||||||
|
},
|
||||||
|
/** Libellé de la tab bar (4 onglets) */
|
||||||
|
tabLabel: {
|
||||||
|
fontFamily: fontFamily.bodyMedium,
|
||||||
|
fontSize: 10,
|
||||||
|
lineHeight: 13,
|
||||||
|
letterSpacing: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
/* --- Labels & chiffres · JetBrains Mono ------------------------- */
|
||||||
|
/** Overline de rangée / d'écran sur la planche de style */
|
||||||
|
overlineLg: {
|
||||||
|
fontFamily: fontFamily.monoMedium,
|
||||||
|
fontSize: 13,
|
||||||
|
lineHeight: 17,
|
||||||
|
letterSpacing: 1.82,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
},
|
||||||
|
/** Libellé de section dans l'app */
|
||||||
|
sectionLabel: {
|
||||||
|
fontFamily: fontFamily.monoMedium,
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 14,
|
||||||
|
letterSpacing: 1.76,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
},
|
||||||
|
/** Overline resserré (cartouches, en-têtes de colonne) */
|
||||||
|
overlineSm: {
|
||||||
|
fontFamily: fontFamily.monoMedium,
|
||||||
|
fontSize: 10,
|
||||||
|
lineHeight: 13,
|
||||||
|
letterSpacing: 1.8,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
},
|
||||||
|
/** Badge / pastille (« COMPLET », « CE SOIR ») */
|
||||||
|
badge: {
|
||||||
|
fontFamily: fontFamily.monoMedium,
|
||||||
|
fontSize: 9,
|
||||||
|
lineHeight: 12,
|
||||||
|
letterSpacing: 1.62,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
},
|
||||||
|
/** Chiffres, horaires, prix, distances — 22:30 · 25 CHF · 18 km */
|
||||||
digits: {
|
digits: {
|
||||||
fontFamily: fontFamily.mono,
|
fontFamily: fontFamily.mono,
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
lineHeight: 16,
|
lineHeight: 14,
|
||||||
letterSpacing: 0.4,
|
letterSpacing: 0,
|
||||||
},
|
},
|
||||||
} as const;
|
digitsSm: {
|
||||||
|
fontFamily: fontFamily.mono,
|
||||||
|
fontSize: 10,
|
||||||
|
lineHeight: 13,
|
||||||
|
letterSpacing: 0,
|
||||||
|
},
|
||||||
|
/** Grand chiffre de statistique (dashboard organisateur) */
|
||||||
|
statValue: {
|
||||||
|
fontFamily: fontFamily.displayBold,
|
||||||
|
fontSize: 24,
|
||||||
|
lineHeight: 26,
|
||||||
|
letterSpacing: -0.48,
|
||||||
|
},
|
||||||
|
} as const satisfies Record<string, TextStyle>;
|
||||||
|
|
||||||
export const loadFonts = () =>
|
export type TypographyToken = keyof typeof typography;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ *
|
||||||
|
* Chargement — hook, donc préfixe `use` (règle react-hooks).
|
||||||
|
* ------------------------------------------------------------------ */
|
||||||
|
export const useAppFonts = () =>
|
||||||
useFonts({
|
useFonts({
|
||||||
SpaceGrotesk_600SemiBold,
|
SpaceGrotesk_600SemiBold,
|
||||||
SpaceGrotesk_700Bold,
|
SpaceGrotesk_700Bold,
|
||||||
Inter_400Regular,
|
Inter_400Regular,
|
||||||
Inter_500Medium,
|
Inter_500Medium,
|
||||||
|
Inter_600SemiBold,
|
||||||
|
Inter_700Bold,
|
||||||
|
JetBrainsMono_400Regular,
|
||||||
|
JetBrainsMono_500Medium,
|
||||||
|
JetBrainsMono_700Bold,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** @deprecated conservé pour compat — utiliser `useAppFonts` */
|
||||||
|
export const loadFonts = useAppFonts;
|
||||||
|
|
||||||
export default typography;
|
export default typography;
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import { createContext, useContext } from 'react';
|
import { createContext, useContext } from 'react';
|
||||||
|
|
||||||
import colors from './colors';
|
import { colors, glow, gradientDirection, gradients, palette } from './colors';
|
||||||
import spacing, { borderRadius } from './spacing';
|
import spacing, { borderRadius } from './spacing';
|
||||||
import typography from './typography';
|
import typography from './typography';
|
||||||
|
|
||||||
export type ThemeContextValue = {
|
export type ThemeContextValue = {
|
||||||
colors: typeof colors;
|
colors: typeof colors;
|
||||||
|
gradients: typeof gradients;
|
||||||
|
glow: typeof glow;
|
||||||
|
gradientDirection: typeof gradientDirection;
|
||||||
|
palette: typeof palette;
|
||||||
typography: typeof typography;
|
typography: typeof typography;
|
||||||
spacing: typeof spacing;
|
spacing: typeof spacing;
|
||||||
borderRadius: typeof borderRadius;
|
borderRadius: typeof borderRadius;
|
||||||
|
|||||||
Reference in New Issue
Block a user