This commit is contained in:
Morten Olsen
2022-05-19 15:57:20 +02:00
parent 6181eeb0c8
commit 2b0ad8592b
156 changed files with 26987 additions and 14366 deletions

View File

@@ -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 }}

View File

@@ -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
View File

@@ -1,4 +1,5 @@
/*.log
/.yarn
node_modules/
.expo/
dist/

1
.yarnrc.yml Normal file
View File

@@ -0,0 +1 @@
nodeLinker: node-modules

View File

@@ -1,4 +0,0 @@
import 'react-native-get-random-values';
import { App } from './src/app';
export default App;

View File

@@ -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
View File

@@ -0,0 +1,15 @@
/*.log
node_modules/
.expo/
dist/
npm-debug.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
*.orig.*
web-build/
# macOS
.DS_Store

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -6,6 +6,7 @@ module.exports = function(api) {
[require.resolve('babel-plugin-module-resolver'), {
alias: {
'#': './src',
'@morten-olsen/ui': '@morten-olsen/ui/src',
},
}],
],

View 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
View 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
}

View File

@@ -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>
);
};

View File

@@ -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
View 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);

View File

@@ -0,0 +1 @@
export default '';

View File

@@ -0,0 +1,2 @@
import '@fontsource/montserrat';
export default '';

View File

@@ -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;

View File

@@ -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} />

View File

@@ -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) {

View File

@@ -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 });
},
})}
/>
);
};

View File

@@ -1,2 +1 @@
export * from './components/base';
export * from './theme';

View File

@@ -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';

View 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 };

View File

@@ -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 = () => {

View File

@@ -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";

View File

@@ -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";

View File

@@ -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} />
))}
</>
)}

View File

@@ -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} />

View File

@@ -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";

View File

@@ -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" />

View 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"]
}
}
}

View File

@@ -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
View File

@@ -0,0 +1,3 @@
/node_modules
/.expo
/public

View 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',
});
},
};

View File

@@ -0,0 +1,7 @@
import { addons } from '@storybook/addons';
import { theme } from './theme';
addons.setConfig({
theme,
});

View 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,
},
};

View 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
View 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"
}
}

View 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} />

View 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 };

View File

@@ -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) && (

View 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 };

View File

@@ -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 = {

View 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 };

View File

@@ -1,4 +1,5 @@
export * from './icon';
export * from './horizontal';
export * from './modal';
export * from './page';
export * from './popup';

Some files were not shown because too many files have changed in this diff Show More