From ea88379559188411ae484aaec79e3fa8dfa62a9c Mon Sep 17 00:00:00 2001 From: andrei Date: Mon, 20 Apr 2026 14:08:52 +0200 Subject: [PATCH 1/7] feat: updated styles of list controls elements --- .../filtersort/StandardFilterSort.tsx | 9 +- vis/stylesheets/modules/list/_header.scss | 342 ++++++++++-------- 2 files changed, 188 insertions(+), 163 deletions(-) diff --git a/vis/js/templates/filtersort/StandardFilterSort.tsx b/vis/js/templates/filtersort/StandardFilterSort.tsx index f01b19275..f1b439ce1 100644 --- a/vis/js/templates/filtersort/StandardFilterSort.tsx +++ b/vis/js/templates/filtersort/StandardFilterSort.tsx @@ -1,4 +1,5 @@ import React, { FC } from "react"; + import FilterDropdown from "../../components/filtersort/FilterDropdown"; import SearchBox from "../../components/filtersort/SearchBox"; import SortDropdown from "../../components/filtersort/SortDropdown"; @@ -16,11 +17,13 @@ const StandardFilterSort: FC = ({
- - {!!displaySort && } +
+ + {!!displaySort && } +
); }; diff --git a/vis/stylesheets/modules/list/_header.scss b/vis/stylesheets/modules/list/_header.scss index e5327aa21..44677f51d 100755 --- a/vis/stylesheets/modules/list/_header.scss +++ b/vis/stylesheets/modules/list/_header.scss @@ -1,246 +1,268 @@ @import "list-show-button"; #explorer_options { - padding: 15px 5px 15px 15px; - background-color: $list_explorer_header; - border-bottom: 2px solid #e8e8e8; + display: flex; + flex-direction: column; + padding: 15px; + background-color: $list_explorer_header; + border-bottom: 2px solid #e8e8e8; + + & .input-group { + width: 100%; + + & .form-control { + width: 100%; + } + } } .explorer_options_color { - background-color: $list_explorer_header; + background-color: $list_explorer_header; } #filter_container { - vertical-align: top; - float: none; - display: inline-block; + vertical-align: top; + float: none; + display: inline-block; } #filter_input { - padding-right: 22px; + padding-right: 22px; } #filter_parameter_container { - vertical-align: top; - float: none; - display: inline-block; + vertical-align: top; + float: none; + display: inline-block; } #sort_container { - vertical-align: top; - float: none; - display: none; - font-family: $link-font-family; + vertical-align: top; + float: none; + display: none; + font-family: $link-font-family; } -#filter_parameter_container .dropdown-menu, #sort_container .dropdown-menu { - width: 140px !important; - min-width: 140px; +#filter_parameter_container .dropdown-menu, +#sort_container .dropdown-menu { + width: 140px !important; + min-width: 140px; } -.btn-default.active, .btn-default:active, .btn-default:focus, .btn-default.active:focus, .btn-default.active.focus, .btn-default.focus, .open > .dropdown-toggle.btn-default { - color: $journal-gray; - background-color: $white; - border-color: $journal-gray; - font-weight: $fontweight700; +.btn-default.active, +.btn-default:active, +.btn-default:focus, +.btn-default.active:focus, +.btn-default.active.focus, +.btn-default.focus, +.open > .dropdown-toggle.btn-default { + color: $journal-gray; + background-color: $white; + border-color: $journal-gray; + font-weight: $fontweight700; } .btn-default { - font-family: $link-font-family; + font-family: $link-font-family; } -.btn:active, .btn.active { - outline: 0; - background-image: none; - -webkit-box-shadow: inset 0 0px 0px rgba(0, 0, 0, 0); - box-shadow: inset 0 0px 0px rgba(0, 0, 0, 0); +.btn:active, +.btn.active { + outline: 0; + background-image: none; + -webkit-box-shadow: inset 0 0px 0px rgba(0, 0, 0, 0); + box-shadow: inset 0 0px 0px rgba(0, 0, 0, 0); } -.btn-default.active:hover, .btn-default:hover { - background-color: $okm-clouds; - color: black; +.btn-default.active:hover, +.btn-default:hover { + background-color: $okm-clouds; + color: black; } #sortby { - margin-right: 5px; - padding: 10px 0px; - line-height: 250%; - font-size: 12px; - font-family: $link-font-family; + margin-right: 5px; + padding: 10px 0px; + line-height: 250%; + font-size: 12px; + font-family: $link-font-family; } #filter_container input { - width: 260px; - height: 28px; - margin-bottom: 10px; - margin-right: 10px; - padding-left: 22px; - border-radius: 27px; - -webkit-border-radius: 27px; - -moz-border-radius: 27px; + width: 260px; + height: 28px; + margin-bottom: 10px; + margin-right: 10px; + padding-left: 22px; + border-radius: 27px; + -webkit-border-radius: 27px; + -moz-border-radius: 27px; } @media screen and (max-width: 1650px) { - #filter_container input { - width: 240px; - } + #filter_container input { + width: 240px; + } } @media screen and (max-width: 1600px) { - #filter_container input { - width: 220px; - } + #filter_container input { + width: 220px; + } } @media screen and (max-width: 1500px) { - #filter_container input { - width: 180px; - } + #filter_container input { + width: 180px; + } } @media screen and (max-width: 1220px) { - .btn-sm, .btn-group-sm > .btn { - font-size: 10px; - } - - #sort-buttons { - padding: 0px 0px; - float: none !important; - } - - #sortby { - font-size: 10px; - } + .btn-sm, + .btn-group-sm > .btn { + font-size: 10px; + } + + #sort-buttons { + padding: 0px 0px; + float: none !important; + } + + #sortby { + font-size: 10px; + } } @media screen and (max-width: 1160px) { - #sort_container { - float: none; - margin-top: 0px; - } + #sort_container { + float: none; + margin-top: 0px; + } - #filter_container { - float: none; - } + #filter_container { + float: none; + } } @media screen and (max-width: 1130px) { - #filter_container { - display: block; + #filter_container { + display: block; - input { - width: 230px; - } + input { + width: 230px; } + } - #filter_params, #sort { - margin-bottom: 0px; - } + #filter_params, + #sort { + margin-bottom: 0px; + } } -@if $skin == 'cris_vis' { +@if $skin == "cris_vis" { + #filter_container { + vertical-align: top; + float: left; + display: block; + } + #filter_parameter_container { + vertical-align: top; + float: right; + display: block; + } - #filter_container { - vertical-align: top; - float: left; - display: block; - } - #filter_parameter_container { - vertical-align: top; - float: right; - display: block; - } + #sort_container { + width: 150px; + vertical-align: top; + float: right; + display: none; + } - #sort_container { - width: 150px; - vertical-align: top; - float: right; - display: none; - } + #sort { + width: 150px; + } - #sort { - width: 150px; - } + #filter_container input { + margin-bottom: 0px; + margin-right: 0px; + } - #filter_container input { - margin-bottom: 0px; - margin-right: 0px; - } + #explorer_options { + border-bottom: 2px solid #eaeaee; + } - #explorer_options { - border-bottom: 2px solid #EAEAEE; + @media screen and (max-width: 1000px) { + #sort_container { + float: none; + margin-top: 10px; } - @media screen and (max-width: 1000px) { - #sort_container { - float: none; - margin-top: 10px; - } - - #filter_container { - float: none; - } + #filter_container { + float: none; } + } } -@if $skin == 'linkedcat' { - #sort_container .dropdown-menu { - width: 200px !important; - min-width:140px; - } +@if $skin == "linkedcat" { + #sort_container .dropdown-menu { + width: 200px !important; + min-width: 140px; + } - #sort { - border-radius: 17px; - -webkit-border-radius: 17px; - -moz-border-radius: 17px; - width: 200px; - } + #sort { + border-radius: 17px; + -webkit-border-radius: 17px; + -moz-border-radius: 17px; + width: 200px; + } + + #filter_container input { + width: 360px; + } + @media screen and (max-width: 1650px) { #filter_container input { - width: 360px; + width: 360px; } + } - @media screen and (max-width: 1650px) { - #filter_container input { - width: 360px; - } + @media screen and (max-width: 1600px) { + #filter_container input { + width: 360px; } + } - @media screen and (max-width: 1600px) { - #filter_container input { - width: 360px; - } + @media screen and (max-width: 1500px) { + #filter_container input { + width: 260px; } + } - @media screen and (max-width: 1500px) { - #filter_container input { - width: 260px; - } + @media screen and (max-width: 1260px) { + #sort { + border-radius: 17px; + -webkit-border-radius: 17px; + -moz-border-radius: 17px; + width: 160px; } - - @media screen and (max-width: 1260px) { - #sort { - border-radius: 17px; - -webkit-border-radius: 17px; - -moz-border-radius: 17px; - width: 160px; - } - #sort_container .dropdown-menu { - width: 160px !important; - } + #sort_container .dropdown-menu { + width: 160px !important; } + } - @media screen and (max-width: 1130px) { - #filter_container { - display: block; + @media screen and (max-width: 1130px) { + #filter_container { + display: block; - input { - width: 240px; - } - } + input { + width: 240px; + } } + } } -#filter_params, #sort, #scale-menu { - font-weight: normal; +#filter_params, +#sort, +#scale-menu { + font-weight: normal; } From 418e0f813382f6ebf08a9b3ebfc768e726633257 Mon Sep 17 00:00:00 2001 From: andrei Date: Mon, 20 Apr 2026 14:10:43 +0200 Subject: [PATCH 2/7] refactor: update test cases --- .../__snapshots__/list-base.test.jsx.snap | 812 +++++++++--------- .../__snapshots__/listfilter.test.jsx.snap | 338 ++++---- 2 files changed, 578 insertions(+), 572 deletions(-) diff --git a/vis/test/snapshot/__snapshots__/list-base.test.jsx.snap b/vis/test/snapshot/__snapshots__/list-base.test.jsx.snap index bb0af6be1..085c3758d 100644 --- a/vis/test/snapshot/__snapshots__/list-base.test.jsx.snap +++ b/vis/test/snapshot/__snapshots__/list-base.test.jsx.snap @@ -93,245 +93,247 @@ exports[`List entries component snapshot (BASE) > matches a snapshot (zoomed-in, /> -
+
- - +
-
-
- -
+ + +
  • - - -
  • -
  • - + Title +
  • + + +
  • - - -
  • - +
    + Authors +
    + + +
  • + +
    + Year +
    +
    +
  • + + @@ -658,245 +660,247 @@ exports[`List entries component snapshot (BASE) > matches a snapshot (zoomed-out /> -
    +
    - - +
    -
    -
    - -
    + + +
  • - - -
  • -
  • - + Title +
  • + + +
  • - - -
  • - +
    + Authors +
    + + +
  • + +
    + Year +
    +
    +
  • + + diff --git a/vis/test/snapshot/__snapshots__/listfilter.test.jsx.snap b/vis/test/snapshot/__snapshots__/listfilter.test.jsx.snap index 3637fecf4..3b0d47f13 100644 --- a/vis/test/snapshot/__snapshots__/listfilter.test.jsx.snap +++ b/vis/test/snapshot/__snapshots__/listfilter.test.jsx.snap @@ -34,207 +34,209 @@ exports[`List filter component snapshot > matches a snapshot 1`] = ` /> -
    +
    - - +
    -
    -
    - - +
    From eabaa4d42082376b6f4230a566903a10512304a4 Mon Sep 17 00:00:00 2001 From: andrei Date: Mon, 20 Apr 2026 14:56:27 +0200 Subject: [PATCH 3/7] feat: filters in List controls can be disabled --- vis/js/components/FilterSort.tsx | 13 +++++++------ vis/js/default-config.ts | 2 ++ vis/js/reducers/list.ts | 2 ++ vis/js/types/configs/config.ts | 1 + vis/js/types/state/slices/list.ts | 1 + 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/vis/js/components/FilterSort.tsx b/vis/js/components/FilterSort.tsx index 5bac4826c..503b1c7f8 100644 --- a/vis/js/components/FilterSort.tsx +++ b/vis/js/components/FilterSort.tsx @@ -1,25 +1,26 @@ import React, { FC } from "react"; import { connect } from "react-redux"; -import StandardFilterSort from "../templates/filtersort/StandardFilterSort"; + import BasicFilterSort from "../templates/filtersort/BasicFilterSort"; +import StandardFilterSort from "../templates/filtersort/StandardFilterSort"; import { State } from "../types"; export interface FilterSortProps { - showList: boolean; + showSort: boolean; showFilter: boolean; color: string | null; } -const FilterSort: FC = ({ showList, showFilter, color }) => { +const FilterSort: FC = ({ showSort, showFilter, color }) => { if (showFilter) { - return ; + return ; } - return ; + return ; }; const mapStateToProps = (state: State) => ({ - showList: state.list.show, + showSort: state.list.showSort, showFilter: state.list.showFilter, color: state.selectedBubble ? state.selectedBubble.color : null, }); diff --git a/vis/js/default-config.ts b/vis/js/default-config.ts index 1b41b5d89..1e4bc1370 100644 --- a/vis/js/default-config.ts +++ b/vis/js/default-config.ts @@ -161,6 +161,8 @@ var config: Config = { //display filter menu dropdown filter_menu_dropdown: false, + // display sort menu dropdown + sort_menu_dropdown: false, list_images: [], list_images_path: "images/", visual_distributions: false, diff --git a/vis/js/reducers/list.ts b/vis/js/reducers/list.ts index 9f91670ce..c2b4b0bd1 100644 --- a/vis/js/reducers/list.ts +++ b/vis/js/reducers/list.ts @@ -3,6 +3,7 @@ import { Config, Context } from "../types"; const list = ( state = { show: true, + showSort: false, searchValue: "", showFilter: false, filterField: null, @@ -35,6 +36,7 @@ const list = ( return { ...state, show: !!config.show_list, + showSort: !!config.sort_menu_dropdown, showFilter: config.filter_menu_dropdown, filterField: config.filter_field, filterValue: config.filter_options ? config.filter_options[0] : null, diff --git a/vis/js/types/configs/config.ts b/vis/js/types/configs/config.ts index f8cf3fba3..25b90eb04 100644 --- a/vis/js/types/configs/config.ts +++ b/vis/js/types/configs/config.ts @@ -96,6 +96,7 @@ export type Config = { highlight_query_fields: string[]; filter_menu_dropdown: boolean; + sort_menu_dropdown: boolean; list_images: string[]; list_images_path: string; visual_distributions: boolean; diff --git a/vis/js/types/state/slices/list.ts b/vis/js/types/state/slices/list.ts index 5a6353268..37832b2ac 100644 --- a/vis/js/types/state/slices/list.ts +++ b/vis/js/types/state/slices/list.ts @@ -13,6 +13,7 @@ export interface List { noCitationDoctypes: string[]; searchValue: string; show: boolean; + showSort: boolean; showDocumentType: boolean; showFilter: boolean; showKeywords: boolean; From f84b0eaae719de6422c0056a048c883ed11ace71 Mon Sep 17 00:00:00 2001 From: andrei Date: Mon, 20 Apr 2026 15:15:05 +0200 Subject: [PATCH 4/7] feat: update test cases --- vis/test/component/listfilter.test.jsx | 53 +++++---- .../__snapshots__/listfilter.test.jsx.snap | 104 ------------------ vis/test/store/list.test.js | 14 +-- 3 files changed, 37 insertions(+), 134 deletions(-) diff --git a/vis/test/component/listfilter.test.jsx b/vis/test/component/listfilter.test.jsx index 2d6fd5735..c8635be15 100644 --- a/vis/test/component/listfilter.test.jsx +++ b/vis/test/component/listfilter.test.jsx @@ -1,11 +1,12 @@ -import { expect, describe, it, vitest } from 'vitest'; +import { fireEvent, render } from "@testing-library/react"; import React from "react"; import { Provider } from "react-redux"; -import { render, fireEvent } from "@testing-library/react"; import configureStore from "redux-mock-store"; -import { search, filter, sort } from "../../js/actions"; +import { describe, expect, it, vitest } from "vitest"; + +import { filter, search, sort } from "../../js/actions"; import FilterSort from "../../js/components/FilterSort"; -import LocalizationProvider from '../../js/components/LocalizationProvider'; +import LocalizationProvider from "../../js/components/LocalizationProvider"; const mockStore = configureStore([]); const setup = (overrideListObject = {}, overrideStoreObject = {}) => { @@ -33,7 +34,7 @@ const setup = (overrideListObject = {}, overrideStoreObject = {}) => { sort_by_label: "Sort by: ", }, }, - overrideStoreObject + overrideStoreObject, ); return storeObject; @@ -47,7 +48,7 @@ describe("List filter component", () => { const result = render( - + , ); expect(result.container.querySelector("#explorer_options")).not.toBe(null); @@ -62,11 +63,11 @@ describe("List filter component", () => { const result = render( - + , ); expect( - result.container.querySelector("#filter_input").getAttribute("value") + result.container.querySelector("#filter_input").getAttribute("value"), ).toEqual(SEARCH_TEXT); }); @@ -78,7 +79,7 @@ describe("List filter component", () => { const result = render( - + , ); expect(result.container.querySelector("#searchclear")).toBe(null); @@ -90,7 +91,7 @@ describe("List filter component", () => { // const SEARCH_TEXT = "some text"; // const EXPECTED_PAYLOAD = search(SEARCH_TEXT); - // const storeObject = setup({ + // const storeObject = setup({ // searchValue: SEARCH_TEXT, // showFilter: true // }); @@ -117,9 +118,9 @@ describe("List filter component", () => { // vitest.advanceTimersByTime(300); // vitest.runAllTimers(); - + // const actions = store.getActions(); - + // expect(actions).toEqual([EXPECTED_PAYLOAD]); // }); @@ -132,7 +133,7 @@ describe("List filter component", () => { const result = render( - + , ); const searchClear = result.container.querySelector("#searchclear"); @@ -153,12 +154,12 @@ describe("List filter component", () => { const result = render( - + , ); - expect(result.container.querySelector("#filter_params").textContent).toContain( - storeObject.localization[FILTER_ID] - ); + expect( + result.container.querySelector("#filter_params").textContent, + ).toContain(storeObject.localization[FILTER_ID]); }); it("triggers a correct redux action when option is clicked", () => { @@ -171,10 +172,12 @@ describe("List filter component", () => { const result = render( - + , ); - const filterOption = result.container.querySelector("#filter_option_" + EXPECTED_ID); + const filterOption = result.container.querySelector( + `#filter_option_${EXPECTED_ID}`, + ); fireEvent.click(filterOption); const actions = store.getActions(); @@ -188,6 +191,7 @@ describe("List filter component", () => { const SORT_ID = "relevance"; const storeObject = setup({ showFilter: false, + showSort: true, showDropdownSort: true, sortValue: SORT_ID, }); @@ -196,11 +200,11 @@ describe("List filter component", () => { const result = render( - + , ); expect(result.container.querySelector("#sort").textContent).toContain( - storeObject.localization[SORT_ID] + storeObject.localization[SORT_ID], ); }); @@ -210,6 +214,7 @@ describe("List filter component", () => { const SORT_ID = "relevance"; const storeObject = setup({ showFilter: false, + showSort: true, showDropdownSort: true, sortValue: SORT_ID, }); @@ -218,10 +223,12 @@ describe("List filter component", () => { const result = render( - + , ); - const sortOption = result.container.querySelector("#sort_option_" + EXPECTED_ID); + const sortOption = result.container.querySelector( + `#sort_option_${EXPECTED_ID}`, + ); fireEvent.click(sortOption); const actions = store.getActions(); diff --git a/vis/test/snapshot/__snapshots__/listfilter.test.jsx.snap b/vis/test/snapshot/__snapshots__/listfilter.test.jsx.snap index 3b0d47f13..64cd70b35 100644 --- a/vis/test/snapshot/__snapshots__/listfilter.test.jsx.snap +++ b/vis/test/snapshot/__snapshots__/listfilter.test.jsx.snap @@ -134,110 +134,6 @@ exports[`List filter component snapshot > matches a snapshot 1`] = ` -
    -
    - - -
    -
    `; diff --git a/vis/test/store/list.test.js b/vis/test/store/list.test.js index e1b5455ec..6886c201d 100644 --- a/vis/test/store/list.test.js +++ b/vis/test/store/list.test.js @@ -1,15 +1,14 @@ -import { expect, describe, it } from 'vitest'; +import { describe, expect, it } from "vitest"; import { - toggleList, - showList, - search, filter, - sort, highlightArea, + search, + showList, + sort, + toggleList, updateDimensions, } from "../../js/actions"; - import listReducer from "../../js/reducers/list"; describe("list state", () => { @@ -76,6 +75,7 @@ describe("list state", () => { show: true, searchValue: "", showFilter: false, + showSort: false, filterField: null, filterValue: null, filterOptions: [], @@ -152,7 +152,7 @@ describe("list state", () => { const result = listReducer( INITIAL_STATE, - updateDimensions({}, { height: 500 }) + updateDimensions({}, { height: 500 }), ); expect(result).toEqual(EXPECTED_STATE); From f30a5d17543331e7e5165abc190319b0120ffb19 Mon Sep 17 00:00:00 2001 From: andrei Date: Tue, 21 Apr 2026 10:11:22 +0200 Subject: [PATCH 5/7] feat: automatic list sorting when 'Sort by' filter is not displayed --- vis/js/reducers/list.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/vis/js/reducers/list.ts b/vis/js/reducers/list.ts index c2b4b0bd1..a63b84655 100644 --- a/vis/js/reducers/list.ts +++ b/vis/js/reducers/list.ts @@ -33,6 +33,8 @@ const list = ( switch (action.type) { case "INITIALIZE": + const initialSortValue = getEffectiveInitialSortValue(config, context); + return { ...state, show: !!config.show_list, @@ -41,8 +43,8 @@ const list = ( filterField: config.filter_field, filterValue: config.filter_options ? config.filter_options[0] : null, filterOptions: config.filter_options, - sortValue: getSortValue(config, context), - defaultSort: getSortValue(config, context), + sortValue: initialSortValue, + defaultSort: initialSortValue, sortOptions: config.sort_options, showDocumentType: config.show_resulttype, showMetrics: config.metric_list, @@ -130,3 +132,13 @@ const getSortValue = (config: Config, context: Context) => { return config.sort_options[0]; }; + +const getEffectiveInitialSortValue = (config: Config, context: Context) => { + const fallbackSortValue = getSortValue(config, context); + + if (!config.sort_menu_dropdown && config.sort_options?.includes("title")) { + return "title"; + } + + return fallbackSortValue; +}; From 4db3614a6da0460ca44840e2b5d8656cd7145851 Mon Sep 17 00:00:00 2001 From: andrei Date: Thu, 23 Apr 2026 12:39:05 +0200 Subject: [PATCH 6/7] bugfix: the sort by filter is not displayed when list is hidden --- vis/js/components/FilterSort.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/vis/js/components/FilterSort.tsx b/vis/js/components/FilterSort.tsx index 503b1c7f8..8eaac84c7 100644 --- a/vis/js/components/FilterSort.tsx +++ b/vis/js/components/FilterSort.tsx @@ -8,18 +8,29 @@ import { State } from "../types"; export interface FilterSortProps { showSort: boolean; showFilter: boolean; + isListVisible: boolean; color: string | null; } -const FilterSort: FC = ({ showSort, showFilter, color }) => { +const FilterSort: FC = ({ + showSort, + showFilter, + color, + isListVisible, +}) => { + const isSortFilterDisplayed = showSort && isListVisible; + if (showFilter) { - return ; + return ( + + ); } - return ; + return ; }; const mapStateToProps = (state: State) => ({ + isListVisible: state.list.show, showSort: state.list.showSort, showFilter: state.list.showFilter, color: state.selectedBubble ? state.selectedBubble.color : null, From 546f718ddc405678515bea445f870507ceb6f6a5 Mon Sep 17 00:00:00 2001 From: andrei Date: Tue, 28 Apr 2026 12:12:19 +0200 Subject: [PATCH 7/7] refactor: styles of the search clear cross in the list --- vis/stylesheets/components/_buttons.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vis/stylesheets/components/_buttons.scss b/vis/stylesheets/components/_buttons.scss index c734a6a0f..ddb679ed2 100755 --- a/vis/stylesheets/components/_buttons.scss +++ b/vis/stylesheets/components/_buttons.scss @@ -1,12 +1,12 @@ /* Clear button in input form */ #searchclear { position: absolute; - right: 15px; + right: 7px; top: 0; - bottom: 10px; + bottom: 16px; height: 14px; margin: auto; - font-size: 14px; + font-size: 20px; cursor: pointer; color: #ccc; z-index: 100;