monorepo
15
.github/workflows/expo-main.yml
vendored
@@ -17,14 +17,17 @@ jobs:
|
||||
|
||||
- name: Install and Build 🔧
|
||||
run: |
|
||||
corepack enable
|
||||
yarn install
|
||||
yarn expo build:web
|
||||
yarn build:web
|
||||
yarn build:storybook
|
||||
cp -r packages/ui/public packages/app/web-build/design-system
|
||||
- name: Deploy 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@4.0.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: gh-pages
|
||||
folder: web-build
|
||||
folder: packages/app/web-build
|
||||
publish-native:
|
||||
name: Publish native versions
|
||||
runs-on: ubuntu-latest
|
||||
@@ -44,18 +47,24 @@ jobs:
|
||||
git config --global user.email "github-action@example.com"
|
||||
git config --global user.name "Github Bot"
|
||||
yarn version --new-version=$BUILD_VERSION
|
||||
working-directory: packages/app
|
||||
env:
|
||||
BUILD_VERSION: 1.${{ github.run_id }}.${{ github.run_number }}
|
||||
- run: yarn install
|
||||
|
||||
- run: |
|
||||
corepack enable
|
||||
yarn install
|
||||
|
||||
- run: echo $BUILD_VERSION
|
||||
|
||||
# - run: eas build -p android --non-interactive
|
||||
|
||||
- run: eas build -p ios --non-interactive
|
||||
working-directory: packages/app
|
||||
env:
|
||||
EXPO_APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.EXPO_APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
|
||||
- run: eas submit --platform ios --non-interactive --latest
|
||||
working-directory: packages/app
|
||||
env:
|
||||
EXPO_APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.EXPO_APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
|
||||
10
.github/workflows/expo-pr.yml
vendored
@@ -9,15 +9,18 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
|
||||
- uses: expo/expo-github-action@v5
|
||||
with:
|
||||
expo-packager: yarn
|
||||
expo-username: ${{ secrets.EXPO_CLI_USERNAME }}
|
||||
expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
|
||||
expo-cache: true
|
||||
|
||||
- name: Cache Node Modules
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
@@ -30,9 +33,14 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- name: Install Packages
|
||||
run: npm i -g yarn && yarn install
|
||||
run: |
|
||||
corepack enable
|
||||
yarn install
|
||||
|
||||
- name: Expo Publish Channel
|
||||
run: expo publish --non-interactive --release-channel pr${{ github.event.number }}
|
||||
working-directory: packages/app
|
||||
|
||||
- name: Add Comment To PR
|
||||
uses: mshick/add-pr-comment@v1
|
||||
env:
|
||||
|
||||
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
/*.log
|
||||
/.yarn
|
||||
node_modules/
|
||||
.expo/
|
||||
dist/
|
||||
|
||||
1
.yarnrc.yml
Normal file
@@ -0,0 +1 @@
|
||||
nodeLinker: node-modules
|
||||
4
App.tsx
@@ -1,4 +0,0 @@
|
||||
import 'react-native-get-random-values';
|
||||
import { App } from './src/app';
|
||||
|
||||
export default App;
|
||||
76
package.json
@@ -1,76 +1,16 @@
|
||||
{
|
||||
"name": "bob",
|
||||
"version": "1.0.0",
|
||||
"main": "node_modules/expo/AppEntry.js",
|
||||
"homepage": "/bob-the-algorithm",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"web": "expo start --web",
|
||||
"eject": "expo eject",
|
||||
"test": "jest --watchAll"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "jest-expo"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "~17.0.21",
|
||||
"@types/react-dom": "~18.0.3",
|
||||
"react-error-overlay": "6.0.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^12.0.0",
|
||||
"@react-native-async-storage/async-storage": "~1.15.0",
|
||||
"@react-navigation/bottom-tabs": "^6.0.5",
|
||||
"@react-navigation/native": "^6.0.2",
|
||||
"@react-navigation/native-stack": "^6.1.0",
|
||||
"@react-navigation/stack": "^6.2.1",
|
||||
"chroma-js": "^2.4.2",
|
||||
"date-fns": "^2.28.0",
|
||||
"expo": "~44.0.0",
|
||||
"expo-asset": "~8.4.4",
|
||||
"expo-calendar": "~10.1.0",
|
||||
"expo-constants": "~13.0.0",
|
||||
"expo-font": "~10.0.4",
|
||||
"expo-linking": "~3.0.0",
|
||||
"expo-location": "~14.0.1",
|
||||
"expo-random": "^12.1.2",
|
||||
"expo-splash-screen": "~0.14.0",
|
||||
"expo-status-bar": "~1.2.0",
|
||||
"expo-task-manager": "~10.1.0",
|
||||
"expo-updates": "~0.11.7",
|
||||
"expo-web-browser": "~10.1.0",
|
||||
"parse-css-color": "^0.2.1",
|
||||
"react": "17.0.1",
|
||||
"react-dom": "17.0.1",
|
||||
"react-native": "0.64.3",
|
||||
"react-native-calendar-strip": "^2.2.5",
|
||||
"react-native-calendars": "^1.1284.0",
|
||||
"react-native-collapsible": "^1.6.0",
|
||||
"react-native-gesture-handler": "^2.4.2",
|
||||
"react-native-get-random-values": "^1.8.0",
|
||||
"react-native-safe-area-context": "3.3.2",
|
||||
"react-native-screens": "~3.10.1",
|
||||
"react-native-web": "0.17.1",
|
||||
"string-to-color": "^2.2.2",
|
||||
"styled-components": "^5.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.9",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.5",
|
||||
"@types/chroma-js": "^2.1.3",
|
||||
"@types/react": "~17.0.21",
|
||||
"@types/react-dom": "^18.0.3",
|
||||
"@types/styled-components-react-native": "^5.1.3",
|
||||
"babel-plugin-module-resolver": "^4.1.0",
|
||||
"expo-cli": "^5.4.3",
|
||||
"jest": "^26.6.3",
|
||||
"jest-expo": "~44.0.1",
|
||||
"react-refresh": "^0.13.0",
|
||||
"react-test-renderer": "17.0.1",
|
||||
"typescript": "~4.3.5",
|
||||
"webpack-hot-middleware": "^2.25.1"
|
||||
"dev": "yarn workspace @morten-olsen/bob start",
|
||||
"build:web": "yarn workspace @morten-olsen/bob build:web",
|
||||
"build:storybook": "yarn workspace @morten-olsen/ui build:storybook",
|
||||
"dev:storybook": "yarn workspace @morten-olsen/ui dev"
|
||||
},
|
||||
"packageManager": "yarn@3.1.0",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"private": true
|
||||
}
|
||||
|
||||
15
packages/app/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/*.log
|
||||
node_modules/
|
||||
.expo/
|
||||
dist/
|
||||
npm-debug.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
*.orig.*
|
||||
web-build/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
@@ -6,6 +6,7 @@ module.exports = function(api) {
|
||||
[require.resolve('babel-plugin-module-resolver'), {
|
||||
alias: {
|
||||
'#': './src',
|
||||
'@morten-olsen/ui': '@morten-olsen/ui/src',
|
||||
},
|
||||
}],
|
||||
],
|
||||
20
packages/app/metro.config.js
Normal file
@@ -0,0 +1,20 @@
|
||||
// Learn more https://docs.expo.io/guides/customizing-metro
|
||||
const { getDefaultConfig } = require('expo/metro-config');
|
||||
const path = require('path');
|
||||
|
||||
// Find the workspace root, this can be replaced with `find-yarn-workspace-root`
|
||||
const workspaceRoot = path.resolve(__dirname, '../..');
|
||||
const projectRoot = __dirname;
|
||||
|
||||
const config = getDefaultConfig(projectRoot);
|
||||
|
||||
// 1. Watch all files within the monorepo
|
||||
config.watchFolders = [workspaceRoot];
|
||||
// 2. Let Metro know where to resolve packages, and in what order
|
||||
config.resolver.nodeModulesPaths = [
|
||||
path.resolve(projectRoot, 'node_modules'),
|
||||
path.resolve(workspaceRoot, 'node_modules'),
|
||||
];
|
||||
|
||||
module.exports = config;
|
||||
|
||||
79
packages/app/package.json
Normal file
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"name": "@morten-olsen/bob",
|
||||
"version": "1.0.0",
|
||||
"main": "./src/index.ts",
|
||||
"homepage": "/bob-the-algorithm",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"web": "expo start --web",
|
||||
"eject": "expo eject",
|
||||
"test": "jest --watchAll",
|
||||
"build:web": "expo build:web"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "jest-expo"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "~17.0.21",
|
||||
"@types/react-dom": "~18.0.3",
|
||||
"react-error-overlay": "6.0.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^12.0.0",
|
||||
"@morten-olsen/ui": "workspace:^",
|
||||
"@react-native-async-storage/async-storage": "~1.15.0",
|
||||
"@react-navigation/bottom-tabs": "^6.0.5",
|
||||
"@react-navigation/native": "^6.0.2",
|
||||
"@react-navigation/native-stack": "^6.1.0",
|
||||
"@react-navigation/stack": "^6.2.1",
|
||||
"chroma-js": "^2.4.2",
|
||||
"date-fns": "^2.28.0",
|
||||
"expo": "~44.0.0",
|
||||
"expo-asset": "~8.4.4",
|
||||
"expo-calendar": "~10.1.0",
|
||||
"expo-constants": "~13.0.0",
|
||||
"expo-font": "~10.0.4",
|
||||
"expo-linking": "~3.0.0",
|
||||
"expo-location": "~14.0.1",
|
||||
"expo-random": "^12.1.2",
|
||||
"expo-splash-screen": "~0.14.0",
|
||||
"expo-status-bar": "~1.2.0",
|
||||
"expo-task-manager": "~10.1.0",
|
||||
"expo-updates": "~0.11.7",
|
||||
"expo-web-browser": "~10.1.0",
|
||||
"parse-css-color": "^0.2.1",
|
||||
"react": "17.0.1",
|
||||
"react-dom": "17.0.1",
|
||||
"react-native": "0.64.3",
|
||||
"react-native-calendar-strip": "^2.2.5",
|
||||
"react-native-calendars": "^1.1284.0",
|
||||
"react-native-collapsible": "^1.6.0",
|
||||
"react-native-gesture-handler": "^2.4.2",
|
||||
"react-native-get-random-values": "^1.8.0",
|
||||
"react-native-safe-area-context": "3.3.2",
|
||||
"react-native-screens": "~3.10.1",
|
||||
"react-native-web": "0.17.1",
|
||||
"string-to-color": "^2.2.2",
|
||||
"styled-components": "^5.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.9",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.5",
|
||||
"@types/chroma-js": "^2.1.3",
|
||||
"@types/react": "~17.0.21",
|
||||
"@types/react-dom": "^18.0.3",
|
||||
"@types/styled-components-react-native": "^5.1.3",
|
||||
"babel-loader": "^8.2.5",
|
||||
"babel-plugin-module-resolver": "^4.1.0",
|
||||
"expo-cli": "^5.4.3",
|
||||
"jest": "^26.6.3",
|
||||
"jest-expo": "~44.0.1",
|
||||
"react-refresh": "^0.13.0",
|
||||
"react-test-renderer": "17.0.1",
|
||||
"typescript": "~4.3.5",
|
||||
"webpack-hot-middleware": "^2.25.1"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'react-native-get-random-values';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
import { useCallback } from 'react';
|
||||
import { Setup } from './features/setup';
|
||||
import { Router } from './ui/router';
|
||||
import { ThemeProvider } from 'styled-components/native';
|
||||
import { light } from './ui';
|
||||
import { Provider } from '@morten-olsen/ui';
|
||||
|
||||
const App: React.FC = () => {
|
||||
const getTransit = useCallback(
|
||||
@@ -19,11 +19,11 @@ const App: React.FC = () => {
|
||||
return (
|
||||
<SafeAreaProvider>
|
||||
<StatusBar />
|
||||
<ThemeProvider theme={light}>
|
||||
<Provider>
|
||||
<Setup getTransit={getTransit}>
|
||||
<Router />
|
||||
</Setup>
|
||||
</ThemeProvider>
|
||||
</Provider>
|
||||
</SafeAreaProvider>
|
||||
);
|
||||
};
|
||||
@@ -43,6 +43,10 @@ const minutesToTime = (minutes: number): Time => {
|
||||
return { hour, minute };
|
||||
}
|
||||
|
||||
const timeToDate = (time: Time) => {
|
||||
return new Date(0, 0, 0, time.hour, time.minute);
|
||||
}
|
||||
|
||||
const add = (a: Time, b: Time | number) => {
|
||||
const toAdd = typeof b === 'number' ? b : b.hour * 60 + b.minute
|
||||
const current = a.hour * 60 + a.minute + toAdd;
|
||||
@@ -55,6 +59,7 @@ const timeUtils = {
|
||||
equal,
|
||||
largerThan,
|
||||
timeToMinutes,
|
||||
timeToDate,
|
||||
max,
|
||||
add,
|
||||
};
|
||||
10
packages/app/src/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { registerRootComponent } from 'expo';
|
||||
import './setup';
|
||||
|
||||
import { App } from './app';
|
||||
|
||||
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
|
||||
// It also ensures that whether you load the app in Expo Go or in a native build,
|
||||
// the environment is set up appropriately
|
||||
registerRootComponent(App);
|
||||
|
||||
1
packages/app/src/setup.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default '';
|
||||
2
packages/app/src/setup.web.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import '@fontsource/montserrat';
|
||||
export default '';
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Task } from "#/features/data";
|
||||
import { Row, RowProps } from "../../base";
|
||||
import { Row, RowProps } from "@morten-olsen/ui";
|
||||
|
||||
type Props = RowProps & {
|
||||
item: Task;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { dayUtils } from "#/features/day";
|
||||
import { PlanResultDay } from "#/features/planner"
|
||||
import { Body1, Jumbo } from "#/ui/typography";
|
||||
import { Body1, Jumbo } from "@morten-olsen/ui";
|
||||
import { PlanDayTask } from "./task";
|
||||
|
||||
type Props = {
|
||||
@@ -16,7 +16,6 @@ const PlanDay: React.FC<Props> = ({ day }) => {
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Jumbo>{dayUtils.toId(day.day)}</Jumbo>
|
||||
{day.plan.map((item) => {
|
||||
if (item.type === 'task') {
|
||||
return <PlanDayTask task={item} />
|
||||
@@ -4,7 +4,7 @@ import chroma from 'chroma-js';
|
||||
import styled from "styled-components/native";
|
||||
import stringToColor from 'string-to-color';
|
||||
import { timeUtils } from '#/features/data';
|
||||
import { Body1 } from '#/ui/typography';
|
||||
import { Body1, CalendarEntry, Row } from '@morten-olsen/ui';
|
||||
|
||||
type Props = {
|
||||
task: PlannedTask;
|
||||
@@ -62,20 +62,18 @@ const PlanDayTask: React.FC<Props> = ({ task, onPress }) => {
|
||||
[task.name],
|
||||
);
|
||||
const height = useMemo(
|
||||
() => timeUtils.timeToMinutes(task.end) - timeUtils.timeToMinutes(task.start),
|
||||
() => (timeUtils.timeToMinutes(task.end) - timeUtils.timeToMinutes(task.start)) / 10,
|
||||
[task.start, task.end],
|
||||
);
|
||||
const view = (
|
||||
<Block height={Math.max(70, height * 10)} background={color}>
|
||||
<TimeBox background={color}>
|
||||
<Time background={color}>{timeUtils.timeToString(task.start)}</Time>
|
||||
<Time background={color}>{timeUtils.timeToString(task.end)}</Time>
|
||||
</TimeBox>
|
||||
<Main>
|
||||
<Body1>{task.name}</Body1>
|
||||
</Main>
|
||||
<Filler />
|
||||
</Block>
|
||||
<Row>
|
||||
<CalendarEntry
|
||||
location={task.location?.join(', ') || 'anywhere'}
|
||||
start={timeUtils.timeToDate(task.start)}
|
||||
end={timeUtils.timeToDate(task.end)}
|
||||
title={task.name}
|
||||
/>
|
||||
</Row>
|
||||
);
|
||||
|
||||
if (onPress) {
|
||||
@@ -1,10 +1,9 @@
|
||||
import { TaskType } from "#/features/data";
|
||||
import { useTasks } from "#/features/tasks";
|
||||
import { Group } from "#/ui/components/base"
|
||||
import { Group } from "@morten-olsen/ui"
|
||||
import { RootNavigationProp } from "#/ui/router";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { useCallback } from "react";
|
||||
import { TaskListItem } from "../list-item";
|
||||
|
||||
type Props = {
|
||||
type: TaskType;
|
||||
@@ -29,14 +28,12 @@ const TaskGroup: React.FC<Props> = ({ type }) => {
|
||||
add={() => add(type)}
|
||||
items={tasks || []}
|
||||
getKey={(task) => task.id}
|
||||
render={(task) => (
|
||||
<TaskListItem
|
||||
item={task}
|
||||
onPress={() => {
|
||||
navigate('set-override', { id: task.id });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
render={(task) => ({
|
||||
title: task.title,
|
||||
onPress: () => {
|
||||
navigate('set-override', { id: task.id });
|
||||
},
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,2 +1 @@
|
||||
export * from './components/base';
|
||||
export * from './theme';
|
||||
@@ -4,7 +4,7 @@ import { useTheme } from 'styled-components/native';
|
||||
import { NavigationContainer, DefaultTheme } from '@react-navigation/native';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import { Icon } from '../components/base';
|
||||
import { Icon } from '@morten-olsen/ui/components/base';
|
||||
import { DayScreen } from '../screens/day';
|
||||
import { TaskAddScreen } from '../screens/task/add';
|
||||
import { MainTabParamList, RootStackParamList } from './types';
|
||||
50
packages/app/src/ui/screens/day/index.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { useAppointmentStatus } from "#/features/appointments";
|
||||
import { AppointmentsStatus } from "#/features/appointments/context";
|
||||
import { TaskType } from "#/features/data";
|
||||
import { dayUtils, useDate, useSetDate } from "#/features/day";
|
||||
import { useSetStartTimeOverride, useStartTimeOverride } from "#/features/overrides";
|
||||
import { CalendarStrip } from "@morten-olsen/ui/components/date"
|
||||
import { TimeInput } from "@morten-olsen/ui/components/form";
|
||||
import { TaskGroup } from "#/ui/containers/tasks/group";
|
||||
import styled from "styled-components/native";
|
||||
|
||||
const Wrapper = styled.View`
|
||||
margin-top: 30px;
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const Content = styled.ScrollView`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const DayScreen: React.FC = () => {
|
||||
const date = useDate();
|
||||
const setDate = useSetDate();
|
||||
const appointmentStatus = useAppointmentStatus();
|
||||
const startTimeOverride = useStartTimeOverride();
|
||||
const [setStartTimeOverride] = useSetStartTimeOverride();
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<CalendarStrip
|
||||
selected={dayUtils.dayToDate(date)}
|
||||
onSelect={(date) => setDate(dayUtils.dateToDay(date))}
|
||||
/>
|
||||
<Content>
|
||||
<TimeInput
|
||||
key={dayUtils.toId(date)}
|
||||
label="Start time"
|
||||
value={startTimeOverride}
|
||||
onChange={setStartTimeOverride}
|
||||
/>
|
||||
{appointmentStatus === AppointmentsStatus.rejected && (
|
||||
<TaskGroup type={TaskType.appointment} />
|
||||
)}
|
||||
<TaskGroup type={TaskType.routine} />
|
||||
<TaskGroup type={TaskType.goal} />
|
||||
</Content>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export { DayScreen };
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useLocations, useRemoveLocation } from "#/features/location"
|
||||
import { List, Page } from "#/ui/components/base";
|
||||
import { List, Page } from "@morten-olsen/ui/components/base";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
|
||||
const LocationListScreen: React.FC = () => {
|
||||
@@ -1,8 +1,8 @@
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useAsyncCallback } from "#/features/async";
|
||||
import { useLocations, useSetLocation } from "#/features/location"
|
||||
import { Button, Popup, Row } from "#/ui/components/base";
|
||||
import { TextInput } from "#/ui/components/form";
|
||||
import { Button, Popup, Row } from "@morten-olsen/ui/components/base";
|
||||
import { TextInput } from "@morten-olsen/ui/components/form";
|
||||
import { LocationSetScreenRouteProp, RootNavigationProp } from "#/ui/router";
|
||||
import { useNavigation, useRoute } from "@react-navigation/native";
|
||||
import { useEffect, useState } from "react";
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TaskType } from "#/features/data";
|
||||
import { Page, Row } from "#/ui/components/base";
|
||||
import { Page, Row } from "@morten-olsen/ui/components/base";
|
||||
import { MoreScreenNavigationProps } from "#/ui/router";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Day, dayUtils, useDate } from "#/features/day"
|
||||
import { usePlan } from "#/features/planner";
|
||||
import { Button } from "#/ui/components/base";
|
||||
import DateInput from "#/ui/components/form/date";
|
||||
import { PlanDay } from "#/ui/components/plan";
|
||||
import { Body1 } from "#/ui/typography";
|
||||
import { Button, Row } from "@morten-olsen/ui/components/base";
|
||||
import { CalendarStrip, DateSelector, FormLayout } from "@morten-olsen/ui";
|
||||
import { PlanDay } from "#/ui/containers/plan";
|
||||
import { useCallback, useState } from "react";
|
||||
import styled from "styled-components/native";
|
||||
|
||||
@@ -14,6 +13,13 @@ const Wrapper = styled.View`
|
||||
margin: 60px 0;
|
||||
`
|
||||
|
||||
const Horizontal = styled.View`
|
||||
`;
|
||||
|
||||
const FlexDateSelector = styled(DateSelector)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const PlanScreen: React.FC = () => {
|
||||
const [start, setStart] = useState<Day>(dayUtils.today());
|
||||
const [end, setEnd] = useState<Day>(dayUtils.today());
|
||||
@@ -31,21 +37,27 @@ const PlanScreen: React.FC = () => {
|
||||
return (
|
||||
<Scroll>
|
||||
<Wrapper>
|
||||
<DateInput
|
||||
label="Start date"
|
||||
selected={start}
|
||||
onSelect={setStart}
|
||||
/>
|
||||
<DateInput
|
||||
label="End date"
|
||||
selected={end}
|
||||
onSelect={setEnd}
|
||||
/>
|
||||
<Button onPress={runPlanning} title="Plan" />
|
||||
<Horizontal>
|
||||
<FlexDateSelector
|
||||
label="Start date"
|
||||
selected={start}
|
||||
onSelect={setStart}
|
||||
/>
|
||||
<DateSelector
|
||||
label="End date"
|
||||
selected={end}
|
||||
onSelect={setEnd}
|
||||
/>
|
||||
</Horizontal>
|
||||
<Row>
|
||||
<Button onPress={runPlanning} title="Plan" />
|
||||
</Row>
|
||||
{!!result && (
|
||||
<>
|
||||
<CalendarStrip
|
||||
/>
|
||||
{Object.entries(result.days).map(([key, day]) => (
|
||||
<PlanDay day={day} />
|
||||
<PlanDay day={day} />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
@@ -3,12 +3,12 @@ import { nanoid } from 'nanoid';
|
||||
import { Task, TaskType, Time, UserLocation } from "#/features/data";
|
||||
import { useLocations } from "#/features/location";
|
||||
import { useSetTask, useTasks } from "#/features/tasks";
|
||||
import { Button, Cell, Group, Popup, Row } from "#/ui/components/base"
|
||||
import { Checkbok, TextInput, TimeInput, OptionalSelector } from "#/ui/components/form";
|
||||
import { Button, Cell, Group, Popup, Row } from "@morten-olsen/ui/components/base"
|
||||
import { Checkbox, TextInput, TimeInput, OptionalSelector } from "@morten-olsen/ui/components/form";
|
||||
import { RootNavigationProp, TaskAddScreenRouteProp } from "#/ui/router";
|
||||
import { Overline } from "#/ui/typography";
|
||||
import { Overline } from "@morten-olsen/ui/typography";
|
||||
import { useNavigation, useRoute } from "@react-navigation/native";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import styled from "styled-components/native";
|
||||
|
||||
const SideBySide = styled.View`
|
||||
@@ -153,7 +153,7 @@ const TaskAddScreen: React.FC = () => {
|
||||
disabledText="Any day"
|
||||
enabledText="Specific days"
|
||||
/>
|
||||
<Checkbok label="Required" onChange={setRequired} value={required} />
|
||||
<Checkbox label="Required" onChange={setRequired} value={required} />
|
||||
{type === TaskType.goal && (
|
||||
<SideBySide>
|
||||
<TextInput label="Start" flex={1} />
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useRemoveTask, useTasks } from "#/features/tasks";
|
||||
import { List, Page } from "#/ui/components/base";
|
||||
import { List, Page } from "@morten-olsen/ui/components/base";
|
||||
import { RootNavigationProp, TaskListScreenRouteProp } from "#/ui/router";
|
||||
import { useNavigation, useRoute } from "@react-navigation/native";
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { useAsyncCallback } from "#/features/async";
|
||||
import { nanoid } from 'nanoid';
|
||||
import { Task, TaskType, Time, timeUtils, UserLocation } from "#/features/data";
|
||||
import { Time, timeUtils, UserLocation } from "#/features/data";
|
||||
import { useLocations } from "#/features/location";
|
||||
import { useSetTask, useTasks } from "#/features/tasks";
|
||||
import { Button, Cell, Group, Popup, Row } from "#/ui/components/base"
|
||||
import { Checkbok, TextInput, TimeInput, OptionalSelector } from "#/ui/components/form";
|
||||
import { useTasks } from "#/features/tasks";
|
||||
import { Button, Cell, Popup, Row } from "@morten-olsen/ui/components/base"
|
||||
import { Checkbox, TextInput, TimeInput, OptionalSelector } from "@morten-olsen/ui/components/form";
|
||||
import { RootNavigationProp, TaskAddScreenRouteProp } from "#/ui/router";
|
||||
import { Overline } from "#/ui/typography";
|
||||
import { Overline } from "@morten-olsen/ui/typography";
|
||||
import { useNavigation, useRoute } from "@react-navigation/native";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import styled from "styled-components/native";
|
||||
import { Override, useClearTaskOverride, useOverrides, useSetOverride, useSetTaskOverride } from "#/features/overrides";
|
||||
import { Override, useClearTaskOverride, useOverrides, useSetTaskOverride } from "#/features/overrides";
|
||||
|
||||
const SideBySide = styled.View`
|
||||
flex-direction: row;
|
||||
@@ -154,10 +153,11 @@ const OverrideSetScreen: React.FC = () => {
|
||||
disabledText="Any day"
|
||||
enabledText="Specific days"
|
||||
/>
|
||||
<SideBySide>
|
||||
<Checkbok label="Required" flex={1} />
|
||||
<TextInput label="Priority" flex={1} />
|
||||
</SideBySide>
|
||||
<Checkbox label="Required" />
|
||||
<TextInput
|
||||
label="Priority"
|
||||
placeholder="5"
|
||||
/>
|
||||
<Row>
|
||||
<SideBySide>
|
||||
<Button onPress={clear} title="Clear" type="destructive" />
|
||||
12
packages/app/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "expo/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"#/*": ["./src/*"],
|
||||
"@morten-olsen/ui/*": ["../ui/src/*"],
|
||||
"@morten-olsen/ui": ["../ui/src/index.ts"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
const path = require("path");
|
||||
const createExpoWebpackConfigAsync = require("@expo/webpack-config");
|
||||
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
|
||||
|
||||
@@ -13,5 +14,13 @@ module.exports = async function (env, argv) {
|
||||
);
|
||||
}
|
||||
|
||||
config.module.rules.push({
|
||||
test: /\.tsx?$/,
|
||||
loader: require.resolve("babel-loader"),
|
||||
include: [
|
||||
/@morten-olsen\/ui/,
|
||||
],
|
||||
});
|
||||
|
||||
return config;
|
||||
};
|
||||
3
packages/ui/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/node_modules
|
||||
/.expo
|
||||
/public
|
||||
58
packages/ui/.storybook/main.js
Normal file
@@ -0,0 +1,58 @@
|
||||
const path = require('path');
|
||||
const { mergeConfig } = require('vite');
|
||||
|
||||
module.exports = {
|
||||
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
||||
addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
|
||||
framework: '@storybook/react',
|
||||
// core: {
|
||||
// builder: '@storybook/builder-vite',
|
||||
// },
|
||||
typescript: {
|
||||
check: false,
|
||||
checkOptions: {},
|
||||
reactDocgen: 'react-docgen-typescript',
|
||||
reactDocgenTypescriptOptions: {
|
||||
shouldExtractLiteralValuesFromEnum: true,
|
||||
compilerOptions: {
|
||||
allowSyntheticDefaultImports: false,
|
||||
esModuleInterop: false,
|
||||
},
|
||||
}
|
||||
},
|
||||
staticsDirs: ['../public', '../public/assets'],
|
||||
async webpackFinal(config, { configType }) {
|
||||
config.resolve.alias['react-native'] = 'react-native-web';
|
||||
config.module.rules.push({
|
||||
loader: 'babel-loader',
|
||||
test: /\.jsx?$/,
|
||||
options: {
|
||||
presets: ['@babel/preset-env', '@babel/preset-react'],
|
||||
},
|
||||
include: [
|
||||
/node_modules\/.*react-native.*/,
|
||||
],
|
||||
});
|
||||
return config;
|
||||
},
|
||||
async viteFinal(config) {
|
||||
return mergeConfig(config, {
|
||||
optimizeDeps: {
|
||||
esbuildOptions: {
|
||||
loader: {
|
||||
'.js': 'jsx',
|
||||
},
|
||||
},
|
||||
},
|
||||
base: './',
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, '..', 'src'),
|
||||
'react-native': 'react-native-web',
|
||||
},
|
||||
},
|
||||
assetsDir: './public/assets',
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
7
packages/ui/.storybook/manager.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { addons } from '@storybook/addons';
|
||||
import { theme } from './theme';
|
||||
|
||||
addons.setConfig({
|
||||
theme,
|
||||
});
|
||||
|
||||
31
packages/ui/.storybook/preview.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { theme } from './theme';
|
||||
import { addDecorator } from "@storybook/react";
|
||||
import { Provider } from '../src/theme';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
import "@fontsource/montserrat";
|
||||
|
||||
const ThemeDecorator = (storyFn: any) => (
|
||||
<SafeAreaProvider>
|
||||
<Provider>{storyFn()}</Provider>
|
||||
</SafeAreaProvider>
|
||||
)
|
||||
|
||||
addDecorator(ThemeDecorator);
|
||||
|
||||
export const parameters = {
|
||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/,
|
||||
},
|
||||
},
|
||||
options: {
|
||||
storySort: (b: any, a: any) =>
|
||||
a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, undefined, { numeric: true }),
|
||||
},
|
||||
docs: {
|
||||
theme,
|
||||
},
|
||||
};
|
||||
|
||||
38
packages/ui/.storybook/theme.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import { create } from '@storybook/theming';
|
||||
|
||||
const theme = create({
|
||||
base: 'light',
|
||||
|
||||
colorPrimary: '#156E80',
|
||||
colorSecondary: '#156E80',
|
||||
|
||||
// UI
|
||||
appBg: 'white',
|
||||
appContentBg: '#F7F9FA',
|
||||
appBorderColor: '#D4DBDE',
|
||||
appBorderRadius: 4,
|
||||
|
||||
// Typography
|
||||
fontBase: '"Rubik", sans-serif',
|
||||
fontCode: 'monospace',
|
||||
|
||||
// Text colors
|
||||
textColor: '#003143',
|
||||
textInverseColor: 'rgba(255,255,255,0.9)',
|
||||
|
||||
// Toolbar default and active colors
|
||||
barTextColor: '#609CA9',
|
||||
barSelectedColor: '#156E80',
|
||||
barBg: 'white',
|
||||
|
||||
// Form colors
|
||||
inputBg: 'white',
|
||||
inputBorder: '#156E80',
|
||||
inputTextColor: '#003143',
|
||||
inputBorderRadius: 4,
|
||||
|
||||
brandTitle: 'Morten\'s App Design System',
|
||||
//brandImage: 'https://zn-prod-euw1-cdn.s3.eu-west-1.amazonaws.com/logo-dark-gradient.png',
|
||||
});
|
||||
|
||||
export { theme };
|
||||
89
packages/ui/package.json
Normal file
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"name": "@morten-olsen/ui",
|
||||
"version": "1.0.0",
|
||||
"homepage": "/bob-the-algorithm",
|
||||
"jest": {
|
||||
"preset": "jest-expo"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "~17.0.21",
|
||||
"@types/react-dom": "~18.0.3"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "yarn clean && run-p build:*",
|
||||
"build:types": "tsc --emitDeclarationOnly && tsc-alias",
|
||||
"build:script": "vite build",
|
||||
"clean": "rimraf dist",
|
||||
"lint:types": "tsc --noEmit",
|
||||
"preview": "vite preview",
|
||||
"dev": "start-storybook -p 6006",
|
||||
"build:storybook": "build-storybook -o public",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^12.0.0",
|
||||
"@fontsource/montserrat": "^4.5.10",
|
||||
"@react-navigation/bottom-tabs": "^6.0.5",
|
||||
"@react-navigation/native": "^6.0.2",
|
||||
"@react-navigation/native-stack": "^6.1.0",
|
||||
"@react-navigation/stack": "^6.2.1",
|
||||
"chroma-js": "^2.4.2",
|
||||
"date-fns": "^2.28.0",
|
||||
"feather-icons": "^4.29.0",
|
||||
"parse-css-color": "^0.2.1",
|
||||
"react-feather": "^2.0.9",
|
||||
"react-native-calendar-strip": "^2.2.5",
|
||||
"react-native-calendars": "^1.1284.0",
|
||||
"react-native-collapsible": "^1.6.0",
|
||||
"react-native-gesture-handler": "^2.4.2",
|
||||
"react-native-get-random-values": "^1.8.0",
|
||||
"react-native-safe-area-context": "3.3.2",
|
||||
"react-native-screens": "~3.10.1",
|
||||
"string-to-color": "^2.2.2",
|
||||
"styled-components": "^5.3.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "17.0.1",
|
||||
"react-dom": "17.0.1",
|
||||
"react-native": "0.64.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.9",
|
||||
"@bunchtogether/vite-plugin-flow": "^1.0.2",
|
||||
"@originjs/vite-plugin-commonjs": "^1.0.3",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.5",
|
||||
"@rollup/plugin-babel": "^5.3.1",
|
||||
"@storybook/addon-actions": "^6.4.22",
|
||||
"@storybook/addon-essentials": "^6.4.22",
|
||||
"@storybook/addon-links": "^6.4.22",
|
||||
"@storybook/builder-vite": "^0.1.33",
|
||||
"@storybook/react": "^6.4.22",
|
||||
"@storybook/theming": "^6.4.22",
|
||||
"@types/chroma-js": "^2.1.3",
|
||||
"@types/feather-icons": "^4.7.0",
|
||||
"@types/react": "~17.0.21",
|
||||
"@types/react-dom": "^18.0.3",
|
||||
"@types/styled-components-react-native": "^5.1.3",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"babel-plugin-module-resolver": "^4.1.0",
|
||||
"esbuild-plugin-flow": "^0.3.2",
|
||||
"expo-cli": "^5.4.3",
|
||||
"flow-remove-types": "^2.178.0",
|
||||
"jest": "^26.6.3",
|
||||
"jest-expo": "~44.0.1",
|
||||
"postcss": "^8.4.13",
|
||||
"react": "17.0.1",
|
||||
"react-docgen": "^5.4.0",
|
||||
"react-docgen-typescript": "^2.2.2",
|
||||
"react-dom": "17.0.1",
|
||||
"react-native-web": "0.17.1",
|
||||
"react-refresh": "^0.13.0",
|
||||
"react-test-renderer": "17.0.1",
|
||||
"storybook": "^6.4.22",
|
||||
"typescript": "~4.3.5",
|
||||
"vite": "^2.9.9",
|
||||
"webpack-hot-middleware": "^2.25.1",
|
||||
"webpack-merge": "^5.8.0"
|
||||
}
|
||||
}
|
||||
39
packages/ui/src/components/base/button/index.stories.mdx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Canvas, Meta, Story, ArgsTable } from '@storybook/addon-docs';
|
||||
import { Button } from '.';
|
||||
|
||||
<Meta title="Components/Button" component={Button} />
|
||||
|
||||
# Button
|
||||
|
||||
Has following variants: Primary, secondary, outlined, text only, destructive, disabled and comes in 4 sizes.
|
||||
|
||||
## Variants
|
||||
|
||||
`type: "primary"`
|
||||
|
||||
<Canvas>
|
||||
<Story name="Primary">
|
||||
<Button type="primary" title="Foo" />
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
`type: "secondary"`
|
||||
|
||||
<Canvas>
|
||||
<Story name="Secondary">
|
||||
<Button type="secondary" title="Foo" />
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
`type: "destructive"`
|
||||
|
||||
<Canvas>
|
||||
<Story name="Destructive">
|
||||
<Button type="destructive" title="Foo" />
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
## Component arguments
|
||||
|
||||
<ArgsTable of={Button} />
|
||||
|
||||
82
packages/ui/src/components/base/button/index.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components/native';
|
||||
import { IconNames, Icon } from '../icon';
|
||||
import { Theme } from '../../../theme';
|
||||
import { Link } from '../../../typography';
|
||||
import { StyleProp, ViewStyle } from 'react-native';
|
||||
|
||||
type AccessibilityRole = 'button' | 'link' | 'image' | 'keyboardkey' | 'search' | 'text' | 'adjustable' | 'header' | 'imagebutton';
|
||||
|
||||
type ButtonProps = {
|
||||
title?: string;
|
||||
icon?: IconNames;
|
||||
onPress?: () => any;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
accessibilityRole?: AccessibilityRole;
|
||||
accessibilityLabel?: string;
|
||||
accessibilityHint?: string;
|
||||
type?: 'primary' | 'secondary' | 'destructive';
|
||||
}
|
||||
|
||||
const Touch = styled.TouchableOpacity``;
|
||||
|
||||
const Wrapper = styled.View<{
|
||||
style?: StyleProp<ViewStyle>;
|
||||
background?: keyof Theme['colors'],
|
||||
border: keyof Theme['colors'],
|
||||
theme: Theme,
|
||||
}>`
|
||||
background: ${({ background, theme }) => background ? theme.colors[background] : 'transparent'};
|
||||
border-color: ${({ border, theme }) => theme.colors[border]};
|
||||
border-width: 1px;
|
||||
padding:
|
||||
${({ theme }) => theme.margins.medium}px;
|
||||
${({ theme }) => theme.margins.small}px;
|
||||
border-radius: ${({ theme }) => theme.sizes.corners}px;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const getColors = (type: ButtonProps['type']): [keyof Theme['colors'], keyof Theme['colors'], keyof Theme['colors']] => {
|
||||
switch (type) {
|
||||
case 'primary': {
|
||||
return ['background', 'primary', 'primary'];
|
||||
}
|
||||
case 'secondary': {
|
||||
return ['primary', 'background', 'primary'];
|
||||
}
|
||||
case 'destructive': {
|
||||
return ['background', 'destructive', 'destructive'];
|
||||
}
|
||||
}
|
||||
throw new Error('Button type not supported');
|
||||
};
|
||||
|
||||
const Button = ({
|
||||
title,
|
||||
icon,
|
||||
type = 'primary',
|
||||
onPress,
|
||||
accessibilityHint,
|
||||
accessibilityRole,
|
||||
accessibilityLabel,
|
||||
style,
|
||||
}: ButtonProps) => {
|
||||
const [textColor, backgroundColor, borderColor] = getColors(type);
|
||||
return (
|
||||
<Touch
|
||||
onPress={onPress}
|
||||
accessible
|
||||
accessibilityHint={accessibilityHint}
|
||||
accessibilityRole={accessibilityRole}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
>
|
||||
<Wrapper style={style} background={backgroundColor} border={borderColor}>
|
||||
{title && <Link color={textColor}>{title}</Link>}
|
||||
{icon && <Icon name={icon} color={textColor} />}
|
||||
</Wrapper>
|
||||
</Touch>
|
||||
);
|
||||
};
|
||||
|
||||
export type { ButtonProps, AccessibilityRole };
|
||||
export { Button };
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { Fragment, ReactNode, useState } from 'react';
|
||||
import React, { ReactNode, useState } from 'react';
|
||||
import styled from 'styled-components/native';
|
||||
import Collapsible from 'react-native-collapsible';
|
||||
import { Body1 } from '#/ui/typography';
|
||||
import { Body1 } from '../../../typography';
|
||||
import { Icon } from '../icon';
|
||||
import { Row, Cell } from '../row';
|
||||
import { Row, Cell, RowProps } from '../row';
|
||||
import { Header } from './header';
|
||||
|
||||
interface ListProps<T> {
|
||||
@@ -11,7 +11,7 @@ interface ListProps<T> {
|
||||
items: T[];
|
||||
startHidden?: boolean;
|
||||
getKey: (item: T) => any;
|
||||
render: (item: T) => ReactNode;
|
||||
render: (item: T) => RowProps;
|
||||
add?: () => void;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ function Group<T = any>(props: ListProps<T> | ChildProps) {
|
||||
/>
|
||||
<Collapsible collapsed={!visible}>
|
||||
{items && items.map((item, i) => (
|
||||
<Fragment key={getKey(item) || i}>{render(item)}</Fragment>
|
||||
<Row key={getKey(item) || i} {...render(item)} />
|
||||
))}
|
||||
{children}
|
||||
{!children && (!items || items.length === 0) && (
|
||||
18
packages/ui/src/components/base/horizontal/index.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import styled from "styled-components/native";
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
const Wrapper = styled.View`
|
||||
flex-direction: row;
|
||||
`;
|
||||
|
||||
const Horizontal = ({ children }: Props) => {
|
||||
return (
|
||||
<Wrapper>
|
||||
{children}
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export { Horizontal };
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Feather, } from '@expo/vector-icons';
|
||||
import { useTheme } from 'styled-components/native';
|
||||
import { Theme } from '#/ui/theme';
|
||||
import { Theme } from '../../../theme';
|
||||
|
||||
type IconNames = keyof typeof Feather.glyphMap;
|
||||
type Props = {
|
||||
31
packages/ui/src/components/base/icon/index.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { icons } from 'feather-icons';
|
||||
import { useTheme } from 'styled-components/native';
|
||||
import { Theme } from '../../../theme';
|
||||
|
||||
type IconNames = keyof typeof icons;
|
||||
type Props = {
|
||||
size?: number;
|
||||
color?: keyof Theme['colors'];
|
||||
name: IconNames;
|
||||
}
|
||||
|
||||
function Icon({
|
||||
size = 24,
|
||||
color,
|
||||
name,
|
||||
}: Props) {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<svg
|
||||
dangerouslySetInnerHTML={{ __html: icons[name].toSvg({ color: color ? theme.colors[color] : undefined }) }}
|
||||
viewBox={`0 0 24 24`}
|
||||
width={size}
|
||||
height={size}
|
||||
fill={color ? theme.colors[color] : undefined}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export type { IconNames };
|
||||
export { Icon };
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './icon';
|
||||
export * from './horizontal';
|
||||
export * from './modal';
|
||||
export * from './page';
|
||||
export * from './popup';
|
||||