@coldsurf/ocean-road 1.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/dist/css/global.css +30 -0
  2. package/dist/index.d.ts +641 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +733 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/native.cjs +94 -0
  7. package/dist/native.cjs.map +1 -0
  8. package/dist/native.d.cts +304 -0
  9. package/dist/native.d.cts.map +1 -0
  10. package/dist/native.d.ts +304 -0
  11. package/dist/native.d.ts.map +1 -0
  12. package/dist/native.js +94 -0
  13. package/dist/native.js.map +1 -0
  14. package/dist/next.cjs +949 -0
  15. package/dist/next.cjs.map +1 -0
  16. package/dist/next.d.cts +270 -0
  17. package/dist/next.d.cts.map +1 -0
  18. package/dist/next.d.ts +270 -0
  19. package/dist/next.d.ts.map +1 -0
  20. package/dist/next.js +949 -0
  21. package/dist/next.js.map +1 -0
  22. package/native/index.d.ts +7 -0
  23. package/next/index.d.ts +7 -0
  24. package/package.json +126 -0
  25. package/src/GlobalStyle.tsx +111 -0
  26. package/src/base/badge/badge.tsx +50 -0
  27. package/src/base/badge/index.ts +1 -0
  28. package/src/base/button/button.styled.tsx +123 -0
  29. package/src/base/button/button.tsx +60 -0
  30. package/src/base/button/button.types.ts +20 -0
  31. package/src/base/button/button.utils.ts +36 -0
  32. package/src/base/button/index.tsx +2 -0
  33. package/src/base/checkbox/checkbox.styled.ts +52 -0
  34. package/src/base/checkbox/checkbox.tsx +26 -0
  35. package/src/base/checkbox/index.ts +1 -0
  36. package/src/base/icon-button/icon-button.styled.ts +8 -0
  37. package/src/base/icon-button/icon-button.tsx +15 -0
  38. package/src/base/icon-button/icon-button.types.ts +3 -0
  39. package/src/base/icon-button/index.ts +2 -0
  40. package/src/base/index.ts +11 -0
  41. package/src/base/label/index.ts +1 -0
  42. package/src/base/label/label.styled.ts +7 -0
  43. package/src/base/label/label.tsx +27 -0
  44. package/src/base/modal/index.ts +1 -0
  45. package/src/base/modal/modal.tsx +59 -0
  46. package/src/base/spinner/index.ts +2 -0
  47. package/src/base/spinner/spinner.styled.ts +25 -0
  48. package/src/base/spinner/spinner.tsx +36 -0
  49. package/src/base/spinner/spinner.types.ts +1 -0
  50. package/src/base/switch/index.ts +1 -0
  51. package/src/base/switch/switch.styled.tsx +49 -0
  52. package/src/base/switch/switch.tsx +29 -0
  53. package/src/base/text/index.ts +1 -0
  54. package/src/base/text/text.styled.ts +17 -0
  55. package/src/base/text/text.tsx +37 -0
  56. package/src/base/text-area/index.ts +2 -0
  57. package/src/base/text-area/text-area.styled.ts +16 -0
  58. package/src/base/text-area/text-area.tsx +29 -0
  59. package/src/base/text-area/text-area.types.ts +11 -0
  60. package/src/base/text-input/index.ts +2 -0
  61. package/src/base/text-input/text-input.styled.ts +40 -0
  62. package/src/base/text-input/text-input.tsx +59 -0
  63. package/src/base/text-input/text-input.types.ts +15 -0
  64. package/src/base/toast/index.ts +2 -0
  65. package/src/base/toast/toast.tsx +60 -0
  66. package/src/base/toast/toast.types.ts +5 -0
  67. package/src/constants.ts +1 -0
  68. package/src/contexts/ColorSchemeProvider.tsx +154 -0
  69. package/src/css/global.css +30 -0
  70. package/src/extensions/accordion/accordion.hooks.ts +11 -0
  71. package/src/extensions/accordion/accordion.tsx +80 -0
  72. package/src/extensions/accordion/index.ts +1 -0
  73. package/src/extensions/app-header/app-header.hooks.ts +94 -0
  74. package/src/extensions/app-header/app-header.tsx +31 -0
  75. package/src/extensions/app-header/app-header.types.ts +1 -0
  76. package/src/extensions/app-header/index.ts +8 -0
  77. package/src/extensions/app-logo/app-logo.tsx +40 -0
  78. package/src/extensions/app-logo/index.ts +1 -0
  79. package/src/extensions/app-store-button/app-store-button.tsx +64 -0
  80. package/src/extensions/app-store-button/index.ts +1 -0
  81. package/src/extensions/brand-icon/brand-icon.android.tsx +11 -0
  82. package/src/extensions/brand-icon/brand-icon.apple.tsx +11 -0
  83. package/src/extensions/brand-icon/brand-icon.google.tsx +11 -0
  84. package/src/extensions/brand-icon/brand-icon.tsx +22 -0
  85. package/src/extensions/brand-icon/index.ts +1 -0
  86. package/src/extensions/color-scheme-toggle/color-scheme-toggle.tsx +76 -0
  87. package/src/extensions/color-scheme-toggle/index.ts +1 -0
  88. package/src/extensions/dropdown/dropdown.menu-item.tsx +237 -0
  89. package/src/extensions/dropdown/dropdown.result-item.tsx +26 -0
  90. package/src/extensions/dropdown/dropdown.styled.tsx +48 -0
  91. package/src/extensions/dropdown/dropdown.trigger.tsx +72 -0
  92. package/src/extensions/dropdown/dropdown.tsx +222 -0
  93. package/src/extensions/dropdown/dropdown.types.ts +3 -0
  94. package/src/extensions/dropdown/dropdown.utils.ts +40 -0
  95. package/src/extensions/dropdown/index.ts +14 -0
  96. package/src/extensions/error-ui/index.ts +7 -0
  97. package/src/extensions/error-ui/network-error/index.ts +1 -0
  98. package/src/extensions/error-ui/network-error/network-error.styled.ts +16 -0
  99. package/src/extensions/error-ui/network-error/network-error.tsx +14 -0
  100. package/src/extensions/error-ui/unknown-error/index.ts +1 -0
  101. package/src/extensions/error-ui/unknown-error/unknown-error.styled.ts +16 -0
  102. package/src/extensions/error-ui/unknown-error/unknown-error.tsx +14 -0
  103. package/src/extensions/full-screen-modal/full-screen-modal.tsx +52 -0
  104. package/src/extensions/full-screen-modal/index.ts +1 -0
  105. package/src/extensions/grid-card-image/grid-card-image.tsx +11 -0
  106. package/src/extensions/grid-card-image/index.ts +1 -0
  107. package/src/extensions/grid-card-image-empty/grid-card-image-empty.tsx +11 -0
  108. package/src/extensions/grid-card-image-empty/index.ts +1 -0
  109. package/src/extensions/grid-card-item/grid-card-item.masonry.styled.tsx +95 -0
  110. package/src/extensions/grid-card-item/grid-card-item.masonry.tsx +63 -0
  111. package/src/extensions/grid-card-item/grid-card-item.styled.tsx +93 -0
  112. package/src/extensions/grid-card-item/grid-card-item.subscribe-btn-layout.tsx +30 -0
  113. package/src/extensions/grid-card-item/grid-card-item.tsx +81 -0
  114. package/src/extensions/grid-card-item/index.ts +2 -0
  115. package/src/extensions/grid-card-list/grid-card-list.masonry.styled.tsx +45 -0
  116. package/src/extensions/grid-card-list/grid-card-list.masonry.tsx +58 -0
  117. package/src/extensions/grid-card-list/grid-card-list.styled.tsx +40 -0
  118. package/src/extensions/grid-card-list/grid-card-list.tsx +59 -0
  119. package/src/extensions/grid-card-list/index.ts +2 -0
  120. package/src/extensions/grid-card-list-empty/grid-card-list-empty.tsx +38 -0
  121. package/src/extensions/grid-card-list-empty/index.ts +1 -0
  122. package/src/extensions/grid-card-list-load-more/grid-card-list-load-more.styled.tsx +15 -0
  123. package/src/extensions/grid-card-list-load-more/grid-card-list-load-more.tsx +43 -0
  124. package/src/extensions/grid-card-list-load-more/index.ts +1 -0
  125. package/src/extensions/index.ts +38 -0
  126. package/src/extensions/menu-item/index.ts +1 -0
  127. package/src/extensions/menu-item/menu-item.tsx +87 -0
  128. package/src/extensions/sns-icon/index.ts +1 -0
  129. package/src/extensions/sns-icon/sns-icon.facebook.tsx +11 -0
  130. package/src/extensions/sns-icon/sns-icon.instagram.tsx +11 -0
  131. package/src/extensions/sns-icon/sns-icon.tsx +24 -0
  132. package/src/extensions/sns-icon/sns-icon.x.tsx +11 -0
  133. package/src/extensions/sns-icon/sns-icon.youtube.tsx +11 -0
  134. package/src/index.ts +8 -0
  135. package/src/native/button/button.styled.tsx +99 -0
  136. package/src/native/button/button.tsx +42 -0
  137. package/src/native/button/index.ts +1 -0
  138. package/src/native/contexts/color-scheme-context/color-scheme-context.tsx +45 -0
  139. package/src/native/contexts/color-scheme-context/index.ts +1 -0
  140. package/src/native/contexts/index.ts +1 -0
  141. package/src/native/icon-button/icon-button.styled.ts +6 -0
  142. package/src/native/icon-button/icon-button.tsx +33 -0
  143. package/src/native/icon-button/icon-button.types.ts +14 -0
  144. package/src/native/icon-button/icon-button.utils.ts +114 -0
  145. package/src/native/icon-button/index.ts +1 -0
  146. package/src/native/index.ts +9 -0
  147. package/src/native/modal/index.ts +2 -0
  148. package/src/native/modal/modal.styled.ts +17 -0
  149. package/src/native/modal/modal.tsx +21 -0
  150. package/src/native/modal/modal.types.ts +8 -0
  151. package/src/native/profile-thumbnail/index.ts +1 -0
  152. package/src/native/profile-thumbnail/profile-thumbnail.tsx +91 -0
  153. package/src/native/spinner/index.ts +1 -0
  154. package/src/native/spinner/spinner.tsx +75 -0
  155. package/src/native/text/index.ts +2 -0
  156. package/src/native/text/text.tsx +51 -0
  157. package/src/native/text/text.types.ts +5 -0
  158. package/src/native/text-input/index.ts +2 -0
  159. package/src/native/text-input/text-input.tsx +72 -0
  160. package/src/native/text-input/text-input.types.ts +3 -0
  161. package/src/native/toast/index.ts +2 -0
  162. package/src/native/toast/toast.styled.ts +40 -0
  163. package/src/native/toast/toast.tsx +23 -0
  164. package/src/native/toast/toast.types.ts +10 -0
  165. package/src/next/app-footer/app-footer.tsx +250 -0
  166. package/src/next/app-footer/index.ts +1 -0
  167. package/src/next/app-header/app-header.fixed-header.tsx +83 -0
  168. package/src/next/app-header/app-header.full-screen-mobile-accordion-drawer.tsx +131 -0
  169. package/src/next/app-header/app-header.logo.tsx +50 -0
  170. package/src/next/app-header/app-header.modal-mobile-accordion-drawer.tsx +69 -0
  171. package/src/next/app-header/app-header.styled.ts +160 -0
  172. package/src/next/app-header/app-header.tsx +91 -0
  173. package/src/next/app-header/index.ts +13 -0
  174. package/src/next/global-link/global-link.store.ts +41 -0
  175. package/src/next/global-link/global-link.tsx +52 -0
  176. package/src/next/global-link/global-link.utils.ts +9 -0
  177. package/src/next/global-link/index.ts +3 -0
  178. package/src/next/grid-card-item/grid-card-item.masonry.tsx +23 -0
  179. package/src/next/grid-card-item/grid-card-item.tsx +23 -0
  180. package/src/next/grid-card-item/index.ts +2 -0
  181. package/src/next/index.ts +16 -0
  182. package/src/next/new-tab-link/index.ts +1 -0
  183. package/src/next/new-tab-link/new-tab-link.tsx +15 -0
  184. package/src/next/route-loading/index.ts +1 -0
  185. package/src/next/route-loading/route-loading.tsx +21 -0
  186. package/src/tokens/index.ts +2 -0
  187. package/src/tokens/tokens.ts +8 -0
  188. package/src/tokens/tokens.types.ts +7 -0
  189. package/src/utils/breakpoints.ts +9 -0
  190. package/src/utils/common-styles.ts +23 -0
  191. package/src/utils/index.ts +2 -0
  192. package/src/utils/media.ts +23 -0
  193. package/src/utils/use-prevent-scroll-effect.ts +19 -0
  194. package/src/utils/with-id.ts +3 -0
  195. package/src/utils/with-stop-propagation.ts +10 -0
