Skip to content

Commit cc9fce6

Browse files
authored
Merge pull request #711 from acelaya-forks/feature/tailwind-dropdowns
Migrate dropdowns to tailwind-based `Dropdown`
2 parents 8a904d0 + bb2e348 commit cc9fce6

44 files changed

Lines changed: 550 additions & 534 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

dev/App.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ShlinkApiClient } from '@shlinkio/shlink-js-sdk';
22
import { FetchHttpClient } from '@shlinkio/shlink-js-sdk/fetch';
3+
import { clsx } from 'clsx';
34
import type { FC } from 'react';
45
import { useCallback , useEffect, useMemo, useState } from 'react';
56
import { Link, Navigate, Route, Routes, useLocation } from 'react-router';
@@ -39,21 +40,24 @@ export const App: FC = () => {
3940

4041
return (
4142
<>
42-
<header className="header fixed-top text-white d-flex justify-content-between">
43+
<header className={clsx(
44+
'tw:h-(--header-height) tw:fixed tw:top-0 tw:left-0 tw:right-0 tw:flex tw:justify-between',
45+
'tw:bg-lm-main tw:dark:bg-dm-main tw:text-white',
46+
)}>
4347
<ServerInfoForm serverInfo={serverInfo} onChange={onServerInfoChange} />
4448
<div className="tw:h-full tw:pr-4 tw:flex tw:items-center tw:gap-4">
45-
<Link to="/" className="text-white">Home</Link>
46-
<Link to="/settings" className="text-white">Settings</Link>
49+
<Link to="/" className="tw:text-white">Home</Link>
50+
<Link to="/settings" className="tw:text-white">Settings</Link>
4751
<ThemeToggle />
4852
</div>
4953
</header>
50-
<div className="wrapper">
54+
<div className="tw:py-(--header-height)">
5155
<Routes>
5256
<Route path="/settings">
5357
<Route
5458
path="*"
5559
element={(
56-
<div className="container pt-4">
60+
<div className="tw:container tw:mx-auto tw:pt-6">
5761
<ShlinkWebSettings
5862
settings={settings}
5963
onUpdateSettings={setSettings}
@@ -73,9 +77,9 @@ export const App: FC = () => {
7377
settings={settings}
7478
routesPrefix={routesPrefix}
7579
/>
76-
) : <div className="container pt-4">Not connected</div>}
80+
) : <div className="tw:container tw:mx-auto tw:pt-6">Not connected</div>}
7781
/>
78-
<Route path="*" element={<h3 className="mt-3 text-center">Not found</h3>} />
82+
<Route path="*" element={<h3 className="tw:mt-4 tw:text-center">Not found</h3>} />
7983
</Routes>
8084
</div>
8185
</>

dev/index.scss

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,3 @@
44
);
55
@use '../node_modules/@shlinkio/shlink-frontend-kit/dist/index';
66
@use '../src/index.scss' as dev-index;
7-
8-
.header {
9-
height: base.$headerHeight;
10-
background-color: var(--brand-color);
11-
}
12-
13-
.wrapper {
14-
padding: base.$headerHeight 0;
15-
}

dev/server-info/ServerInfoForm.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const ServerInfoForm: FC<ServerInfoFormProps> = ({ serverInfo, onChange }
2424
}, [serverInfo]);
2525

