-
Notifications
You must be signed in to change notification settings - Fork 13
[solidjs-tailwinf] Add user profile card #830
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| function TwitterIcon(className) { | ||
| return ( | ||
| <svg | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| viewBox="0 0 24 24" | ||
| class={className.class || 'w-6 h-6'} | ||
| fill="currentColor" | ||
| > | ||
| <path fill="none" d="M0 0h24v24H0z" /> | ||
| <path d="M22.162 5.656a8.384 8.384 0 0 1-2.402.658A4.196 4.196 0 0 0 21.6 4c-.82.488-1.719.83-2.656 1.015a4.182 4.182 0 0 0-7.126 3.814 11.874 11.874 0 0 1-8.62-4.37 4.168 4.168 0 0 0-.566 2.103c0 1.45.738 2.731 1.86 3.481a4.168 4.168 0 0 1-1.894-.523v.052a4.185 4.185 0 0 0 3.355 4.101 4.21 4.21 0 0 1-1.89.072A4.185 4.185 0 0 0 7.97 16.65a8.394 8.394 0 0 1-6.191 1.732 11.83 11.83 0 0 0 6.41 1.88c7.693 0 11.9-6.373 11.9-11.9 0-.18-.005-.362-.013-.54a8.496 8.496 0 0 0 2.087-2.165z" /> | ||
| </svg> | ||
| ); | ||
| } | ||
|
|
||
| export default TwitterIcon; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { default as TwitterIcon } from './TwitterIcon'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import { For } from 'solid-js'; | ||
| function OrgList(props) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use splitProps as it shows a major feature in SolidJs
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @hdJerry I don't understand this comment. Can you explain why
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @hdJerry It doesn't make sense to use it when all the properties passed are going to be used in that component. If we need to show how major a feature it is, we can have a store that returns both user and repositories data in one object, then split it into two and pass it into their respective components(or into components where the data is needed). Or do the split inside the component and retrieve just the properties needed by the component.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the props are not reactive then no need. @vyktoremario , though it also help to know the props expected in there just like how TS does. SO if I view the component with the spiltProps or mergeProps i know the props allowed in that component. |
||
| return ( | ||
| <div class="mt-5 border-t border-gray-200"> | ||
| <h2 class="my-2 pt-2 text-gray-800 font-bold">Organizations</h2> | ||
| <div data-testid="profile page orgs" class="flex flex-wrap space-x-2"> | ||
| <For each={props.organizations}> | ||
| {(props) => ( | ||
| <div class="relative w-9 h-9 rounded border border-gray-300 overflow-hidden"> | ||
| <img src={props.avatarUrl} alt="Organization" /> | ||
| </div> | ||
| )} | ||
| </For> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export default OrgList; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| import { | ||
| users, | ||
| star, | ||
| buildingOffice, | ||
| mapPin, | ||
| link, | ||
| } from 'solid-heroicons/outline'; | ||
| import { Icon } from 'solid-heroicons'; | ||
| import { TwitterIcon } from '../Icons'; | ||
| import OrgList from './OrgList'; | ||
|
|
||
| const UserProfile = (userProfileProps) => { | ||
vyktoremario marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return ( | ||
| <div> | ||
| <img | ||
vyktoremario marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| src={userProfileProps.avatarUrl} | ||
| alt="Avatar" | ||
| width={260} | ||
| height={260} | ||
| class="rounded-full shadow z-30" | ||
| /> | ||
| <h1 class="mt-2"> | ||
| <div class="text-2xl text-gray-800 font-bold leading-tight"> | ||
| {userProfileProps.name} | ||
| </div> | ||
| <div class="text-xl text-gray-500 font-light"> | ||
| {userProfileProps.username} | ||
| </div> | ||
| </h1> | ||
| <div class="text-gray-800 mt-4">{userProfileProps.bio}</div> | ||
| <div class="text-sm text-gray-600 my-4"> | ||
| <Icon path={users} class="w-4 h-4 mb-0.5 mr-1 inline" /> | ||
| <span class="inline-block"> | ||
| <span class="font-medium text-gray-900"> | ||
| {userProfileProps.followers} | ||
| </span>{' '} | ||
| followers | ||
| </span> | ||
| <span class="mx-1">·</span> | ||
| <span class="inline-block"> | ||
| <span class="font-medium text-gray-900"> | ||
| {userProfileProps.following} | ||
| </span>{' '} | ||
| following | ||
| </span> | ||
| <span class="mx-1">·</span> | ||
| <Icon path={star} class="w-4 h-4 mb-0.5 mr-1 inline" /> | ||
| <span class="inline-block"> | ||
| <span class="font-medium text-gray-900"> | ||
| {userProfileProps.starredRepos} | ||
| </span>{' '} | ||
| </span> | ||
| </div> | ||
| <div class="text-sm text-gray-800 space-y-1"> | ||
| {userProfileProps.company && ( | ||
| <div> | ||
| <Icon path={buildingOffice} class="w-4 h-4 mb-0.5 mr-1 inline" /> | ||
| {userProfileProps.company} | ||
| </div> | ||
| )} | ||
| {userProfileProps.location && ( | ||
| <div> | ||
| <Icon path={mapPin} class="w-4 h-4 mb-0.5 mr-1 inline" /> | ||
| {userProfileProps.location} | ||
| </div> | ||
| )} | ||
| {userProfileProps.websiteUrl && ( | ||
| <div> | ||
| <Icon path={link} class="w-4 h-4 mb-0.5 mr-1 inline" /> | ||
| <a | ||
| class="hover:text-blue-600 hover:underline" | ||
| href={userProfileProps.websiteUrl} | ||
| target="_blank" | ||
| rel="noreferrer" | ||
| > | ||
| {userProfileProps.websiteUrl} | ||
| </a> | ||
| </div> | ||
| )} | ||
| {userProfileProps.twitterUsername && ( | ||
| <div> | ||
| <TwitterIcon class="w-4 h-4 mb-0.5 mr-1 inline" /> | ||
| <a | ||
| class="hover:text-blue-600 hover:underline" | ||
| href={`https:/twitter.com/${userProfileProps.twitterUsername}`} | ||
| target="_blank" | ||
| rel="noreferrer" | ||
| > | ||
| @{userProfileProps.twitterUsername} | ||
| </a> | ||
| </div> | ||
| )} | ||
| </div> | ||
| {userProfileProps.organizations.length > 0 && ( | ||
| <OrgList organizations={userProfileProps.organizations} /> | ||
| )} | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default UserProfile; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import { Router } from '@solidjs/router'; | ||
| import { render } from 'solid-testing-library'; | ||
| import { beforeEach, describe, expect, it } from 'vitest'; | ||
| import UserProfile from './UserProfile.jsx'; | ||
| import { userProfileProps } from './data'; | ||
|
|
||
| describe('User profile card', () => { | ||
| let wrapper; | ||
| beforeEach(async () => { | ||
| wrapper = await render(() => ( | ||
| <Router> | ||
| <UserProfile {...userProfileProps} /> | ||
| </Router> | ||
| )); | ||
| }); | ||
|
|
||
| it('should mount', () => { | ||
| expect(wrapper).toBeTruthy(); | ||
| }); | ||
|
|
||
| it('should show the user display name', async () => { | ||
| const fullName = await wrapper.getByText(userProfileProps.name); | ||
| expect(fullName).toBeVisible(); | ||
| }); | ||
|
|
||
| it('should have a link for user profile picture', async () => { | ||
| const avatar = await wrapper.getByAltText('Avatar'); | ||
vyktoremario marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| expect(avatar).toBeVisible(); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import { Router } from '@solidjs/router'; | ||
| import { UserProfile } from '.'; | ||
| import { userProfileProps } from './data.js'; | ||
|
|
||
| export default { | ||
| title: 'Components/User Profile Card', | ||
| component: UserProfile, | ||
| }; | ||
|
|
||
| const Template = (args) => ( | ||
| <Router> | ||
| <UserProfile {...args} /> | ||
| </Router> | ||
| ); | ||
| export const Default = Template.bind({}); | ||
|
|
||
| Default.args = { | ||
| ...userProfileProps, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| export const userProfileProps = { | ||
| avatarUrl: 'https://avatars.githubusercontent.com/u/2487968?v=4', | ||
| bio: `Senior Software Engineer @thisdot`, | ||
| company: '@thisdot', | ||
| followers: 24, | ||
| following: 20, | ||
| location: 'Washington, DC', | ||
| login: 'tvanantwerp', | ||
| name: 'Tom VanAntwerp', | ||
| twitterUsername: 'tvanantwerp', | ||
| websiteUrl: 'https://tomvanantwerp.com', | ||
| organizations: [ | ||
| { | ||
| avatarUrl: 'https://avatars.githubusercontent.com/u/22839396?v=4', | ||
| login: 'thisdot', | ||
| }, | ||
| ], | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export {default as UserProfile } from './UserProfile'; |
Uh oh!
There was an error while loading. Please reload this page.