@@ -0,0 +1,7 @@
1
+ // This file is needed for projects that have `moduleResolution` set to `node`
2
+ // in their tsconfig.json to be able to `import {} from '@coldsurfers/ocean-road/native'`.
3
+ // Other module resolutions strategies will look for the `exports` in `package.json`,
4
+ // but with `node`, TypeScript will look for a .d.ts file with that name at the
5
+ // root of the package.
6
+
7
+ export * from '../dist/native';
@@ -0,0 +1,7 @@
1
+ // This file is needed for projects that have `moduleResolution` set to `node`
2
+ // in their tsconfig.json to be able to `import {} from '@coldsurfers/ocean-road/next'`.
3
+ // Other module resolutions strategies will look for the `exports` in `package.json`,
4
+ // but with `node`, TypeScript will look for a .d.ts file with that name at the
5
+ // root of the package.
6
+
7
+ export * from '../dist/next';
package/package.json ADDED
@@ -0,0 +1,126 @@
1
+ {
2
+ "name": "@coldsurf/ocean-road",
3
+ "author": {
4
+ "name": "Dong-Ho Choi",
5
+ "email": "imcoldsurf@gmail.com",
6
+ "url": "https://coldsurf.io"
7
+ },
8
+ "version": "1.13.2",
9
+ "bugs": {
10
+ "url": "https://github.com/coldsurfers/ocean-road/issues"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/coldsurfers/ocean-road",
15
+ "directory": "packages/ocean-road"
16
+ },
17
+ "homepage": "https://storybook.ocean-road.coldsurf.io",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "default": "./dist/index.js"
22
+ },
23
+ "./next": {
24
+ "types": "./dist/next.d.ts",
25
+ "default": "./dist/next.cjs"
26
+ },
27
+ "./native": {
28
+ "types": "./dist/native.d.ts",
29
+ "default": "./dist/native.cjs"
30
+ }
31
+ },
32
+ "license": "MIT",
33
+ "type": "module",
34
+ "module": "dist/index.js",
35
+ "source": "src/",
36
+ "types": "dist/index.d.ts",
37
+ "files": [
38
+ "src",
39
+ "next",
40
+ "native",
41
+ "dist",
42
+ "package.json"
43
+ ],
44
+ "scripts": {
45
+ "build": "pnpm build:core",
46
+ "build:core": "tsdown",
47
+ "lint": "biome lint .",
48
+ "lint:fix": "biome lint --write .",
49
+ "format": "biome format .",
50
+ "format:fix": "biome format --write .",
51
+ "check": "biome check .",
52
+ "check:fix": "biome check --write .",
53
+ "check:type": "tsc --noEmit"
54
+ },
55
+ "dependencies": {
56
+ "@coldsurf/shared-utils": "1.1.15",
57
+ "ts-pattern": "5.8.0"
58
+ },
59
+ "devDependencies": {
60
+ "@babel/preset-react": "^7.18.6",
61
+ "@babel/preset-typescript": "^7.21.4",
62
+ "@biomejs/biome": "1.9.4",
63
+ "@coldsurfers/ocean-road-design-tokens": "1.10.8",
64
+ "@emotion/css": "11.10.6",
65
+ "@emotion/native": "11.11.0",
66
+ "@emotion/react": "11.10.6",
67
+ "@emotion/styled": "11.10.6",
68
+ "@types/react": "18.3.27",
69
+ "@types/react-dom": "19.0.3",
70
+ "framer-motion": "11.11.13",
71
+ "lucide-react": "0.469.0",
72
+ "lucide-react-native": "0.468.0",
73
+ "next": "15.5.9",
74
+ "overlay-kit": "^1.8.6",
75
+ "prettier": "*",
76
+ "react": "19.2.3",
77
+ "react-dom": "19.2.3",
78
+ "react-native": "0.81.4",
79
+ "react-native-reanimated": "4.1.2",
80
+ "react-native-svg": "15.13.0",
81
+ "react-native-worklets": "0.6.0",
82
+ "tsdown": "^0.11.9"
83
+ },
84
+ "peerDependencies": {
85
+ "@emotion/css": "^11.10.6",
86
+ "@emotion/native": "^11.11.0",
87
+ "@emotion/react": "^11.10.6",
88
+ "@emotion/styled": "^11.10.6",
89
+ "lucide-react-native": "^0.468.0",
90
+ "react": ">= 19.1.0",
91
+ "react-dom": ">= 19.1.4",
92
+ "react-native-reanimated": ">= 3.17.1",
93
+ "react-native-svg": "^15.11.1",
94
+ "react-native-worklets": "^0.6.0"
95
+ },
96
+ "peerDependenciesMeta": {
97
+ "next": {
98
+ "optional": true
99
+ },
100
+ "react-dom": {
101
+ "optional": true
102
+ },
103
+ "react-native": {
104
+ "optional": true
105
+ },
106
+ "react-native-reanimated": {
107
+ "optional": true
108
+ },
109
+ "react-native-worklets": {
110
+ "optional": true
111
+ },
112
+ "react-native-svg": {
113
+ "optional": true
114
+ },
115
+ "lucide-react-native": {
116
+ "optional": true
117
+ },
118
+ "overlay-kit": {
119
+ "optional": true
120
+ }
121
+ },
122
+ "publishConfig": {
123
+ "registry": "https://registry.npmjs.org/",
124
+ "access": "public"
125
+ }
126
+ }
@@ -0,0 +1,111 @@
1
+ import { Global, css } from '@emotion/react';
2
+ import { darkModeTheme, lightModeTheme, themeToStyles } from './contexts/ColorSchemeProvider';
3
+ import { colors, semantics } from './tokens';
4
+
5
+ type Props = {
6
+ themeStorageItem?: string;
7
+ };
8
+
9
+ const SAFE_STORAGE_KEY_PATTERN = /^[A-Za-z0-9._:@/-]+$/;
10
+ const DEFAULT_THEME_STORAGE_KEY = 'ocean-road-theme';
11
+
12
+ export default function GlobalStyle({ themeStorageItem }: Props) {
13
+ const safeThemeStorageItem =
14
+ themeStorageItem && SAFE_STORAGE_KEY_PATTERN.test(themeStorageItem)
15
+ ? themeStorageItem
16
+ : DEFAULT_THEME_STORAGE_KEY;
17
+
18
+ return (
19
+ <>
20
+ <Global
21
+ styles={css`
22
+ html,
23
+ body {
24
+ padding: 0;
25
+ margin: 0;
26
+ }
27
+
28
+ html {
29
+ ${themeToStyles(lightModeTheme)}
30
+ }
31
+ html.dark {
32
+ ${themeToStyles(darkModeTheme)}
33
+ }
34
+
35
+ body {
36
+ background-color: ${semantics.color.background[2]};
37
+ color: ${semantics.color.foreground[1]};
38
+ white-space: pre-wrap;
39
+ }
40
+
41
+ a {
42
+ color: ${colors.oc.blue[5].value};
43
+ text-decoration: none;
44
+ }
45
+
46
+ * {
47
+ box-sizing: border-box;
48
+ }
49
+ `}
50
+ />
51
+ {themeStorageItem && (
52
+ <script
53
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: <explanation>
54
+ dangerouslySetInnerHTML={{
55
+ __html: `
56
+ (function () {
57
+ var themeStorage = ${JSON.stringify(safeThemeStorageItem)};
58
+
59
+ function setTheme(newTheme) {
60
+ window.__theme = newTheme;
61
+ if (newTheme === 'dark') {
62
+ document.documentElement.classList.add('dark');
63
+ } else if (newTheme === 'light') {
64
+ document.documentElement.classList.remove('dark');
65
+ }
66
+ }
67
+
68
+ var preferredTheme;
69
+ try {
70
+ preferredTheme = localStorage.getItem(themeStorage);
71
+ } catch (err) { }
72
+
73
+ window.__setPreferredTheme = function(newTheme) {
74
+ preferredTheme = newTheme;
75
+ setTheme(newTheme);
76
+ try {
77
+ localStorage.setItem(themeStorage, newTheme);
78
+ } catch (err) {
79
+ console.error(err);
80
+ }
81
+ };
82
+
83
+ var initialTheme = preferredTheme;
84
+ var darkQuery = window.matchMedia('(prefers-color-scheme: dark)');
85
+
86
+ if (!initialTheme) {
87
+ initialTheme = darkQuery.matches ? 'dark' : 'light';
88
+ }
89
+ setTheme(initialTheme);
90
+
91
+ darkQuery.addEventListener('change', function (e) {
92
+ if (!preferredTheme) {
93
+ setTheme(e.matches ? 'dark' : 'light');
94
+ }
95
+ });
96
+
97
+ // Detect whether the browser is Mac to display platform specific content
98
+ // An example of such content can be the keyboard shortcut displayed in the search bar
99
+ // document.documentElement.classList.add(
100
+ // window.navigator.platform.includes('Mac')
101
+ // ? "platform-mac"
102
+ // : "platform-win"
103
+ // );
104
+ })();
105
+ `,
106
+ }}
107
+ />
108
+ )}
109
+ </>
110
+ );
111
+ }
@@ -0,0 +1,50 @@
1
+ import styled from '@emotion/styled';
2
+ import type { icons as Icons } from 'lucide-react';
3
+ import { type ReactElement, forwardRef } from 'react';
4
+ import { semantics } from '../../tokens';
5
+ import { createStyledIcon } from '../button/button.styled';
6
+ import { Text } from '../text';
7
+
8
+ const StyledFeedNavigationItem = styled.div<{ $isHighlighted?: boolean }>`
9
+ display: flex;
10
+ border-radius: 999px;
11
+ background-color: ${({ $isHighlighted }) => ($isHighlighted ? semantics.color.background[4] : semantics.color.background[3])};
12
+ padding: 0.5rem 1rem;
13
+ cursor: pointer;
14
+ user-select: none;
15
+ `;
16
+
17
+ const StyledFeedNavigationItemText = styled(Text)`
18
+ font-size: 1rem;
19
+ font-weight: 600;
20
+ color: ${semantics.color.foreground[1]};
21
+ `;
22
+
23
+ type Props = {
24
+ leftIcon?: keyof typeof Icons;
25
+ onClick?: () => void;
26
+ isHighlighted?: boolean;
27
+ children?: string | ReactElement;
28
+ };
29
+
30
+ export const Badge = forwardRef<HTMLDivElement, Props>(
31
+ ({ children, leftIcon, isHighlighted, onClick, ...otherProps }, ref) => {
32
+ return (
33
+ <StyledFeedNavigationItem
34
+ ref={ref}
35
+ $isHighlighted={isHighlighted}
36
+ onClick={onClick}
37
+ {...otherProps}
38
+ >
39
+ {leftIcon && createStyledIcon(leftIcon, 'lg', 'left', 'transparent')}
40
+ {typeof children === 'string' ? (
41
+ <StyledFeedNavigationItemText>{children}</StyledFeedNavigationItemText>
42
+ ) : (
43
+ children
44
+ )}
45
+ </StyledFeedNavigationItem>
46
+ );
47
+ }
48
+ );
49
+
50
+ Badge.displayName = 'Badge';
@@ -0,0 +1 @@
1
+ export * from './badge';
@@ -0,0 +1,123 @@
1
+ import styled from '@emotion/styled';
2
+ import { icons as Icons } from 'lucide-react';
3
+ import { Text } from '../text';
4
+ import type { ButtonTheme } from './button.types';
5
+ import { getButtonBackgroundColor, getButtonForegroundColor } from './button.utils';
6
+
7
+ export const StyledButton = styled.button<{
8
+ colorTheme: ButtonTheme;
9
+ size: 'lg' | 'md' | 'sm';
10
+ }>`
11
+ align-items: center;
12
+ justify-content: center;
13
+ background-color: ${({ colorTheme }) => getButtonBackgroundColor(colorTheme)};
14
+ border: none;
15
+ font-family: inherit;
16
+
17
+ padding: ${(props) => {
18
+ switch (props.size) {
19
+ case 'lg':
20
+ return '18px';
21
+ case 'md':
22
+ return '14px';
23
+ default:
24
+ return '10px';
25
+ }
26
+ }};
27
+
28
+ border-radius: ${(props) => {
29
+ switch (props.size) {
30
+ case 'lg':
31
+ return '24px';
32
+ case 'md':
33
+ return '22px';
34
+ default:
35
+ return '20px';
36
+ }
37
+ }};
38
+
39
+ cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
40
+
41
+ opacity: ${(props) => (props.colorTheme === 'transparentDarkGray' ? 0.5 : 1.0)};
42
+
43
+ display: flex;
44
+ align-items: center;
45
+ justify-content: center;
46
+
47
+ &:hover {
48
+ opacity: ${({ disabled }) => (disabled ? 1.0 : 0.75)};
49
+ }
50
+
51
+ &:active {
52
+ opacity: ${({ disabled }) => (disabled ? 1.0 : 0.5)};
53
+ }
54
+ `;
55
+
56
+ export const StyledButtonText = styled(Text)<{
57
+ colorTheme: ButtonTheme;
58
+ size: 'lg' | 'md' | 'sm';
59
+ textWeight: 'light' | 'medium' | 'bold';
60
+ }>`
61
+ font-weight: ${({ textWeight }) => {
62
+ switch (textWeight) {
63
+ case 'light':
64
+ return '300';
65
+ case 'medium':
66
+ return '500';
67
+ default:
68
+ return '700';
69
+ }
70
+ }};
71
+ color: ${({ colorTheme }) => getButtonForegroundColor(colorTheme)};
72
+ font-family: inherit;
73
+ margin: unset;
74
+
75
+ font-size: ${(props) => {
76
+ switch (props.size) {
77
+ case 'lg':
78
+ return '16px';
79
+ case 'md':
80
+ return '14px';
81
+ default:
82
+ return '12px';
83
+ }
84
+ }};
85
+ `;
86
+
87
+ const iconSize = (size: 'lg' | 'md' | 'sm') => {
88
+ switch (size) {
89
+ case 'lg':
90
+ return '18px';
91
+ case 'md':
92
+ return '16px';
93
+ default:
94
+ return '14px';
95
+ }
96
+ };
97
+
98
+ export const createStyledIcon = (
99
+ icon: keyof typeof Icons,
100
+ size: 'lg' | 'md' | 'sm',
101
+ position: 'left' | 'right',
102
+ colorTheme: ButtonTheme
103
+ ) => {
104
+ const TargetIcon = styled(Icons[icon])`
105
+ color: ${getButtonForegroundColor(colorTheme)};
106
+ `;
107
+
108
+ return (
109
+ <TargetIcon
110
+ size={iconSize(size)}
111
+ // strokeWidth={2.5}
112
+ style={{
113
+ marginLeft: position === 'right' ? 4 : undefined,
114
+ marginRight: position === 'left' ? 4 : undefined,
115
+ }}
116
+ />
117
+ );
118
+ };
119
+
120
+ export const StyledIconWrapper = styled.div<{ $position: 'left' | 'right' }>`
121
+ margin-right: ${({ $position }) => ($position === 'left' ? '4px' : '0px')};
122
+ margin-left: ${({ $position }) => ($position === 'right' ? '4px' : '0px')};
123
+ `;
@@ -0,0 +1,60 @@
1
+ import type { icons as Icons } from 'lucide-react';
2
+ import { type ButtonHTMLAttributes, forwardRef } from 'react';
3
+ import {
4
+ StyledButton,
5
+ StyledButtonText,
6
+ StyledIconWrapper,
7
+ createStyledIcon,
8
+ } from './button.styled';
9
+ import type { ButtonProps } from './button.types';
10
+
11
+ export const Button = forwardRef<
12
+ HTMLButtonElement,
13
+ ButtonProps & ButtonHTMLAttributes<HTMLButtonElement>
14
+ >(
15
+ (
16
+ {
17
+ variant = 'indigo',
18
+ leftIcon,
19
+ rightIcon,
20
+ textWeight = 'medium',
21
+ size = 'md',
22
+ children,
23
+ className,
24
+ ...otherProps
25
+ },
26
+ ref
27
+ ) => {
28
+ return (
29
+ <StyledButton
30
+ ref={ref}
31
+ colorTheme={variant}
32
+ size={size}
33
+ className={className}
34
+ {...otherProps}
35
+ >
36
+ {typeof children === 'string' ? (
37
+ <>
38
+ {typeof leftIcon === 'string' ? (
39
+ createStyledIcon(leftIcon as keyof typeof Icons, size, 'left', variant)
40
+ ) : (
41
+ <StyledIconWrapper $position="left">{leftIcon}</StyledIconWrapper>
42
+ )}
43
+ <StyledButtonText size={size} textWeight={textWeight} colorTheme={variant}>
44
+ {children}
45
+ </StyledButtonText>
46
+ {typeof rightIcon === 'string' ? (
47
+ createStyledIcon(rightIcon as keyof typeof Icons, size, 'right', variant)
48
+ ) : (
49
+ <StyledIconWrapper $position="right">{rightIcon}</StyledIconWrapper>
50
+ )}
51
+ </>
52
+ ) : (
53
+ children
54
+ )}
55
+ </StyledButton>
56
+ );
57
+ }
58
+ );
59
+
60
+ Button.displayName = 'Button';
@@ -0,0 +1,20 @@
1
+ import type { icons } from 'lucide-react';
2
+ import type { PropsWithChildren, ReactElement } from 'react';
3
+
4
+ export type ButtonProps = PropsWithChildren<{
5
+ // @TODO: remove theme prop
6
+ theme?: ButtonTheme;
7
+ variant?: ButtonTheme;
8
+ size?: 'lg' | 'md' | 'sm';
9
+ leftIcon?: keyof typeof icons | ReactElement;
10
+ rightIcon?: keyof typeof icons | ReactElement;
11
+ textWeight?: 'light' | 'medium' | 'bold';
12
+ }>;
13
+
14
+ export type ButtonTheme =
15
+ | 'transparent'
16
+ | 'transparentDarkGray'
17
+ | 'white'
18
+ | 'pink'
19
+ | 'indigo'
20
+ | 'border';
@@ -0,0 +1,36 @@
1
+ import { colors, semantics } from '../../tokens';
2
+ import type { ButtonTheme } from './button.types';
3
+
4
+ export const getButtonBackgroundColor = (colorTheme: ButtonTheme) => {
5
+ switch (colorTheme) {
6
+ case 'indigo':
7
+ return colors.oc.indigo[9].value;
8
+ case 'pink':
9
+ return colors.oc.pink[9].value;
10
+ case 'transparent':
11
+ return 'transparent';
12
+ case 'transparentDarkGray':
13
+ return colors.oc.black.value;
14
+ case 'white':
15
+ return colors.oc.white.value;
16
+ // @todo: for now, border type only supports react native, (do not support dark mode)
17
+ case 'border':
18
+ return colors.oc.white.value;
19
+ default:
20
+ return colors.oc.indigo[9].value;
21
+ }
22
+ };
23
+
24
+ export const getButtonForegroundColor = (colorTheme: ButtonTheme) => {
25
+ switch (colorTheme) {
26
+ case 'transparent':
27
+ case 'transparentDarkGray':
28
+ case 'white':
29
+ return semantics.color.foreground[1];
30
+ // @todo: for now, border type only supports react native
31
+ case 'border':
32
+ return colors.oc.black.value;
33
+ default:
34
+ return colors.oc.white.value;
35
+ }
36
+ };
@@ -0,0 +1,2 @@
1
+ export * from './button';
2
+ export * from './button.types';
@@ -0,0 +1,52 @@
1
+ import styled from '@emotion/styled';
2
+ import { Check } from 'lucide-react';
3
+ import { semantics } from '../../tokens';
4
+
5
+ const sizesStringify = (size: 'lg' | 'md' | 'sm') => {
6
+ switch (size) {
7
+ case 'lg':
8
+ return '32px';
9
+ case 'md':
10
+ return '24px';
11
+ case 'sm':
12
+ return '16px';
13
+ }
14
+ };
15
+
16
+ export const StyledCheckboxLabel = styled.label`
17
+ display: inline-flex;
18
+ align-items: center;
19
+ cursor: pointer;
20
+
21
+ position: relative;
22
+ `;
23
+
24
+ export const StyledCheckboxInput = styled.input<{ $size: 'lg' | 'md' | 'sm' }>`
25
+ appearance: none;
26
+ width: ${({ $size }) => sizesStringify($size)};
27
+ height: ${({ $size }) => sizesStringify($size)};
28
+ border: 2px solid #333;
29
+ border-radius: 4px;
30
+ background-color: #fff;
31
+ margin-right: 8px;
32
+ position: relative;
33
+
34
+ &:checked {
35
+ background-color: ${semantics.color.background[4]};
36
+ border-color: ${semantics.color.background[4]};
37
+ }
38
+
39
+ &:checked + svg {
40
+ display: block;
41
+ }
42
+ `;
43
+
44
+ export const StyledCheckboxIcon = styled(Check)<{ size: 'lg' | 'md' | 'sm' }>`
45
+ position: absolute;
46
+ top: 6px;
47
+ left: 6px;
48
+
49
+ width: ${({ size }) => `calc(${sizesStringify(size)} - 4px)`};
50
+ height: ${({ size }) => `calc(${sizesStringify(size)} - 4px)`};
51
+ display: none; /* Hidden by default */
52
+ `;
@@ -0,0 +1,26 @@
1
+ import { type InputHTMLAttributes, forwardRef, memo } from 'react';
2
+ import { Text } from '../text';
3
+ import { StyledCheckboxIcon, StyledCheckboxInput, StyledCheckboxLabel } from './checkbox.styled';
4
+
5
+ type CheckboxProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'formAction'> & {
6
+ size?: 'lg' | 'md' | 'sm';
7
+ labelText?: string;
8
+ };
9
+
10
+ export const Checkbox = memo(
11
+ forwardRef<HTMLInputElement, CheckboxProps>(
12
+ ({ size = 'md', labelText, style, ...otherProps }, ref) => {
13
+ return (
14
+ <StyledCheckboxLabel style={style}>
15
+ <StyledCheckboxInput ref={ref} type="checkbox" $size={size} {...otherProps} />
16
+ <StyledCheckboxIcon size={size} width={undefined} height={undefined} />
17
+ <Text as="p" style={{ margin: 'unset' }}>
18
+ {labelText}
19
+ </Text>
20
+ </StyledCheckboxLabel>
21
+ );
22
+ }
23
+ )
24
+ );
25
+
26
+ Checkbox.displayName = 'Checkbox';
@@ -0,0 +1 @@
1
+ export * from './checkbox';
@@ -0,0 +1,8 @@
1
+ import styled from '@emotion/styled';
2
+
3
+ export const StyledIconButton = styled.button`
4
+ background: initial;
5
+ border-radius: 50%;
6
+ border: none;
7
+ cursor: pointer;
8
+ `;
@@ -0,0 +1,15 @@
1
+ import { forwardRef } from 'react';
2
+ import { StyledIconButton } from './icon-button.styled';
3
+ import type { IconButtonProps } from './icon-button.types';
4
+
5
+ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
6
+ ({ children, ...otherProps }, ref) => {
7
+ return (
8
+ <StyledIconButton ref={ref} {...otherProps}>
9
+ {children}
10
+ </StyledIconButton>
11
+ );
12
+ }
13
+ );
14
+
15
+ IconButton.displayName = 'IconButton';
@@ -0,0 +1,3 @@
1
+ import type { ButtonHTMLAttributes } from 'react';
2
+
3
+ export type IconButtonProps = {} & ButtonHTMLAttributes<HTMLButtonElement>;
@@ -0,0 +1,2 @@
1
+ export * from './icon-button';
2
+ export * from './icon-button.types';
@@ -0,0 +1,11 @@
1
+ export { Button } from './button';
2
+ export * from './checkbox';
3
+ export * from './icon-button';
4
+ export * from './modal';
5
+ export * from './spinner';
6
+ export * from './text';
7
+ export * from './text-area';
8
+ export * from './text-input';
9
+ export * from './toast';
10
+ export * from './switch';
11
+ export * from './badge';
@@ -0,0 +1 @@
1
+ export * from './label';