2626
return (
27-
<form className="py-2 ps-2 d-flex gap-2" onSubmit={handleSubmit}>
27+
<form className="tw:py-2 tw:pl-2 tw:flex tw:gap-2" onSubmit={handleSubmit}>
2828
<Input
2929
name="baseUrl"
3030
placeholder="Server URL"

package-lock.json

Lines changed: 5 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,12 @@
4646
"@fortawesome/free-solid-svg-icons": "^6.7.2",
4747
"@fortawesome/react-fontawesome": "^0.2.2",
4848
"@reduxjs/toolkit": "^2.5.0",
49-
"@shlinkio/shlink-frontend-kit": "^0.9.8",
49+
"@shlinkio/shlink-frontend-kit": "^0.9.9",
5050
"@shlinkio/shlink-js-sdk": "^2.0.0",
5151
"react": "^18.3 || ^19.0",
5252
"react-dom": "^18.3 || ^19.0",
5353
"react-redux": "^9.2.0",
54-
"react-router": "^7.1.5",
55-
"reactstrap": "^9.2.0"
54+
"react-router": "^7.1.5"
5655
},
5756
"peerDependenciesMeta": {
5857
"@shlinkio/shlink-js-sdk": {

src/domains/DomainSelector.tsx

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { faUndo } from '@fortawesome/free-solid-svg-icons';
22
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3-
import { DropdownBtn, useToggle } from '@shlinkio/shlink-frontend-kit';
4-
import { Button, Input } from '@shlinkio/shlink-frontend-kit/tailwind';
3+
import { useToggle } from '@shlinkio/shlink-frontend-kit';
4+
import { Button, Dropdown, Input } from '@shlinkio/shlink-frontend-kit/tailwind';
55
import { clsx } from 'clsx';
66
import { useCallback } from 'react';
7-
import { DropdownItem } from 'reactstrap';
87
import { Muted } from '../utils/components/Muted';
98
import type { Domain } from './data';
109

@@ -46,25 +45,25 @@ export const DomainSelector = ({ domains, value, onChange }: DomainSelectorProps
4645
</Button>
4746
</div>
4847
) : (
49-
<DropdownBtn
50-
text={valueIsEmpty ? 'Domain' : `Domain: ${value}`}
51-
className={clsx({ 'tw:text-placeholder': valueIsEmpty })}
48+
<Dropdown
49+
buttonContent={valueIsEmpty ? 'Domain' : `Domain: ${value}`}
50+
buttonClassName={clsx('tw:w-full', { 'tw:text-placeholder': valueIsEmpty })}
5251
>
5352
{domains.map(({ domain, isDefault }) => (
54-
<DropdownItem
53+
<Dropdown.Item
5554
key={domain}
56-
active={(value === domain || isDefault) && valueIsEmpty}
55+
selected={(value === domain || isDefault) && valueIsEmpty}
5756
onClick={() => onChange(domain)}
5857
className="tw:flex tw:justify-between tw:items-center"
5958
>
6059
{domain}
6160
{isDefault && <Muted>default</Muted>}
62-
</DropdownItem>
61+
</Dropdown.Item>
6362
))}
64-
<DropdownItem divider />
65-
<DropdownItem onClick={unselectDomainAndShowInput}>
63+
<Dropdown.Separator />
64+
<Dropdown.Item onClick={unselectDomainAndShowInput}>
6665
<i>New domain</i>
67-
</DropdownItem>
68-
</DropdownBtn>
66+
</Dropdown.Item>
67+
</Dropdown>
6968
);
7069
};

src/domains/helpers/DomainDropdown.tsx

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@ import {
55
faList as listIcon,
66
} from '@fortawesome/free-solid-svg-icons';
77
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
8-
import { RowDropdownBtn, useToggle } from '@shlinkio/shlink-frontend-kit';
8+
import { useToggle } from '@shlinkio/shlink-frontend-kit';
9+
import { RowDropdown } from '@shlinkio/shlink-frontend-kit/tailwind';
910
import type { FC } from 'react';
10-
import { Link } from 'react-router';
11-
import { DropdownItem } from 'reactstrap';
1211
import { useFeature } from '../../utils/features';
1312
import { useRoutesPrefix } from '../../utils/routesPrefix';
1413
import { useVisitsComparisonContext } from '../../visits/visits-comparison/VisitsComparisonContext';
@@ -30,37 +29,38 @@ export const DomainDropdown: FC<DomainDropdownProps> = ({ domain, editDomainRedi
3029

3130
return (
3231
<>
33-
<RowDropdownBtn>
34-
<DropdownItem
35-
tag={Link}
32+
<RowDropdown menuAlignment="right">
33+
<RowDropdown.Item
34+
className="tw:gap-1.5"
3635
to={`${routesPrefix}/domain/${domain.domain}${domain.isDefault ? `_${DEFAULT_DOMAIN}` : ''}/visits`}
3736
>
3837
<FontAwesomeIcon icon={pieChartIcon} fixedWidth /> Visit stats
39-
</DropdownItem>
40-
<DropdownItem
38+
</RowDropdown.Item>
39+
<RowDropdown.Item
40+
className="tw:gap-1.5"
4141
disabled={!visitsComparison || !visitsComparison.canAddItemWithName(domain.domain)}
4242
onClick={() => visitsComparison?.addItemToCompare({
4343
name: domain.domain,
4444
query: domain.domain,
4545
})}
4646
>
4747
<FontAwesomeIcon icon={lineChartIcon} fixedWidth /> Compare visits
48-
</DropdownItem>
48+
</RowDropdown.Item>
4949

5050
{canFilterShortUrlsByDomain && (
51-
<DropdownItem
52-
tag={Link}
51+
<RowDropdown.Item
52+
className="tw:gap-1.5"
5353
to={`${routesPrefix}/list-short-urls/1?domain=${domain.isDefault ? DEFAULT_DOMAIN : domain.domain}`}
5454
>
5555
<FontAwesomeIcon icon={listIcon} fixedWidth /> Short URLs
56-
</DropdownItem>
56+
</RowDropdown.Item>
5757
)}
5858

59-
<DropdownItem divider tag="hr" />
60-
<DropdownItem onClick={openModal}>
59+
<RowDropdown.Separator />
60+
<RowDropdown.Item onClick={openModal} className="tw:gap-1.5">
6161
<FontAwesomeIcon icon={editIcon} fixedWidth /> Edit redirects
62-
</DropdownItem>
63-
</RowDropdownBtn>
62+
</RowDropdown.Item>
63+
</RowDropdown>
6464

6565
<EditDomainRedirectsModal
6666
domain={domain}

src/settings/components/DateIntervalSelector.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { DropdownBtn } from '@shlinkio/shlink-frontend-kit';
1+
import { Dropdown } from '@shlinkio/shlink-frontend-kit/tailwind';
22
import type { FC } from 'react';
3-
import { DropdownItem } from 'reactstrap';
43
import type { VisitsSettings } from '..';
54

65
export type DateInterval = VisitsSettings['defaultInterval'];
@@ -30,17 +29,17 @@ const intervalToString = (interval: DateInterval | undefined, fallback: string):
3029
};
3130

3231
export const DateIntervalSelector: FC<DateIntervalSelectorProps> = ({ onChange, active, allText }) => (
33-
<DropdownBtn text={intervalToString(active, allText)}>
34-
<DropdownItem active={active === 'all'} onClick={() => onChange('all')}>
32+
<Dropdown buttonContent={intervalToString(active, allText)} buttonClassName="tw:w-full">
33+
<Dropdown.Item selected={active === 'all'} onClick={() => onChange('all')}>
3534
{allText}
36-
</DropdownItem>
37-
<DropdownItem divider />
35+
</Dropdown.Item>
36+
<Dropdown.Separator />
3837
{Object.entries(INTERVAL_TO_STRING_MAP).map(
3938
([interval, name]) => (
40-
<DropdownItem key={interval} active={active === interval} onClick={() => onChange(interval as DateInterval)}>
39+
<Dropdown.Item key={interval} selected={active === interval} onClick={() => onChange(interval as DateInterval)}>
4140
{name}
42-
</DropdownItem>
41+
</Dropdown.Item>
4342
),
4443
)}
45-
</DropdownBtn>
44+
</Dropdown>
4645
);

src/settings/components/ShortUrlCreationSettings.tsx

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import { DropdownBtn } from '@shlinkio/shlink-frontend-kit';
2-
import { Label, SimpleCard } from '@shlinkio/shlink-frontend-kit/tailwind';
1+
import { Dropdown, Label, SimpleCard } from '@shlinkio/shlink-frontend-kit/tailwind';
32
import type { FC, ReactNode } from 'react';
4-
import { DropdownItem } from 'reactstrap';
53
import { Muted } from '../../utils/components/Muted';
64
import type { ShortUrlCreationSettings as ShortUrlsSettings } from '..';
75
import { useSetting } from '..';
@@ -56,23 +54,26 @@ export const ShortUrlCreationSettings: FC<ShortUrlCreationProps> = ({ onChange }
5654
>
5755
Make all new short URLs forward their query params to the long URL.
5856
</LabelledToggle>
59-
<div>
57+
<div className="tw:flex tw:flex-col">
6058
<Label className="tw:mb-1.5">Tag suggestions search mode:</Label>
61-
<DropdownBtn text={tagFilteringModeText(shortUrlCreation.tagFilteringMode)}>
62-
<DropdownItem
63-
active={!shortUrlCreation.tagFilteringMode || shortUrlCreation.tagFilteringMode === 'startsWith'}
59+
<Dropdown
60+
buttonContent={tagFilteringModeText(shortUrlCreation.tagFilteringMode)}
61+
buttonClassName="tw:w-full"
62+
>
63+
<Dropdown.Item
64+
selected={!shortUrlCreation.tagFilteringMode || shortUrlCreation.tagFilteringMode === 'startsWith'}
6465
onClick={changeTagsFilteringMode('startsWith')}
6566
>
6667
{tagFilteringModeText('startsWith')}
67-
</DropdownItem>
68-
<DropdownItem
69-
active={shortUrlCreation.tagFilteringMode === 'includes'}
68+
</Dropdown.Item>
69+
<Dropdown.Item
70+
selected={shortUrlCreation.tagFilteringMode === 'includes'}
7071
onClick={changeTagsFilteringMode('includes')}
7172
>
7273
{tagFilteringModeText('includes')}
73-
</DropdownItem>
74-
</DropdownBtn>
75-
<Muted size="sm">{tagFilteringModeHint(shortUrlCreation.tagFilteringMode)}</Muted>
74+
</Dropdown.Item>
75+
</Dropdown>
76+
<Muted size="sm" className="tw:mt-0.5">{tagFilteringModeHint(shortUrlCreation.tagFilteringMode)}</Muted>
7677
</div>
7778
</SimpleCard>
7879
);

src/settings/components/ShortUrlsListSettings.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { OrderingDropdown } from '@shlinkio/shlink-frontend-kit';
2-
import { Label, SimpleCard } from '@shlinkio/shlink-frontend-kit/tailwind';
1+
import { Label, OrderingDropdown, SimpleCard } from '@shlinkio/shlink-frontend-kit/tailwind';
32
import type { FC } from 'react';
43
import type { ShortUrlsListSettings as ShortUrlsSettings } from '..';
54
import { useSetting } from '..';
@@ -31,12 +30,15 @@ export const ShortUrlsListSettings: FC<ShortUrlsListSettingsProps> = ({ onChange
3130
>
3231
Request confirmation before deleting a short URL.
3332
</LabelledToggle>
34-
<div>
35-
<Label className="tw:mb-1.5">Default ordering for short URLs list:</Label>
33+
<div className="tw:flex tw:flex-col tw:gap-1.5">
34+
<Label>Default ordering for short URLs list:</Label>
3635
<OrderingDropdown
36+
buttonClassName="tw:w-full"
3737
items={SHORT_URLS_ORDERABLE_FIELDS}
3838
order={shortUrlsList?.defaultOrdering ?? defaultOrdering}
39-
onChange={(field, dir) => onChange({ defaultOrdering: { field, dir } })}
39+
onChange={(newOrder) => onChange(
40+
{ defaultOrdering: !newOrder.dir && !newOrder.field ? undefined : newOrder },
41+
)}
4042
/>
4143
</div>
4244
</SimpleCard>

0 commit comments

Comments
 (0)