From 5465098c124c1dbc2ebcee43d3b90802aeda3950 Mon Sep 17 00:00:00 2001 From: Chaitya Jodhavat Date: Mon, 14 Apr 2025 13:27:15 -0700 Subject: [PATCH 1/8] finished buttons --- src/components/inputs-dropdowns/Input.tsx | 58 ++++++++++++++ src/components/inputs-dropdowns/inputs.css | 88 ++++++++++++++++++++++ src/stories/Inputs.stories.ts | 53 +++++++++++++ 3 files changed, 199 insertions(+) create mode 100644 src/components/inputs-dropdowns/Input.tsx create mode 100644 src/components/inputs-dropdowns/inputs.css create mode 100644 src/stories/Inputs.stories.ts diff --git a/src/components/inputs-dropdowns/Input.tsx b/src/components/inputs-dropdowns/Input.tsx new file mode 100644 index 0000000..a569a8d --- /dev/null +++ b/src/components/inputs-dropdowns/Input.tsx @@ -0,0 +1,58 @@ +import React, { useState, useRef } from 'react'; +import './inputs.css' + +export interface InputProps { + theme?: 'light' | 'dark'; // Define theme prop + type?: 'primary' | 'danger'; + size?: 'desktop' | 'mobile'; + suggestions?: string[]; + onChange?: (value: string) => void; + +} + +export const Input: React.FC = ({ + theme = "light", + type = 'primary', + size = 'desktop', + suggestions = [], + onChange +}) => { + const [value, setValue]= useState(''); + const inputRef = useRef(null); + + const bestSuggestion = suggestions.find(s => + s.toLowerCase().startsWith(value.toLowerCase()) && s.toLowerCase() !== value.toLowerCase()) + || ''; + const handleChange = (e: React.ChangeEvent) => { + const newVal = e.target.value; + setValue(newVal); + onChange?.(newVal); + }; + const handleKeyDown = (e: React.KeyboardEvent) => { + if ((e.key === 'ArrowRight' || e.key === 'Tab') && bestSuggestion) { + e.preventDefault(); + setValue(bestSuggestion); + onChange?.(bestSuggestion); + + } + } + return ( + +
+ + {value && bestSuggestion && ( +
+ {value} + {bestSuggestion.slice(value.length)} +
+ )} +
+ + ); +} \ No newline at end of file diff --git a/src/components/inputs-dropdowns/inputs.css b/src/components/inputs-dropdowns/inputs.css new file mode 100644 index 0000000..0457698 --- /dev/null +++ b/src/components/inputs-dropdowns/inputs.css @@ -0,0 +1,88 @@ + +.inline-suggest-wrapper { + position: relative; + display: inline-block; + width: 291px; /* match input width */ + font-family: "DM Sans", sans-serif; + } + +.inline-suggest-wrapper.desktop { + font-size: 18px; /* match .input.desktop */ + line-height: 24px; /* match .input.desktop */ +} +.inline-suggest-wrapper.mobile { + font-size: 16px; + line-height: 21px; +} + +.inline-suggestion { + top: 50%; + position: absolute; + transform: translateY(-50%); + + left: 0; + padding: 10px 20px 10px 20px; + height: 100%; + display: flex; + align-items: center; + font-family: inherit; + font-weight: 400; + font-size: inherit; + line-height: inherit; + white-space: pre; +} +.inline-suggestion span:first-child { + color: transparent; + user-select: none; + } +.inline-suggestion.light { + color: var(--Surface-Elevation-border, #979797); +} +.inline-suggestion.dark { + color: #797C8B; +} +.input { + position: relative; + width: 291px; + border-radius: 16px; + justify-content: space-between; + padding: 10px 20px 10px 20px; + font-family: "DM Sans", sans-serif; + font-weight: 400; + box-sizing: border-box; + letter-spacing: 0; + vertical-align: middle; +} + + +.input.light { + background-color: #FBFBFB; + +} + +.input.dark { + background-color: #2F3037; + color: var(--Surface-Text-color-text-color, #FFFFFF); + +} + +.input.primary { + border: 1px solid var(--Buttons-CTA-Button-Secondary-secondary-text, #000000); +} + +.input.danger { + border: 1px solid #E36370; + color: #E36370; +} + +.input.desktop { + font-size: 18px; + line-height: 24px; + height: 67px; +} + +.input.mobile { + font-size: 16px; + line-height: 21px; + height: 58px; +} \ No newline at end of file diff --git a/src/stories/Inputs.stories.ts b/src/stories/Inputs.stories.ts new file mode 100644 index 0000000..93f5f04 --- /dev/null +++ b/src/stories/Inputs.stories.ts @@ -0,0 +1,53 @@ +import { fn } from '@storybook/test'; + +import { Input } from '../components/inputs-dropdowns/Input'; + +// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export +export default { + title: 'Example/Input', + component: Input, + parameters: { + // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout + layout: 'centered', + }, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs + tags: ['autodocs'], + // More on argTypes: https://storybook.js.org/docs/api/argtypes + argTypes: { + backgroundColor: { control: 'color' }, + suggestions: { + control: { type: 'object' }, + description: 'Autocomplete suggestions', + defaultValue: ['abacus', 'boy', 'car'], + } + }, + // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args + args: { onClick: fn(), + suggestions: ['acm', 'ai', 'hack', 'design', 'alex zheng'], }, +}; + +// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args +export const Primary = { + args: { + theme: 'light', // Define theme prop + type: 'primary', + size: 'desktop', + }, + +}; + +export const Dark = { + args: { + theme: 'dark', + type: 'primary', + size: 'desktop' + } +} + +export const Danger = { + args: { + theme: 'light', + type: 'danger', + size: 'desktop' + } +} From 716dff22ce03fd9213d1eaded2182ca8874c2559 Mon Sep 17 00:00:00 2001 From: Alex Zhang Date: Mon, 28 Apr 2025 18:27:49 -0700 Subject: [PATCH 2/8] hi --- .github/workflows/gh-pages-preview.yml | 30 ++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/gh-pages-preview.yml diff --git a/.github/workflows/gh-pages-preview.yml b/.github/workflows/gh-pages-preview.yml new file mode 100644 index 0000000..2355d54 --- /dev/null +++ b/.github/workflows/gh-pages-preview.yml @@ -0,0 +1,30 @@ +# .github/workflows/preview.yml +name: Deploy PR previews + +on: + pull_request: + types: + - opened + - reopened + - synchronize + - closed + +concurrency: preview-${{ github.ref }} + +jobs: + deploy-preview: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install and Build + if: github.event.action != 'closed' # You might want to skip the build if the PR has been closed + run: | + npm install + npm run build-storybook + + - name: Deploy preview + uses: rossjrw/pr-preview-action@v1 + with: + source-dir: ./build/ \ No newline at end of file From 7e0d91aee6452d5b0a0c979a728c689f3519ed95 Mon Sep 17 00:00:00 2001 From: Alex Zhang Date: Mon, 28 Apr 2025 18:31:19 -0700 Subject: [PATCH 3/8] update directory --- .github/workflows/gh-pages-preview.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh-pages-preview.yml b/.github/workflows/gh-pages-preview.yml index 2355d54..ac0ce8f 100644 --- a/.github/workflows/gh-pages-preview.yml +++ b/.github/workflows/gh-pages-preview.yml @@ -27,4 +27,4 @@ jobs: - name: Deploy preview uses: rossjrw/pr-preview-action@v1 with: - source-dir: ./build/ \ No newline at end of file + source-dir: ./storybook-static// \ No newline at end of file From 863ee94739d0e43db1041545ed9c7f721d433015 Mon Sep 17 00:00:00 2001 From: Alex Zhang Date: Mon, 28 Apr 2025 18:32:46 -0700 Subject: [PATCH 4/8] hi' --- .github/workflows/gh-pages-preview.yml | 2 +- .gitignore | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gh-pages-preview.yml b/.github/workflows/gh-pages-preview.yml index ac0ce8f..d81948f 100644 --- a/.github/workflows/gh-pages-preview.yml +++ b/.github/workflows/gh-pages-preview.yml @@ -27,4 +27,4 @@ jobs: - name: Deploy preview uses: rossjrw/pr-preview-action@v1 with: - source-dir: ./storybook-static// \ No newline at end of file + source-dir: ./storybook-static/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 398fa0f..fb302d0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules .cache .next storybook-static -dist \ No newline at end of file +dist +package-lock.json \ No newline at end of file From 8cbe7cade785769ac95abdbc945cc231f7d3d089 Mon Sep 17 00:00:00 2001 From: Alex Zhang Date: Mon, 28 Apr 2025 18:39:35 -0700 Subject: [PATCH 5/8] hi --- .github/workflows/gh-pages-preview.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gh-pages-preview.yml b/.github/workflows/gh-pages-preview.yml index d81948f..6445829 100644 --- a/.github/workflows/gh-pages-preview.yml +++ b/.github/workflows/gh-pages-preview.yml @@ -27,4 +27,5 @@ jobs: - name: Deploy preview uses: rossjrw/pr-preview-action@v1 with: - source-dir: ./storybook-static/ \ No newline at end of file + source-dir: ./storybook-static/ + preview-branch: github-pages \ No newline at end of file From 9dd55f94a779e6b7eb84057a6704046f9c4d9c69 Mon Sep 17 00:00:00 2001 From: Alex Zhang Date: Mon, 28 Apr 2025 18:51:43 -0700 Subject: [PATCH 6/8] messing around with stuff --- .github/workflows/gh-pages-preview.yml | 2 +- .github/workflows/gh-pages.yml | 22 ++++++---------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/.github/workflows/gh-pages-preview.yml b/.github/workflows/gh-pages-preview.yml index 6445829..2f51ef5 100644 --- a/.github/workflows/gh-pages-preview.yml +++ b/.github/workflows/gh-pages-preview.yml @@ -28,4 +28,4 @@ jobs: uses: rossjrw/pr-preview-action@v1 with: source-dir: ./storybook-static/ - preview-branch: github-pages \ No newline at end of file + preview-branch: gh-pages \ No newline at end of file diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 7808cb7..97e304c 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -23,7 +23,7 @@ concurrency: jobs: # Build job - build: + deploy: runs-on: ubuntu-latest steps: - name: Checkout @@ -34,19 +34,9 @@ jobs: run: | npm install npm run build-storybook - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + - uses: JamesIves/github-pages-deploy-action@v4 with: - path: storybook-static/ - - # Deployment job - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 + folder: ./storybook-static/ + branch: gh-pages + clean-exclude: pr-preview + force: false From 6ce02fd7fdcf11d9f8d112d15958a1b89fed8c04 Mon Sep 17 00:00:00 2001 From: Alex Zhang Date: Mon, 28 Apr 2025 18:54:58 -0700 Subject: [PATCH 7/8] run it back --- .github/workflows/gh-pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 97e304c..1410534 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -11,7 +11,7 @@ on: # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: - contents: read + contents: write pages: write id-token: write From 4c5e8cb246e09968fde0c59dd23048b9197550b5 Mon Sep 17 00:00:00 2001 From: Chaitya Jodhavat Date: Mon, 5 May 2025 15:43:58 -0700 Subject: [PATCH 8/8] feedback on inputs --- src/components/dropdowns/dropdown.css | 81 ++++++++++++++++++++++ src/components/dropdowns/dropdown.tsx | 49 +++++++++++++ src/components/inputs-dropdowns/Input.tsx | 32 +++++---- src/components/inputs-dropdowns/inputs.css | 28 +++++--- src/stories/Dropdown.stories.ts | 53 ++++++++++++++ src/stories/Inputs.stories.ts | 14 ++-- 6 files changed, 227 insertions(+), 30 deletions(-) create mode 100644 src/components/dropdowns/dropdown.css create mode 100644 src/components/dropdowns/dropdown.tsx create mode 100644 src/stories/Dropdown.stories.ts diff --git a/src/components/dropdowns/dropdown.css b/src/components/dropdowns/dropdown.css new file mode 100644 index 0000000..53fc802 --- /dev/null +++ b/src/components/dropdowns/dropdown.css @@ -0,0 +1,81 @@ +.dropdown-wrapper { + font-family: sans-serif; + width: 291px; + user-select: none; + position: relative; + overflow: visible; + /* padding: 10px 20px 10px 20px; */ + + font-family: "DM Sans", sans-serif; + } + + /* Button part (closed or open) */ + .dropdown-header { + width: 100%; + padding: 10px 20px; + border-bottom: none; + font-size: 16px; + height: 67px; + border: 1px solid black; + border-radius: 16px; + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; + background-color: white; + } + + .dropdown-wrapper.open .dropdown-header { + border-radius: 16px 16px 0 0; + /* border-bottom-left-radius: 0; + border-bottom-right-radius: 0; */ + } + + /* Arrow */ + .arrow { + width: 24px; + height: 24px; + flex-shrink: 0px; + } + + /* Dropdown menu */ + .dropdown-menu { + position: absolute; + top: 100%; + left: 0; + padding: 0px 20px 10px 20px; + width: 100%; + margin: 0; + display: flex; + list-style: none; + flex-direction: column; + gap: 10px; + background-color: white; + border: 1px solid black; + /* border-top: none; */ + border-radius: 0 0 12px 12px; + + z-index: 10; + } + + /* Divider between header and options + .dropdown-menu::before { + content: ''; + display: block; + height: 1px; + background-color: black; + margin-bottom: 8px; + } */ + + /* Each option */ + .dropdown-item { + line-height: 24px; + font-size: 16px; + border-radius: 8px; + padding: 4px 8px; + cursor: pointer; + } + + .dropdown-item:hover { + background-color: #f5f5f5; + } \ No newline at end of file diff --git a/src/components/dropdowns/dropdown.tsx b/src/components/dropdowns/dropdown.tsx new file mode 100644 index 0000000..ec62853 --- /dev/null +++ b/src/components/dropdowns/dropdown.tsx @@ -0,0 +1,49 @@ +import React, {useState} from 'react'; +import './dropdown.css'; + +export interface DropdownProps { + theme?: 'light' | 'dark'; + size?: 'desktop' | 'mobile'; + suggestions?: string[]; + onChange?: (value: string) => void; +} + +export const Dropdown: React.FC = ({ + theme = 'light', + size = "desktop", + suggestions = [], + onChange + +}) => { + const [isOpen, setIsOpen] = useState(false); + const [selected, setIsSelected] = useState(null); + // const options = {[...(selected ? [selected] : []), ...suggestions.filter(s => s !== selected)]}; + // const filtered = selected ? suggestions.filter(s => s !== selected) : suggestions; + const options = [...suggestions].sort((a, b) => a.localeCompare(b)); + const toggleDropdown = () => setIsOpen(!isOpen); + const handleSelect = (option: string) => { + setIsSelected(option); + setIsOpen(false); + onChange?.(option); + } + + return ( +
+
+ {selected ?? 'Select an option'} + + + +
+ {isOpen && ( +
    + {options.map((option, i) => ( +
  • handleSelect(option)}> + {option} +
  • + ))} +
+ )} +
+ ); +} \ No newline at end of file diff --git a/src/components/inputs-dropdowns/Input.tsx b/src/components/inputs-dropdowns/Input.tsx index a569a8d..a5e4ab8 100644 --- a/src/components/inputs-dropdowns/Input.tsx +++ b/src/components/inputs-dropdowns/Input.tsx @@ -1,21 +1,22 @@ import React, { useState, useRef } from 'react'; import './inputs.css' -export interface InputProps { +export interface InputProps extends React.InputHTMLAttributes { theme?: 'light' | 'dark'; // Define theme prop - type?: 'primary' | 'danger'; - size?: 'desktop' | 'mobile'; + variant?: 'primary' | 'error'; + inputSize?: 'desktop' | 'mobile'; suggestions?: string[]; - onChange?: (value: string) => void; + onValueChange?: (value: string) => void; } export const Input: React.FC = ({ theme = "light", - type = 'primary', - size = 'desktop', + variant = 'primary', + inputSize = 'desktop', suggestions = [], - onChange + onValueChange, + ...rest }) => { const [value, setValue]= useState(''); const inputRef = useRef(null); @@ -26,30 +27,37 @@ export const Input: React.FC = ({ const handleChange = (e: React.ChangeEvent) => { const newVal = e.target.value; setValue(newVal); - onChange?.(newVal); + onValueChange?.(newVal); }; const handleKeyDown = (e: React.KeyboardEvent) => { if ((e.key === 'ArrowRight' || e.key === 'Tab') && bestSuggestion) { e.preventDefault(); setValue(bestSuggestion); - onChange?.(bestSuggestion); + onValueChange?.(bestSuggestion); } } return ( -
+
{value && bestSuggestion && (
{value} - {bestSuggestion.slice(value.length)} + { + setValue(bestSuggestion); + onValueChange?.(bestSuggestion); + inputRef.current?.focus(); // optional: put focus back in input + }} + >{bestSuggestion.slice(value.length)}
)}
diff --git a/src/components/inputs-dropdowns/inputs.css b/src/components/inputs-dropdowns/inputs.css index 0457698..1228bda 100644 --- a/src/components/inputs-dropdowns/inputs.css +++ b/src/components/inputs-dropdowns/inputs.css @@ -7,21 +7,23 @@ } .inline-suggest-wrapper.desktop { - font-size: 18px; /* match .input.desktop */ - line-height: 24px; /* match .input.desktop */ + font-size: 1.125rem; /* match .input.desktop */ + line-height: 1.5rem; /* match .input.desktop */ } .inline-suggest-wrapper.mobile { - font-size: 16px; - line-height: 21px; + font-size: 1rem; + line-height: 1.3125rem; } .inline-suggestion { top: 50%; position: absolute; transform: translateY(-50%); - + pointer-events: none; left: 0; - padding: 10px 20px 10px 20px; + box-sizing: border-box; + /* padding: 10px 20px 10px 20px; */ + padding: 0 20px; height: 100%; display: flex; align-items: center; @@ -31,6 +33,10 @@ line-height: inherit; white-space: pre; } +.inline-suggestion .hint { + pointer-events: auto; + cursor: pointer; + } .inline-suggestion span:first-child { color: transparent; user-select: none; @@ -70,19 +76,19 @@ border: 1px solid var(--Buttons-CTA-Button-Secondary-secondary-text, #000000); } -.input.danger { +.input.error { border: 1px solid #E36370; color: #E36370; } .input.desktop { - font-size: 18px; - line-height: 24px; + font-size: 1.125rem; + line-height: 1.5e; height: 67px; } .input.mobile { - font-size: 16px; - line-height: 21px; + font-size: 1rem; + line-height: 1.3125rem; height: 58px; } \ No newline at end of file diff --git a/src/stories/Dropdown.stories.ts b/src/stories/Dropdown.stories.ts new file mode 100644 index 0000000..6316db8 --- /dev/null +++ b/src/stories/Dropdown.stories.ts @@ -0,0 +1,53 @@ +import { fn } from '@storybook/test'; + +import { Dropdown } from '../components/dropdowns/dropdown'; + +// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export +export default { + title: 'Example/Dropdown', + component: Dropdown, + parameters: { + // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout + layout: 'centered', + }, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs + tags: ['autodocs'], + // More on argTypes: https://storybook.js.org/docs/api/argtypes + argTypes: { + backgroundColor: { control: 'color' }, + suggestions: { + control: { type: 'object' }, + description: 'Autocomplete suggestions', + defaultValue: ['abacus', 'boy', 'car'], + } + }, + // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args + args: { onClick: fn(), + suggestions: ['a', 'b', 'c', 'd', 'e'], }, +}; + +// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args +export const Primary = { + args: { + theme: 'light', // Define theme prop + type: 'primary', + size: 'desktop', + }, + +}; + +export const Dark = { + args: { + theme: 'dark', + type: 'primary', + size: 'desktop' + } +} + +export const Danger = { + args: { + theme: 'light', + type: 'danger', + size: 'desktop' + } +} diff --git a/src/stories/Inputs.stories.ts b/src/stories/Inputs.stories.ts index 93f5f04..5801264 100644 --- a/src/stories/Inputs.stories.ts +++ b/src/stories/Inputs.stories.ts @@ -22,7 +22,7 @@ export default { } }, // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args - args: { onClick: fn(), + args: { onValueChange: fn(), suggestions: ['acm', 'ai', 'hack', 'design', 'alex zheng'], }, }; @@ -30,8 +30,8 @@ export default { export const Primary = { args: { theme: 'light', // Define theme prop - type: 'primary', - size: 'desktop', + variant: 'primary', + inputSize: 'desktop', }, }; @@ -39,15 +39,15 @@ export const Primary = { export const Dark = { args: { theme: 'dark', - type: 'primary', - size: 'desktop' + variant: 'primary', + inputSize: 'desktop' } } export const Danger = { args: { theme: 'light', - type: 'danger', - size: 'desktop' + variant: 'error', + inputSize: 'desktop' } }