@autobusal/routes-order 1.8.0 → 1.9.3
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.
- package/CHANGELOG.md +112 -0
- package/Found/Dates/Dates.tsx +38 -2
- package/Found/Dates/styles.ts +37 -3
- package/Found/Filters/Filters.tsx +194 -0
- package/Found/Filters/styles.ts +188 -0
- package/Found/Found.tsx +116 -16
- package/Found/Group/Group.tsx +66 -0
- package/Found/Group/styles.ts +44 -0
- package/Found/Orders/Orders.tsx +67 -26
- package/Found/Orders/styles.ts +14 -0
- package/Found/Picks/Picks.tsx +68 -0
- package/Found/Picks/styles.ts +55 -0
- package/Found/Route/Route.tsx +46 -7
- package/Found/Route/styles.ts +55 -8
- package/Found/Sort/Sort.tsx +40 -0
- package/Found/Sort/styles.ts +42 -0
- package/Found/Summary/Summary.tsx +49 -0
- package/Found/Summary/styles.ts +42 -0
- package/Found/refine.ts +626 -0
- package/package.json +1 -1
- package/services.ts +7 -1
- package/styles.ts +36 -0
- package/types.ts +6 -0
package/Found/Route/styles.ts
CHANGED
|
@@ -73,17 +73,46 @@ export const Location = styled.div`
|
|
|
73
73
|
display: flex;
|
|
74
74
|
flex-direction: column;
|
|
75
75
|
align-items: center;
|
|
76
|
-
gap:
|
|
76
|
+
gap: 3px;
|
|
77
77
|
text-align: center;
|
|
78
78
|
|
|
79
79
|
& > h2 {
|
|
80
80
|
margin-bottom: 0;
|
|
81
81
|
}
|
|
82
|
+
`;
|
|
82
83
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-01
|
|
86
|
+
*
|
|
87
|
+
* The stop name is supporting detail, not a heading. At the same 16px as
|
|
88
|
+
* body copy it competed with the city above it; a step down lets the eye
|
|
89
|
+
* land on the city first and read the stop only when it needs to.
|
|
90
|
+
*/
|
|
91
|
+
export const Stop = styled.span`
|
|
92
|
+
font-size: ${ props => props.theme.size.xs };
|
|
93
|
+
font-weight: 700;
|
|
94
|
+
line-height: 1.3;
|
|
95
|
+
color: ${ props => props.theme.font.faded };
|
|
96
|
+
`;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-01
|
|
100
|
+
*
|
|
101
|
+
* The country code comes out of the database lowercase ("alb", "grc").
|
|
102
|
+
* An ISO country code is by definition upper case, so it is cased here at
|
|
103
|
+
* render time rather than left to the data - every consumer would otherwise
|
|
104
|
+
* have to remember to do it. Set smaller and muted so it qualifies the city
|
|
105
|
+
* without competing with it.
|
|
106
|
+
*/
|
|
107
|
+
export const Iso = styled.span`
|
|
108
|
+
// JSX drops the literal spaces around this element, so the gap on either
|
|
109
|
+
// side of "(ALB)" is this margin's job, not whitespace's
|
|
110
|
+
margin: 0 5px;
|
|
111
|
+
font-size: ${ props => props.theme.size.xs };
|
|
112
|
+
font-weight: 700;
|
|
113
|
+
text-transform: uppercase;
|
|
114
|
+
letter-spacing: .5px;
|
|
115
|
+
color: ${ props => props.theme.font.faded };
|
|
87
116
|
`;
|
|
88
117
|
|
|
89
118
|
export const Time = styled.div`
|
|
@@ -98,13 +127,24 @@ export const Time = styled.div`
|
|
|
98
127
|
}
|
|
99
128
|
|
|
100
129
|
& > span {
|
|
101
|
-
display:
|
|
102
|
-
|
|
103
|
-
|
|
130
|
+
display: flex;
|
|
131
|
+
align-items: center;
|
|
132
|
+
gap: 5px;
|
|
133
|
+
padding: 3px 12px;
|
|
134
|
+
white-space: nowrap;
|
|
135
|
+
font-size: ${ props => props.theme.size.m };
|
|
104
136
|
background: ${ props => props.theme.background.neutral };
|
|
105
137
|
border-radius: 100px;
|
|
106
138
|
}
|
|
107
139
|
|
|
140
|
+
// the hourglass is a hint, not a heading - the big arrow above already
|
|
141
|
+
// owns the visual weight in this column
|
|
142
|
+
& > span > svg {
|
|
143
|
+
flex-shrink: 0;
|
|
144
|
+
font-size: ${ props => props.theme.size.m };
|
|
145
|
+
opacity: .7;
|
|
146
|
+
}
|
|
147
|
+
|
|
108
148
|
@media (min-width: 640px) {
|
|
109
149
|
flex-basis: 120px;
|
|
110
150
|
}
|
|
@@ -126,6 +166,13 @@ export const Price = styled.div`
|
|
|
126
166
|
}
|
|
127
167
|
`;
|
|
128
168
|
|
|
169
|
+
export const PriceNotice = styled.span`
|
|
170
|
+
margin-top: -6px;
|
|
171
|
+
font-size: ${ props => props.theme.size.xs };
|
|
172
|
+
color: ${ props => props.theme.font.faded };
|
|
173
|
+
text-align: center;
|
|
174
|
+
`;
|
|
175
|
+
|
|
129
176
|
export const LinkCompany = styled(Link)`
|
|
130
177
|
display: block;
|
|
131
178
|
max-width: 165px;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { TFunction } from 'i18next';
|
|
2
|
+
import { Container, Label, Options, Option } from './styles';
|
|
3
|
+
import { SortKey, SORTS } from '../refine';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
value: SortKey
|
|
7
|
+
t: TFunction<'common'>
|
|
8
|
+
onChange: (value: SortKey) => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Sort control for the results list.
|
|
13
|
+
*
|
|
14
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-01
|
|
15
|
+
*
|
|
16
|
+
* Rendered as always-visible pills rather than a <select>: the options are
|
|
17
|
+
* few and short, and the point is that somebody who never opens a dropdown
|
|
18
|
+
* still sees that "cheapest" is one click away.
|
|
19
|
+
*/
|
|
20
|
+
const Sort = ({ value, t, onChange }: Props): JSX.Element => (
|
|
21
|
+
<Container>
|
|
22
|
+
<Label>{ t('routes_order.step2.sort.title') }</Label>
|
|
23
|
+
|
|
24
|
+
<Options>
|
|
25
|
+
{ SORTS.map(sort => (
|
|
26
|
+
<Option
|
|
27
|
+
key={ sort }
|
|
28
|
+
type="button"
|
|
29
|
+
$active={ value === sort }
|
|
30
|
+
aria-pressed={ value === sort }
|
|
31
|
+
onClick={ () => onChange(sort) }
|
|
32
|
+
>
|
|
33
|
+
{ t(`routes_order.step2.sort.${ sort }`) }
|
|
34
|
+
</Option>
|
|
35
|
+
)) }
|
|
36
|
+
</Options>
|
|
37
|
+
</Container>
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
export default Sort;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import styled, { css } from 'styled-components';
|
|
2
|
+
|
|
3
|
+
export const Container = styled.div`
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
gap: 8px;
|
|
7
|
+
flex-wrap: wrap;
|
|
8
|
+
`;
|
|
9
|
+
|
|
10
|
+
export const Label = styled.span`
|
|
11
|
+
font-size: ${ props => props.theme.size.s };
|
|
12
|
+
font-weight: 700;
|
|
13
|
+
color: ${ props => props.theme.font.faded };
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
export const Options = styled.div`
|
|
17
|
+
display: flex;
|
|
18
|
+
gap: 6px;
|
|
19
|
+
flex-wrap: wrap;
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
export const Option = styled.button<{ $active: boolean }>`
|
|
23
|
+
padding: 6px 14px;
|
|
24
|
+
font-size: ${ props => props.theme.size.s };
|
|
25
|
+
font-weight: 700;
|
|
26
|
+
white-space: nowrap;
|
|
27
|
+
border-radius: 100px;
|
|
28
|
+
border: 1px solid ${ props => props.theme.inputs.border };
|
|
29
|
+
background: ${ props => props.theme.background.normal };
|
|
30
|
+
color: ${ props => props.theme.font.normal };
|
|
31
|
+
transition: all 0.2s ease;
|
|
32
|
+
|
|
33
|
+
&:hover {
|
|
34
|
+
border-color: ${ props => props.theme.primary.normal };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
${ props => props.$active && css`
|
|
38
|
+
color: ${ props => props.theme.primary.contrast };
|
|
39
|
+
background: ${ props => props.theme.primary.normal };
|
|
40
|
+
border-color: ${ props => props.theme.primary.normal };
|
|
41
|
+
` }
|
|
42
|
+
`;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { TFunction } from 'i18next';
|
|
2
|
+
import { Container, Result, Total, Range } from './styles';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
shown: number
|
|
6
|
+
total: number
|
|
7
|
+
range: { minDisplay: string, maxDisplay: string } | null
|
|
8
|
+
t: TFunction<'common'>
|
|
9
|
+
children?: React.ReactNode
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* "8 of 10 trips - from 38.00 EUR to 70.00 EUR".
|
|
14
|
+
*
|
|
15
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-01
|
|
16
|
+
*
|
|
17
|
+
* Cheap to render and it answers the two questions somebody has before they
|
|
18
|
+
* start reading rows: is there enough choice here, and what will this cost
|
|
19
|
+
* me. The shown/total split also makes it obvious when a filter is hiding
|
|
20
|
+
* results, which is otherwise easy to forget after scrolling.
|
|
21
|
+
*/
|
|
22
|
+
const Summary = ({ shown, total, range, t, children }: Props): JSX.Element => (
|
|
23
|
+
<Container className="box">
|
|
24
|
+
<Result>
|
|
25
|
+
{ /* Edited: Ferjolt Ozuni - Date: 2026-08-01
|
|
26
|
+
`total`, not `count` - i18next treats a `count` variable as a
|
|
27
|
+
request for plural resolution and would look for _one/_few/_other
|
|
28
|
+
suffixes we'd then owe in all fifteen languages, several of which
|
|
29
|
+
have more plural categories than English. */ }
|
|
30
|
+
<Total>
|
|
31
|
+
{ shown === total
|
|
32
|
+
? t('routes_order.step2.summary.count', { total })
|
|
33
|
+
: t('routes_order.step2.summary.filtered', { shown, total }) }
|
|
34
|
+
</Total>
|
|
35
|
+
|
|
36
|
+
{ range !== null && (
|
|
37
|
+
<Range>
|
|
38
|
+
{ range.minDisplay === range.maxDisplay
|
|
39
|
+
? range.minDisplay
|
|
40
|
+
: t('routes_order.step2.summary.range', { min: range.minDisplay, max: range.maxDisplay }) }
|
|
41
|
+
</Range>
|
|
42
|
+
) }
|
|
43
|
+
</Result>
|
|
44
|
+
|
|
45
|
+
{ children }
|
|
46
|
+
</Container>
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
export default Summary;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-01
|
|
5
|
+
*
|
|
6
|
+
* Carries the `box` surface (padding is overridden to something tighter -
|
|
7
|
+
* this is a bar, not a panel) so the count and the sort control sit on the
|
|
8
|
+
* same card background as everything else on the page rather than floating
|
|
9
|
+
* loose on the page background.
|
|
10
|
+
*/
|
|
11
|
+
export const Container = styled.div`
|
|
12
|
+
display: flex;
|
|
13
|
+
flex-direction: column;
|
|
14
|
+
gap: 12px;
|
|
15
|
+
margin-bottom: 15px;
|
|
16
|
+
|
|
17
|
+
&.box {
|
|
18
|
+
padding: 12px 15px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@media (min-width: 1024px) {
|
|
22
|
+
flex-direction: row;
|
|
23
|
+
align-items: center;
|
|
24
|
+
justify-content: space-between;
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
export const Result = styled.div`
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: baseline;
|
|
31
|
+
gap: 8px;
|
|
32
|
+
flex-wrap: wrap;
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
export const Total = styled.strong`
|
|
36
|
+
font-size: ${ props => props.theme.size.l };
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
export const Range = styled.span`
|
|
40
|
+
font-size: ${ props => props.theme.size.s };
|
|
41
|
+
color: ${ props => props.theme.font.faded };
|
|
42
|
+
`;
|